Merge "Managed System Updates API"
diff --git a/Android.bp b/Android.bp
index 7e038ce8..f40aab1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -92,6 +92,7 @@
         "core/java/android/app/IWallpaperManagerCallback.aidl",
         "core/java/android/app/admin/IDeviceAdminService.aidl",
         "core/java/android/app/admin/IDevicePolicyManager.aidl",
+        "core/java/android/app/admin/StartInstallingUpdateCallback.aidl",
         "core/java/android/app/trust/IStrongAuthTracker.aidl",
         "core/java/android/app/trust/ITrustManager.aidl",
         "core/java/android/app/trust/ITrustListener.aidl",
diff --git a/api/current.txt b/api/current.txt
index e84bc8d..abd3c31 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6598,6 +6598,7 @@
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, int);
+    method public void installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallUpdateCallback);
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isAffiliatedUser();
@@ -6840,6 +6841,16 @@
     field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
   }
 
+  public static abstract class DevicePolicyManager.InstallUpdateCallback {
+    ctor public DevicePolicyManager.InstallUpdateCallback();
+    method public void onInstallUpdateError(int, java.lang.String);
+    field public static final int UPDATE_ERROR_BATTERY_LOW = 5; // 0x5
+    field public static final int UPDATE_ERROR_FILE_NOT_FOUND = 4; // 0x4
+    field public static final int UPDATE_ERROR_INCORRECT_OS_VERSION = 2; // 0x2
+    field public static final int UPDATE_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int UPDATE_ERROR_UPDATE_FILE_INVALID = 3; // 0x3
+  }
+
   public static abstract interface DevicePolicyManager.OnClearApplicationUserDataListener {
     method public abstract void onApplicationUserDataCleared(java.lang.String, boolean);
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 1661963..75a2cc4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -441,10 +441,10 @@
   }
 
   public class KeyguardManager {
-    method public void setPrivateNotificationsAllowed(boolean);
-    method public boolean getPrivateNotificationsAllowed();
     method public android.content.Intent createConfirmFactoryResetCredentialIntent(java.lang.CharSequence, java.lang.CharSequence, java.lang.CharSequence);
+    method public boolean getPrivateNotificationsAllowed();
     method public void requestDismissKeyguard(android.app.Activity, java.lang.CharSequence, android.app.KeyguardManager.KeyguardDismissCallback);
+    method public void setPrivateNotificationsAllowed(boolean);
   }
 
   public class Notification implements android.os.Parcelable {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 24ee7f7..00c1863 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,6 +53,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -87,6 +88,7 @@
 import com.android.org.conscrypt.TrustedCertificateStore;
 
 import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1930,6 +1932,48 @@
     public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3;
 
     /**
+     * Callback used in {@link #installSystemUpdate} to indicate that there was an error while
+     * trying to install an update.
+     */
+    public abstract static class InstallUpdateCallback {
+        /** Represents an unknown error while trying to install an update. */
+        public static final int UPDATE_ERROR_UNKNOWN = 1;
+
+        /** Represents the update file being intended for different OS version. */
+        public static final int UPDATE_ERROR_INCORRECT_OS_VERSION = 2;
+
+        /**
+         * Represents the update file being wrong, i.e. payloads are mismatched, wrong compressions
+         * method.
+         */
+        public static final int UPDATE_ERROR_UPDATE_FILE_INVALID = 3;
+
+        /** Represents that the file could not be found. */
+        public static final int UPDATE_ERROR_FILE_NOT_FOUND = 4;
+
+        /** Represents the battery being too low to apply an update. */
+        public static final int UPDATE_ERROR_BATTERY_LOW = 5;
+
+        /** Method invoked when there was an error while installing an update. */
+        public void onInstallUpdateError(
+                @InstallUpdateCallbackErrorConstants int errorCode, String errorMessage) {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "UPDATE_ERROR_" }, value = {
+            InstallUpdateCallback.UPDATE_ERROR_UNKNOWN,
+            InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION,
+            InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
+            InstallUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND,
+            InstallUpdateCallback.UPDATE_ERROR_BATTERY_LOW
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InstallUpdateCallbackErrorConstants {}
+
+    /**
      * Return true if the given administrator component is currently active (enabled) in the system.
      *
      * @param admin The administrator component to check for.
@@ -6796,7 +6840,6 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface CreateAndManageUserFlags {}
 
-
     /**
      * Called by a device owner to create a user with the specified name and a given component of
      * the calling package as profile owner. The UserHandle returned by this method should not be
@@ -9827,6 +9870,62 @@
     }
 
     /**
+     * Called by device owner to install a system update from the given file. The device will be
+     * rebooted in order to finish installing the update. Note that if the device is rebooted, this
+     * doesn't necessarily mean that the update has been applied successfully. The caller should
+     * additionally check the system version with {@link android.os.Build#FINGERPRINT} or {@link
+     * android.os.Build.VERSION}. If an error occurs during processing the OTA before the reboot,
+     * the caller will be notified by {@link InstallUpdateCallback}. If device does not have
+     * sufficient battery level, the installation will fail with error {@link
+     * InstallUpdateCallback#UPDATE_ERROR_BATTERY_LOW}.
+     *
+     * @param admin The {@link DeviceAdminReceiver} that this request is associated with.
+     * @param updateFilePath An Uri of the file that contains the update. The file should be
+     * readable by the calling app.
+     * @param executor The executor through which the callback should be invoked.
+     * @param callback A callback object that will inform the caller when installing an update
+     * fails.
+     */
+    public void installSystemUpdate(
+            @NonNull ComponentName admin, @NonNull Uri updateFilePath,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull InstallUpdateCallback callback) {
+        throwIfParentInstance("installUpdate");
+        if (mService == null) {
+            return;
+        }
+        try (ParcelFileDescriptor fileDescriptor = mContext.getContentResolver()
+                    .openFileDescriptor(updateFilePath, "r")) {
+            mService.installUpdateFromFile(
+                    admin, fileDescriptor, new StartInstallingUpdateCallback.Stub() {
+                        @Override
+                        public void onStartInstallingUpdateError(
+                                int errorCode, String errorMessage) {
+                            executeCallback(errorCode, errorMessage, executor, callback);
+                        }
+                    });
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (FileNotFoundException e) {
+            Log.w(TAG, e);
+            executeCallback(
+                    InstallUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND, Log.getStackTraceString(e),
+                    executor, callback);
+        } catch (IOException e) {
+            Log.w(TAG, e);
+            executeCallback(
+                    InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, Log.getStackTraceString(e),
+                    executor, callback);
+        }
+    }
+
+    private void executeCallback(int errorCode, String errorMessage,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull InstallUpdateCallback callback) {
+        executor.execute(() -> callback.onInstallUpdateError(errorCode, errorMessage));
+    }
+
+    /**
      * Returns the system-wide Private DNS mode.
      *
      * @param admin which {@link DeviceAdminReceiver} this request is associated with.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 918c127..60f79d6 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -20,6 +20,7 @@
 import android.app.admin.NetworkEvent;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
+import android.app.admin.StartInstallingUpdateCallback;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.PasswordMetrics;
@@ -419,4 +420,6 @@
     String getGlobalPrivateDnsHost(in ComponentName admin);
 
     void grantDeviceIdsAccessToProfileOwner(in ComponentName who, int userId);
+
+    void installUpdateFromFile(in ComponentName admin, in ParcelFileDescriptor updateFileDescriptor, in StartInstallingUpdateCallback listener);
 }
diff --git a/core/java/android/app/admin/StartInstallingUpdateCallback.aidl b/core/java/android/app/admin/StartInstallingUpdateCallback.aidl
new file mode 100644
index 0000000..df04707
--- /dev/null
+++ b/core/java/android/app/admin/StartInstallingUpdateCallback.aidl
@@ -0,0 +1,27 @@
+/*
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.app.admin;
+
+/**
+* Callback used between {@link DevicePolicyManager} and {@link DevicePolicyManagerService} to
+* indicate that starting installing an update is finished.
+* {@hide}
+*/
+oneway interface StartInstallingUpdateCallback {
+    void onStartInstallingUpdateError(int errorCode, String errorMessage);
+}
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java
new file mode 100644
index 0000000..05912a5
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/AbUpdateInstaller.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.app.admin.DevicePolicyManager.InstallUpdateCallback;
+import android.app.admin.StartInstallingUpdateCallback;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.UpdateEngine;
+import android.os.UpdateEngineCallback;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+/**
+ * Used for installing an update on <a href="https://source.android.com/devices/tech/ota/ab">AB
+ * devices.</a>
+ * <p>This logic is specific to GOTA and should be modified by OEMs using a different AB update
+ * system.</p>
+ */
+class AbUpdateInstaller extends UpdateInstaller {
+    private static final String PAYLOAD_BIN = "payload.bin";
+    private static final String PAYLOAD_PROPERTIES_TXT = "payload_properties.txt";
+    //https://en.wikipedia.org/wiki/Zip_(file_format)#Local_file_header
+    private static final int OFFSET_TO_FILE_NAME = 30;
+    // kDownloadStateInitializationError constant from system/update_engine/common/error_code.h.
+    private static final int DOWNLOAD_STATE_INITIALIZATION_ERROR = 20;
+    private long mSizeForUpdate;
+    private long mOffsetForUpdate;
+    private List<String> mProperties;
+    private Enumeration<? extends ZipEntry> mEntries;
+    private ZipFile mPackedUpdateFile;
+    private static final Map<Integer, Integer> errorCodesMap = buildErrorCodesMap();
+    private static final Map<Integer, String> errorStringsMap = buildErrorStringsMap();
+    public static final String UNKNOWN_ERROR = "Unknown error with error code = ";
+    private boolean mUpdateInstalled;
+
+    private static Map<Integer, Integer> buildErrorCodesMap() {
+        Map<Integer, Integer> map = new HashMap<>();
+        map.put(UpdateEngine.ErrorCodeConstants.ERROR, InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
+        map.put(
+                DOWNLOAD_STATE_INITIALIZATION_ERROR,
+                InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION);
+
+        // Error constants corresponding to errors related to bad update file.
+        map.put(
+                UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
+                InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+        map.put(
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
+                InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+        map.put(
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
+                InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+        map.put(
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
+                InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+
+        // Error constants corresponding to errors related to devices bad state.
+        map.put(
+                UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
+                InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
+        map.put(
+                UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
+                InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
+        map.put(
+                UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
+                InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
+        map.put(
+                UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
+                InstallUpdateCallback.UPDATE_ERROR_UNKNOWN);
+
+        return map;
+    }
+
+    private static Map<Integer, String> buildErrorStringsMap() {
+        Map<Integer, String> map = new HashMap<>();
+        map.put(UpdateEngine.ErrorCodeConstants.ERROR, UNKNOWN_ERROR);
+        map.put(
+                DOWNLOAD_STATE_INITIALIZATION_ERROR,
+                "The delta update payload was targeted for another version or the source partition"
+                        + "was modified after it was installed");
+        map.put(
+                UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
+                "Failed to finish the configured postinstall works.");
+        map.put(
+                UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
+                "Failed to open one of the partitions it tried to write to or read data from.");
+        map.put(
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
+                "Payload mismatch error.");
+        map.put(
+                UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
+                "Failed to read the payload data from the given URL.");
+        map.put(
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR, "Payload hash error.");
+        map.put(
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
+                "Payload size mismatch error.");
+        map.put(
+                UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
+                "Failed to verify the signature of the payload.");
+        map.put(
+                UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
+                "The payload has been successfully installed,"
+                        + "but the active slot was not flipped.");
+        return map;
+    }
+
+    AbUpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor,
+            StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector,
+            DevicePolicyConstants constants) {
+        super(context, updateFileDescriptor, callback, injector, constants);
+        mUpdateInstalled = false;
+    }
+
+    @Override
+    public void installUpdateInThread() {
+        if (mUpdateInstalled) {
+            throw new IllegalStateException("installUpdateInThread can be called only once.");
+        }
+        try {
+            setState();
+            applyPayload(Paths.get(mCopiedUpdateFile.getAbsolutePath()).toUri().toString());
+        } catch (ZipException e) {
+            Log.w(UpdateInstaller.TAG, e);
+            notifyCallbackOnError(
+                    InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
+                    Log.getStackTraceString(e));
+        } catch (IOException e) {
+            Log.w(UpdateInstaller.TAG, e);
+            notifyCallbackOnError(
+                    InstallUpdateCallback.UPDATE_ERROR_UNKNOWN, Log.getStackTraceString(e));
+        }
+    }
+
+    private void setState() throws IOException {
+        mUpdateInstalled = true;
+        mPackedUpdateFile = new ZipFile(mCopiedUpdateFile);
+        mProperties = new ArrayList<>();
+        mSizeForUpdate = -1;
+        mOffsetForUpdate = 0;
+        mEntries = mPackedUpdateFile.entries();
+    }
+
+    private UpdateEngine buildBoundUpdateEngine() {
+        UpdateEngine updateEngine = new UpdateEngine();
+        updateEngine.bind(new DelegatingUpdateEngineCallback(this, updateEngine));
+        return updateEngine;
+    }
+
+    private void applyPayload(String updatePath) throws IOException {
+        if (!updateStateForPayload()) {
+            return;
+        }
+        String[] headerKeyValuePairs = mProperties.stream().toArray(String[]::new);
+        if (mSizeForUpdate == -1) {
+            Log.w(UpdateInstaller.TAG, "Failed to find payload entry in the given package.");
+            notifyCallbackOnError(
+                    InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
+                    "Failed to find payload entry in the given package.");
+            return;
+        }
+
+        UpdateEngine updateEngine = buildBoundUpdateEngine();
+        updateEngine.applyPayload(
+                updatePath, mOffsetForUpdate, mSizeForUpdate, headerKeyValuePairs);
+    }
+
+    private boolean updateStateForPayload() throws IOException {
+        long offset = 0;
+        while (mEntries.hasMoreElements()) {
+            ZipEntry entry = mEntries.nextElement();
+
+            String name = entry.getName();
+            offset += buildOffsetForEntry(entry, name);
+            if (entry.isDirectory()) {
+                offset -= entry.getCompressedSize();
+                continue;
+            }
+            if (PAYLOAD_BIN.equals(name)) {
+                if (entry.getMethod() != ZipEntry.STORED) {
+                    Log.w(UpdateInstaller.TAG, "Invalid compression method.");
+                    notifyCallbackOnError(
+                            InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
+                            "Invalid compression method.");
+                    return false;
+                }
+                mSizeForUpdate = entry.getCompressedSize();
+                mOffsetForUpdate = offset - entry.getCompressedSize();
+            } else if (PAYLOAD_PROPERTIES_TXT.equals(name)) {
+                updatePropertiesForEntry(entry);
+            }
+        }
+        return true;
+    }
+
+    private long buildOffsetForEntry(ZipEntry entry, String name) {
+        return OFFSET_TO_FILE_NAME + name.length() + entry.getCompressedSize()
+                + (entry.getExtra() == null ? 0 : entry.getExtra().length);
+    }
+
+    private void updatePropertiesForEntry(ZipEntry entry) throws IOException {
+        try (BufferedReader bufferedReader = new BufferedReader(
+                new InputStreamReader(mPackedUpdateFile.getInputStream(entry)))) {
+            String line;
+            /* Neither @line nor @mProperties are size constraint since there is a few properties
+            with limited size. */
+            while ((line = bufferedReader.readLine()) != null) {
+                mProperties.add(line);
+            }
+        }
+    }
+
+    private static class DelegatingUpdateEngineCallback extends UpdateEngineCallback {
+        private UpdateInstaller mUpdateInstaller;
+        private UpdateEngine mUpdateEngine;
+
+        DelegatingUpdateEngineCallback(
+                UpdateInstaller updateInstaller, UpdateEngine updateEngine) {
+            mUpdateInstaller = updateInstaller;
+            mUpdateEngine = updateEngine;
+        }
+
+        @Override
+        public void onStatusUpdate(int statusCode, float percentage) {
+            return;
+        }
+
+        @Override
+        public void onPayloadApplicationComplete(int errorCode) {
+            mUpdateEngine.unbind();
+            if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS) {
+                mUpdateInstaller.notifyCallbackOnSuccess();
+            } else {
+                mUpdateInstaller.notifyCallbackOnError(
+                        errorCodesMap.getOrDefault(
+                                errorCode, InstallUpdateCallback.UPDATE_ERROR_UNKNOWN),
+                        errorStringsMap.getOrDefault(errorCode, UNKNOWN_ERROR + errorCode));
+            }
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 5926bdd..6462d16 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -17,7 +17,9 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.IDevicePolicyManager;
+import android.app.admin.StartInstallingUpdateCallback;
 import android.content.ComponentName;
+import android.os.ParcelFileDescriptor;
 
 import com.android.server.SystemService;
 
@@ -91,4 +93,8 @@
 
     @Override
     public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { }
+
+    @Override
+    public void installUpdateFromFile(ComponentName admin,
+            ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {}
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java
index 71fea02..fd59b43 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java
@@ -42,6 +42,12 @@
     private static final String DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY =
             "das_died_service_stable_connection_threshold_sec";
 
+    private static final String BATTERY_THRESHOLD_NOT_CHARGING_KEY =
+            "battery_threshold_not_charging";
+
+    private static final String BATTERY_THRESHOLD_CHARGING_KEY =
+            "battery_threshold_charging";
+
     /**
      * The back-off before re-connecting, when a service binding died, due to the owner
      * crashing repeatedly.
@@ -63,6 +69,17 @@
      */
     public final long DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC;
 
+    /**
+     * Battery threshold for installing system update while the device is not charging.
+     */
+    public final int BATTERY_THRESHOLD_NOT_CHARGING;
+
+    /**
+     * Battery threshold for installing system update while the device is charging.
+     */
+    public final int BATTERY_THRESHOLD_CHARGING;
+
+
     private DevicePolicyConstants(String settings) {
 
         final KeyValueListParser parser = new KeyValueListParser(',');
@@ -87,6 +104,12 @@
                 DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY,
                 TimeUnit.MINUTES.toSeconds(2));
 
+        int batteryThresholdNotCharging = parser.getInt(
+                BATTERY_THRESHOLD_NOT_CHARGING_KEY, 40);
+
+        int batteryThresholdCharging = parser.getInt(
+                BATTERY_THRESHOLD_CHARGING_KEY, 20);
+
         // Set minimum: 5 seconds.
         dasDiedServiceReconnectBackoffSec = Math.max(5, dasDiedServiceReconnectBackoffSec);
 
@@ -103,6 +126,8 @@
         DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC = dasDiedServiceReconnectMaxBackoffSec;
         DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC =
                 dasDiedServiceStableConnectionThresholdSec;
+        BATTERY_THRESHOLD_NOT_CHARGING = batteryThresholdNotCharging;
+        BATTERY_THRESHOLD_CHARGING = batteryThresholdCharging;
     }
 
     public static DevicePolicyConstants loadFromString(String settings) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bbbc40c..7751b4a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -113,6 +113,7 @@
 import android.app.admin.PasswordMetrics;
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.StartInstallingUpdateCallback;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.backup.IBackupManager;
@@ -379,6 +380,8 @@
     private static final Set<String> GLOBAL_SETTINGS_DEPRECATED;
     private static final Set<String> SYSTEM_SETTINGS_WHITELIST;
     private static final Set<Integer> DA_DISALLOWED_POLICIES;
+    private static final String AB_DEVICE_KEY = "ro.build.ab_update";
+
     static {
         SECURE_SETTINGS_WHITELIST = new ArraySet<>();
         SECURE_SETTINGS_WHITELIST.add(Settings.Secure.DEFAULT_INPUT_METHOD);
@@ -13315,4 +13318,30 @@
 
         return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
     }
+
+    @Override
+    public void installUpdateFromFile(ComponentName admin,
+            ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) {
+        enforceDeviceOwner(admin);
+        final long id = mInjector.binderClearCallingIdentity();
+        try {
+            UpdateInstaller updateInstaller;
+            if (isDeviceAB()) {
+                updateInstaller = new AbUpdateInstaller(
+                        mContext, updateFileDescriptor, callback, mInjector, mConstants);
+            } else {
+                updateInstaller = new NonAbUpdateInstaller(
+                        mContext, updateFileDescriptor, callback, mInjector, mConstants);
+            }
+            updateInstaller.startInstallUpdate();
+        } finally {
+            mInjector.binderRestoreCallingIdentity(id);
+        }
+    }
+
+
+    private boolean isDeviceAB() {
+        return "true".equalsIgnoreCase(android.os.SystemProperties
+                .get(AB_DEVICE_KEY, ""));
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NonAbUpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/NonAbUpdateInstaller.java
new file mode 100644
index 0000000..5f1e926
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NonAbUpdateInstaller.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.StartInstallingUpdateCallback;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.RecoverySystem;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Used for installing an update for <a href="https://source.android.com/devices/tech/ota/nonab">non
+ * AB</a> devices.
+ */
+class NonAbUpdateInstaller extends UpdateInstaller {
+    NonAbUpdateInstaller(Context context,
+            ParcelFileDescriptor updateFileDescriptor,
+            StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector,
+            DevicePolicyConstants constants) {
+        super(context, updateFileDescriptor, callback, injector, constants);
+    }
+
+    @Override
+    public void installUpdateInThread() {
+        try {
+            RecoverySystem.installPackage(mContext, mCopiedUpdateFile);
+            notifyCallbackOnSuccess();
+        } catch (IOException e) {
+            Log.w(TAG, "IO error while trying to install non AB update.", e);
+            notifyCallbackOnError(
+                    DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UNKNOWN,
+                    Log.getStackTraceString(e));
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
new file mode 100644
index 0000000..7910598
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.StartInstallingUpdateCallback;
+import android.content.Context;
+import android.os.BatteryManager;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+abstract class UpdateInstaller {
+    private StartInstallingUpdateCallback mCallback;
+    private ParcelFileDescriptor mUpdateFileDescriptor;
+    private DevicePolicyConstants mConstants;
+    protected Context mContext;
+    protected File mCopiedUpdateFile;
+
+    static final String TAG = "UpdateInstaller";
+    private DevicePolicyManagerService.Injector mInjector;
+
+    protected UpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor,
+            StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector,
+            DevicePolicyConstants constants) {
+        mContext = context;
+        mCallback = callback;
+        mUpdateFileDescriptor = updateFileDescriptor;
+        mInjector = injector;
+        mConstants = constants;
+    }
+
+    public abstract void installUpdateInThread();
+
+    public void startInstallUpdate() {
+        if (!checkIfBatteryIsSufficient()) {
+            notifyCallbackOnError(
+                    DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_BATTERY_LOW,
+                    "The battery level must be above "
+                            + mConstants.BATTERY_THRESHOLD_NOT_CHARGING + " while not charging or"
+                            + "above " + mConstants.BATTERY_THRESHOLD_CHARGING + " while charging");
+            return;
+        }
+        Thread thread = new Thread(() -> {
+            mCopiedUpdateFile = copyUpdateFileToDataOtaPackageDir();
+            if (mCopiedUpdateFile == null) {
+                notifyCallbackOnError(
+                        DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UNKNOWN,
+                        "Error while copying file.");
+                return;
+            }
+            installUpdateInThread();
+        });
+        thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+    }
+
+    private boolean checkIfBatteryIsSufficient() {
+        BatteryManager batteryManager =
+                (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE);
+        if (batteryManager != null) {
+            int chargePercentage = batteryManager
+                    .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
+            return batteryManager.isCharging()
+                    ? chargePercentage >= mConstants.BATTERY_THRESHOLD_CHARGING
+                    : chargePercentage >= mConstants.BATTERY_THRESHOLD_NOT_CHARGING;
+        }
+        return false;
+    }
+
+    private File copyUpdateFileToDataOtaPackageDir() {
+        try {
+            File destination = createNewFileWithPermissions();
+            copyToFile(destination);
+            return destination;
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to copy update file to OTA directory", e);
+            notifyCallbackOnError(
+                    DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UNKNOWN,
+                    Log.getStackTraceString(e));
+            return null;
+        }
+    }
+
+    private File createNewFileWithPermissions() throws IOException {
+        File destination = File.createTempFile(
+                "update", ".zip", new File(Environment.getDataDirectory() + "/ota_package"));
+        FileUtils.setPermissions(
+                /* path= */ destination,
+                /* mode= */ FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH,
+                /* uid= */ -1, /* gid= */ -1);
+        return destination;
+    }
+
+    private void copyToFile(File destination) throws IOException {
+        try (OutputStream out = new FileOutputStream(destination);
+             InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
+                     mUpdateFileDescriptor)) {
+            FileUtils.copy(in, out);
+        }
+    }
+
+    void cleanupUpdateFile() {
+        if (mCopiedUpdateFile.exists()) {
+            mCopiedUpdateFile.delete();
+        }
+    }
+
+    protected void notifyCallbackOnError(int errorCode, String errorMessage) {
+        cleanupUpdateFile();
+        try {
+            mCallback.onStartInstallingUpdateError(errorCode, errorMessage);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error while calling callback", e);
+        }
+    }
+
+    protected void notifyCallbackOnSuccess() {
+        cleanupUpdateFile();
+        mInjector.powerManagerReboot(PowerManager.REBOOT_REQUESTED_BY_DEVICE_OWNER);
+    }
+}