diff options
| -rw-r--r-- | Android.bp | 3 | ||||
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 12 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 8 | ||||
| -rw-r--r-- | core/java/android/os/DynamicAndroidManager.java | 188 | ||||
| -rw-r--r-- | core/java/android/os/IDynamicAndroidService.aidl | 87 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 4 | ||||
| -rw-r--r-- | services/core/Android.bp | 2 | ||||
| -rw-r--r-- | services/core/java/com/android/server/DynamicAndroidService.java | 134 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 6 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/DynamicAndroidServiceTest.java | 47 |
11 files changed, 492 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp index 0407c41a16a7..fcea5d02558b 100644 --- a/Android.bp +++ b/Android.bp @@ -221,6 +221,7 @@ java_defaults { "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 @@ java_defaults { ":storaged_aidl", ":vold_aidl", + ":gsiservice_aidl", ":installd_aidl", ":dumpstate_aidl", @@ -657,6 +659,7 @@ java_defaults { "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 9cfe60428fe1..9a9a261293ab 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -837,6 +837,7 @@ package android.content { 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 b2951df63ebd..a8fc9d242a1e 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -114,11 +114,13 @@ import android.os.BugreportManager; 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 @@ final class SystemServiceRegistry { 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 8959540ff7c3..ba6e7ae82276 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4304,6 +4304,14 @@ public abstract class Context { */ 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 000000000000..5238896016ee --- /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 000000000000..0b28799c8dd0 --- /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 2f3c1db4f775..970892870a4f 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 9b8f51e49b26..fe42805ad3f8 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -7,6 +7,7 @@ java_library_static { "frameworks/native/cmds/dumpstate/binder", "system/core/storaged/binder", "system/vold/binder", + "system/gsid/aidl", ], }, srcs: [ @@ -15,6 +16,7 @@ java_library_static { ":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 000000000000..12a3f02325d2 --- /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 3ecbd47cf12e..75cd82e4e012 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -758,6 +758,7 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 000000000000..149428443fa1 --- /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()); + } + } +} |