Add the DynamicAndroid Service

  Define the DynamicAndroid with AIDL.
  Add a java implementation.
  Start a service instance in the system server.
  Add a permission test.

Bug: 122015653
Test: Build & Test on a pixel phone with following command \
  ./frameworks/base/services/tests/runtests.py -e class com.android.server.DynamicAndroidTest

Merged-In: I2e54b6b71fac4a4c5a9c9c25ce6bdac74cddcfb7
Change-Id: I2e54b6b71fac4a4c5a9c9c25ce6bdac74cddcfb7
diff --git a/Android.bp b/Android.bp
index 0407c41..fcea5d0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -221,6 +221,7 @@
         "core/java/android/os/ICancellationSignal.aidl",
         "core/java/android/os/IDeviceIdentifiersPolicyService.aidl",
         "core/java/android/os/IDeviceIdleController.aidl",
+        "core/java/android/os/IDynamicAndroidService.aidl",
         "core/java/android/os/IHardwarePropertiesManager.aidl",
         ":libincident_aidl",
         "core/java/android/os/IMaintenanceActivityListener.aidl",
@@ -601,6 +602,7 @@
 
         ":storaged_aidl",
         ":vold_aidl",
+        ":gsiservice_aidl",
         ":installd_aidl",
         ":dumpstate_aidl",
 
@@ -657,6 +659,7 @@
             "frameworks/native/aidl/gui",
             "system/core/storaged/binder",
             "system/vold/binder",
+            "system/gsid/aidl",
             "system/bt/binder",
             "system/security/keystore/binder",
         ],
diff --git a/api/system-current.txt b/api/system-current.txt
index 9cfe604..9a9a261 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -837,6 +837,7 @@
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void startActivityAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle);
     field public static final String BACKUP_SERVICE = "backup";
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
+    field public static final String DYNAMIC_ANDROID_SERVICE = "dynamic_android";
     field public static final String EUICC_CARD_SERVICE = "euicc_card";
     field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
     field public static final String NETD_SERVICE = "netd";
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index b2951df..a8fc9d24 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -114,11 +114,13 @@
 import android.os.Build;
 import android.os.DeviceIdleManager;
 import android.os.DropBoxManager;
+import android.os.DynamicAndroidManager;
 import android.os.HardwarePropertiesManager;
 import android.os.IBatteryPropertiesRegistrar;
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
 import android.os.IDumpstate;
+import android.os.IDynamicAndroidService;
 import android.os.IHardwarePropertiesManager;
 import android.os.IPowerManager;
 import android.os.IRecoverySystem;
@@ -1065,6 +1067,16 @@
                             throws ServiceNotFoundException {
                         return new TimeZoneDetector();
                     }});
+        registerService(Context.DYNAMIC_ANDROID_SERVICE, DynamicAndroidManager.class,
+                new CachedServiceFetcher<DynamicAndroidManager>() {
+                    @Override
+                    public DynamicAndroidManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(
+                                Context.DYNAMIC_ANDROID_SERVICE);
+                        return new DynamicAndroidManager(
+                                IDynamicAndroidService.Stub.asInterface(b));
+                    }});
     }
 
     /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8959540..ba6e7ae8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4304,6 +4304,14 @@
      */
     public static final String TELEPHONY_RCS_SERVICE = "ircs";
 
+     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.os.DynamicAndroidManager}.
+     * @hide
+     */
+    @SystemApi
+    public static final String DYNAMIC_ANDROID_SERVICE = "dynamic_android";
+
     /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
diff --git a/core/java/android/os/DynamicAndroidManager.java b/core/java/android/os/DynamicAndroidManager.java
new file mode 100644
index 0000000..5238896
--- /dev/null
+++ b/core/java/android/os/DynamicAndroidManager.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2019 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.os;
+
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.gsi.GsiProgress;
+
+/**
+ * The DynamicAndroidManager offers a mechanism to use a new Android image temporarily. After the
+ * installation, the device can reboot into this image with a new created /data. This image will
+ * last until the next reboot and then the device will go back to the original image. However the
+ * installed image and the new created /data are not deleted but disabled. Thus the application can
+ * either re-enable the installed image by calling {@link #toggle} or use the {@link #remove} to
+ * delete it completely. In other words, there are three device states: no installation, installed
+ * and running. The procedure to install a DynamicAndroid starts with a {@link #startInstallation},
+ * followed by a series of {@link #write} and ends with a {@link commit}. Once the installation is
+ * complete, the device state changes from no installation to the installed state and a followed
+ * reboot will change its state to running. Note one instance of dynamic android can exist on a
+ * given device thus the {@link #startInstallation} will fail if the device is currently running a
+ * DynamicAndroid.
+ *
+ * @hide
+ */
+@SystemService(Context.DYNAMIC_ANDROID_SERVICE)
+public class DynamicAndroidManager {
+    private static final String TAG = "DynamicAndroidManager";
+
+    private final IDynamicAndroidService mService;
+
+    /** {@hide} */
+    public DynamicAndroidManager(IDynamicAndroidService service) {
+        mService = service;
+    }
+
+    /** The DynamicAndroidManager.Session represents a started session for the installation. */
+    public class Session {
+        private Session() {}
+        /**
+         * Write a chunk of the DynamicAndroid system image
+         *
+         * @return {@code true} if the call succeeds. {@code false} if there is any native runtime
+         *     error.
+         */
+        @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+        public boolean write(byte[] buf) {
+            try {
+                return mService.write(buf);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e.toString());
+            }
+        }
+
+        /**
+         * Finish write and make device to boot into the it after reboot.
+         *
+         * @return {@code true} if the call succeeds. {@code false} if there is any native runtime
+         *     error.
+         */
+        @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+        public boolean commit() {
+            try {
+                return mService.commit();
+            } catch (RemoteException e) {
+                throw new RuntimeException(e.toString());
+            }
+        }
+    }
+    /**
+     * Start DynamicAndroid installation. This call may take an unbounded amount of time. The caller
+     * may use another thread to call the getStartProgress() to get the progress.
+     *
+     * @param systemSize system size in bytes
+     * @param userdataSize userdata size in bytes
+     * @return {@code true} if the call succeeds. {@code false} either the device does not contain
+     *     enough space or a DynamicAndroid is currently in use where the {@link #isInUse} would be
+     *     true.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+    public Session startInstallation(long systemSize, long userdataSize) {
+        try {
+            if (mService.startInstallation(systemSize, userdataSize)) {
+                return new Session();
+            } else {
+                return null;
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /**
+     * Query the progress of the current installation operation. This can be called while the
+     * installation is in progress.
+     *
+     * @return GsiProgress GsiProgress { int status; long bytes_processed; long total_bytes; } The
+     *     status field can be IGsiService.STATUS_NO_OPERATION, IGsiService.STATUS_WORKING or
+     *     IGsiService.STATUS_COMPLETE.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+    public GsiProgress getInstallationProgress() {
+        try {
+            return mService.getInstallationProgress();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /**
+     * Abort the installation process. Note this method must be called in a thread other than the
+     * one calling the startInstallation method as the startInstallation method will not return
+     * until it is finished.
+     *
+     * @return {@code true} if the call succeeds. {@code false} if there is no installation
+     *     currently.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+    public boolean abort() {
+        try {
+            return mService.abort();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /** @return {@code true} if the device is running a dynamic android */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+    public boolean isInUse() {
+        try {
+            return mService.isInUse();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /** @return {@code true} if the device has a dynamic android installed */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+    public boolean isInstalled() {
+        try {
+            return mService.isInstalled();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /**
+     * Remove DynamicAndroid installation if present
+     *
+     * @return {@code true} if the call succeeds. {@code false} if there is no installed image.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+    public boolean remove() {
+        try {
+            return mService.remove();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /**
+     * Enable DynamicAndroid when it's not enabled, otherwise, disable it.
+     *
+     * @return {@code true} if the call succeeds. {@code false} if there is no installed image.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+    public boolean toggle() {
+        try {
+            return mService.toggle();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+}
diff --git a/core/java/android/os/IDynamicAndroidService.aidl b/core/java/android/os/IDynamicAndroidService.aidl
new file mode 100644
index 0000000..0b28799
--- /dev/null
+++ b/core/java/android/os/IDynamicAndroidService.aidl
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 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.os;
+
+import android.gsi.GsiProgress;
+
+/** {@hide} */
+interface IDynamicAndroidService
+{
+    /**
+     * Start DynamicAndroid installation. This call may take 60~90 seconds. The caller
+     * may use another thread to call the getStartProgress() to get the progress.
+     *
+     * @param systemSize system size in bytes
+     * @param userdataSize userdata size in bytes
+     * @return true if the call succeeds
+     */
+    boolean startInstallation(long systemSize, long userdataSize);
+
+    /**
+     * Query the progress of the current installation operation. This can be called while
+     * the installation is in progress.
+     *
+     * @return GsiProgress
+     */
+    GsiProgress getInstallationProgress();
+
+    /**
+     * Abort the installation process. Note this method must be called in a thread other
+     * than the one calling the startInstallation method as the startInstallation
+     * method will not return until it is finished.
+     *
+     * @return true if the call succeeds
+     */
+    boolean abort();
+
+    /**
+     * @return true if the device is running an DynamicAnroid image
+     */
+    boolean isInUse();
+
+    /**
+     * @return true if the device has an DynamicAndroid image installed
+     */
+    boolean isInstalled();
+
+    /**
+     * Remove DynamicAndroid installation if present
+     *
+     * @return true if the call succeeds
+     */
+    boolean remove();
+
+    /**
+     * Enable DynamicAndroid when it's not enabled, otherwise, disable it.
+     *
+     * @return true if the call succeeds
+     */
+    boolean toggle();
+
+    /**
+     * Write a chunk of the DynamicAndroid system image
+     *
+     * @return true if the call succeeds
+     */
+    boolean write(in byte[] buf);
+
+    /**
+     * Finish write and make device to boot into the it after reboot.
+     *
+     * @return true if the call succeeds
+     */
+    boolean commit();
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f3c1db..9708928 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1681,6 +1681,10 @@
     <permission android:name="android.permission.HARDWARE_TEST"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows an application to manage DynamicAndroid image -->
+    <permission android:name="android.permission.MANAGE_DYNAMIC_ANDROID"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows access to Broadcast Radio
          @hide This is not a third-party API (intended for system apps).-->
     <permission android:name="android.permission.ACCESS_BROADCAST_RADIO"
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9b8f51e..fe42805 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -7,6 +7,7 @@
             "frameworks/native/cmds/dumpstate/binder",
             "system/core/storaged/binder",
             "system/vold/binder",
+            "system/gsid/aidl",
         ],
     },
     srcs: [
@@ -15,6 +16,7 @@
         ":installd_aidl",
         ":storaged_aidl",
         ":vold_aidl",
+        ":gsiservice_aidl",
         ":mediaupdateservice_aidl",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
diff --git a/services/core/java/com/android/server/DynamicAndroidService.java b/services/core/java/com/android/server/DynamicAndroidService.java
new file mode 100644
index 0000000..12a3f02
--- /dev/null
+++ b/services/core/java/com/android/server/DynamicAndroidService.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.gsi.GsiProgress;
+import android.gsi.IGsiService;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.IDynamicAndroidService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/**
+ * DynamicAndroidService implements IDynamicAndroidService. It provides permission check before
+ * passing requests to gsid
+ */
+public class DynamicAndroidService extends IDynamicAndroidService.Stub implements DeathRecipient {
+    private static final String TAG = "DynamicAndroidService";
+    private static final String NO_SERVICE_ERROR = "no gsiservice";
+
+    private Context mContext;
+    private volatile IGsiService mGsiService;
+
+    DynamicAndroidService(Context context) {
+        mContext = context;
+    }
+
+    private static IGsiService connect(DeathRecipient recipient) throws RemoteException {
+        IBinder binder = ServiceManager.getService("gsiservice");
+        if (binder == null) {
+            throw new RemoteException(NO_SERVICE_ERROR);
+        }
+        /**
+         * The init will restart gsiservice if it crashed and the proxy object will need to be
+         * re-initialized in this case.
+         */
+        binder.linkToDeath(recipient, 0);
+        return IGsiService.Stub.asInterface(binder);
+    }
+
+    /** implements DeathRecipient */
+    @Override
+    public void binderDied() {
+        Slog.w(TAG, "gsiservice died; reconnecting");
+        synchronized (this) {
+            mGsiService = null;
+        }
+    }
+
+    private IGsiService getGsiService() throws RemoteException {
+        checkPermission();
+        synchronized (this) {
+            if (mGsiService == null) {
+                mGsiService = connect(this);
+            }
+            return mGsiService;
+        }
+    }
+
+    private void checkPermission() {
+        if (mContext.checkCallingOrSelfPermission(
+                        android.Manifest.permission.MANAGE_DYNAMIC_ANDROID)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires MANAGE_DYNAMIC_ANDROID permission");
+        }
+    }
+
+    @Override
+    public boolean startInstallation(long systemSize, long userdataSize) throws RemoteException {
+        return getGsiService().startGsiInstall(systemSize, userdataSize, true) == 0;
+    }
+
+    @Override
+    public GsiProgress getInstallationProgress() throws RemoteException {
+        return getGsiService().getInstallProgress();
+    }
+
+    @Override
+    public boolean abort() throws RemoteException {
+        return getGsiService().cancelGsiInstall();
+    }
+
+    @Override
+    public boolean isInUse() throws RemoteException {
+        return getGsiService().isGsiRunning();
+    }
+
+    @Override
+    public boolean isInstalled() throws RemoteException {
+        return getGsiService().isGsiInstalled();
+    }
+
+    @Override
+    public boolean remove() throws RemoteException {
+        return getGsiService().removeGsiInstall();
+    }
+
+    @Override
+    public boolean toggle() throws RemoteException {
+        IGsiService gsiService = getGsiService();
+        if (gsiService.isGsiRunning()) {
+            return gsiService.disableGsiInstall();
+        } else {
+            return gsiService.setGsiBootable() == 0;
+        }
+    }
+
+    @Override
+    public boolean write(byte[] buf) throws RemoteException {
+        return getGsiService().commitGsiChunkFromMemory(buf);
+    }
+
+    @Override
+    public boolean commit() throws RemoteException {
+        return getGsiService().setGsiBootable() == 0;
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3ecbd47..75cd82e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -758,6 +758,7 @@
     private void startOtherServices() {
         final Context context = mSystemContext;
         VibratorService vibrator = null;
+        DynamicAndroidService dynamicAndroid = null;
         IStorageManager storageManager = null;
         NetworkManagementService networkManagement = null;
         IpSecService ipSecService = null;
@@ -867,6 +868,11 @@
             ServiceManager.addService("vibrator", vibrator);
             traceEnd();
 
+            traceBeginAndSlog("StartDynamicAndroidService");
+            dynamicAndroid = new DynamicAndroidService(context);
+            ServiceManager.addService("dynamic_android", dynamicAndroid);
+            traceEnd();
+
             if (!isWatch) {
                 traceBeginAndSlog("StartConsumerIrService");
                 consumerIr = new ConsumerIrService(context);
diff --git a/services/tests/servicestests/src/com/android/server/DynamicAndroidServiceTest.java b/services/tests/servicestests/src/com/android/server/DynamicAndroidServiceTest.java
new file mode 100644
index 0000000..1494284
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/DynamicAndroidServiceTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.os.IDynamicAndroidService;
+import android.os.ServiceManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+public class DynamicAndroidServiceTest extends AndroidTestCase {
+    private static final String TAG = "DynamicAndroidServiceTests";
+    private IDynamicAndroidService mService;
+
+    @Override
+    protected void setUp() throws Exception {
+        mService =
+                IDynamicAndroidService.Stub.asInterface(
+                        ServiceManager.getService("dynamic_android"));
+    }
+
+    @LargeTest
+    public void test1() {
+        assertTrue("dynamic_android service available", mService != null);
+        try {
+            mService.startInstallation(1 << 20, 8 << 30);
+            fail("DynamicAndroidService did not throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        } catch (Exception e) {
+            fail(e.toString());
+        }
+    }
+}