From 33dce2cffd06651a4ab6def32f18e47043f52599 Mon Sep 17 00:00:00 2001 From: Samiul Islam Date: Wed, 4 Aug 2021 20:05:40 +0100 Subject: [PATCH] Hook the new APIs in StagingManager to PackageManagerNative service Also note, if a StagedApexObserver is observing through binder, they might not be able to send the original observing object for unregistration. As such, for binder observer we clean them up when they die. (Note: Unit tests have been removed to resolve merge conflict) Bug: 187444679 Test: atest StagedInstallInternalTest Change-Id: Ie2e01b01690a5882574282f3158e454a9b6056e7 Merged-In: Ie2e01b01690a5882574282f3158e454a9b6056e7 (cherry picked from commit 5ac0ee8278ea33a6f0bf14b96821d31576e81df4) --- .../server/pm/PackageInstallerService.java | 4 + .../server/pm/PackageManagerService.java | 28 ++++++- .../com/android/server/pm/StagingManager.java | 31 ++++++- tests/StagedInstallTest/Android.bp | 14 +++- .../StagedInstallInternalTest.java | 84 +++++++++++++++++++ .../host/StagedInstallInternalTest.java | 15 ++++ 6 files changed, 166 insertions(+), 10 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index f8115d39b375f..0b07bb6f408e9 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -224,6 +224,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mStagingManager = new StagingManager(this, context, apexParserSupplier); } + StagingManager getStagingManager() { + return mStagingManager; + } + boolean okToSendBroadcasts() { return mOkToSendBroadcasts; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e4723adabe71f..1173df6297962 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -21,14 +21,12 @@ import static android.Manifest.permission.INSTALL_PACKAGES; import static android.Manifest.permission.MANAGE_DEVICE_ADMINS; import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; import static android.Manifest.permission.QUERY_ALL_PACKAGES; -import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.REQUEST_DELETE_PACKAGES; import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.Intent.ACTION_MAIN; -import static android.content.Intent.CATEGORY_BROWSABLE; import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.EXTRA_LONG_VERSION_CODE; @@ -36,8 +34,6 @@ import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.EXTRA_VERSION_CODE; import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; -import static android.content.Intent.CATEGORY_BROWSABLE; -import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; @@ -181,6 +177,7 @@ import android.content.pm.IPackageManager; import android.content.pm.IPackageManagerNative; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; +import android.content.pm.IStagedApexObserver; import android.content.pm.InstallSourceInfo; import android.content.pm.InstantAppInfo; import android.content.pm.InstantAppRequest; @@ -220,6 +217,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; import android.content.pm.SigningInfo; +import android.content.pm.StagedApexInfo; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; import android.content.pm.VerifierDeviceIdentity; @@ -24119,6 +24117,28 @@ public class PackageManagerService extends IPackageManager.Stub public String getModuleMetadataPackageName() throws RemoteException { return PackageManagerService.this.mModuleInfoProvider.getPackageName(); } + + @Override + public void registerStagedApexObserver(IStagedApexObserver observer) { + mInstallerService.getStagingManager().registerStagedApexObserver(observer); + } + + @Override + public void unregisterStagedApexObserver(IStagedApexObserver observer) { + mInstallerService.getStagingManager().unregisterStagedApexObserver(observer); + } + + @Override + public String[] getStagedApexModuleNames() { + return mInstallerService.getStagingManager() + .getStagedApexModuleNames().toArray(new String[0]); + } + + @Override + @Nullable + public StagedApexInfo getStagedApexInfo(String moduleName) { + return mInstallerService.getStagingManager().getStagedApexInfo(moduleName); + } } private class PackageManagerInternalImpl extends PackageManagerInternal { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index b531ed1b6c43b..02397daebb16e 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -40,6 +40,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; +import android.content.pm.StagedApexInfo; import android.content.pm.parsing.PackageInfoWithoutStateUtils; import android.content.rollback.IRollbackManager; import android.content.rollback.RollbackInfo; @@ -196,6 +197,23 @@ public class StagingManager { } void registerStagedApexObserver(IStagedApexObserver observer) { + if (observer == null) { + return; + } + if (observer.asBinder() != null) { + try { + observer.asBinder().linkToDeath(new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mStagedApexObservers) { + mStagedApexObservers.remove(observer); + } + } + }, 0); + } catch (RemoteException re) { + Slog.w(TAG, re.getMessage()); + } + } synchronized (mStagedApexObservers) { mStagedApexObservers.add(observer); } @@ -1388,7 +1406,7 @@ public class StagingManager { * Returns ApexInfo of the {@code moduleInfo} provided if it is staged, otherwise returns null. */ @Nullable - ApexInfo getStagedApexInfo(String moduleName) { + StagedApexInfo getStagedApexInfo(String moduleName) { synchronized (mStagedSessions) { for (int i = 0; i < mStagedSessions.size(); i++) { final PackageInstallerSession session = mStagedSessions.valueAt(i); @@ -1396,9 +1414,14 @@ public class StagingManager { || session.hasParentSessionId() || !sessionContainsApex(session)) { continue; } - ApexInfo result = getStagedApexInfos(session).get(moduleName); - if (result != null) { - return result; + ApexInfo ai = getStagedApexInfos(session).get(moduleName); + if (ai != null) { + StagedApexInfo info = new StagedApexInfo(); + info.moduleName = ai.moduleName; + info.diskImagePath = ai.modulePath; + info.versionCode = ai.versionCode; + info.versionName = ai.versionName; + return info; } } } diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index 243c3015b5594..840a588bfe884 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -25,14 +25,24 @@ android_test_helper_app { name: "StagedInstallInternalTestApp", manifest: "app/AndroidManifest.xml", srcs: ["app/src/**/*.java"], - static_libs: ["androidx.test.rules", "cts-install-lib"], + static_libs: [ + "androidx.test.rules", + "cts-install-lib", + ], test_suites: ["general-tests"], + java_resources: [ + ":StagedInstallTestApexV2", + ], + platform_apis: true, } java_test_host { name: "StagedInstallInternalTest", srcs: ["src/**/*.java"], - libs: ["tradefed", "cts-shim-host-lib"], + libs: [ + "tradefed", + "cts-shim-host-lib", + ], static_libs: [ "testng", "compatibility-tradefed", diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index e67354982b05d..a0f4e0ad7e6a9 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -17,12 +17,24 @@ package com.android.tests.stagedinstallinternal; import static com.android.cts.install.lib.InstallUtils.getPackageInstaller; +import static com.android.cts.install.lib.InstallUtils.waitForSessionReady; +import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + import android.Manifest; +import android.content.pm.ApexStagedEvent; +import android.content.pm.IPackageManagerNative; +import android.content.pm.IStagedApexObserver; import android.content.pm.PackageInstaller; +import android.content.pm.StagedApexInfo; +import android.os.IBinder; +import android.os.ServiceManager; import androidx.test.platform.app.InstrumentationRegistry; @@ -35,6 +47,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -50,6 +64,10 @@ public class StagedInstallInternalTest { private static final String TAG = StagedInstallInternalTest.class.getSimpleName(); + private static final TestApp APEX_V2 = new TestApp( + "ApexV2", SHIM_APEX_PACKAGE_NAME, 2, /* isApex= */ true, + "com.android.apex.cts.shim.v2.apex"); + private File mTestStateFile = new File( InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), "stagedinstall_state"); @@ -109,6 +127,72 @@ public class StagedInstallInternalTest { Install.multi(TestApp.AIncompleteSplit, TestApp.B1, TestApp.Apex1).setStaged()); } + @Test + public void testGetStagedModuleNames() throws Exception { + // Before staging a session + String[] result = getPackageManagerNative().getStagedApexModuleNames(); + assertThat(result).hasLength(0); + // Stage an apex + int sessionId = Install.single(APEX_V2).setStaged().commit(); + waitForSessionReady(sessionId); + result = getPackageManagerNative().getStagedApexModuleNames(); + assertThat(result).hasLength(1); + assertThat(result).isEqualTo(new String[]{SHIM_APEX_PACKAGE_NAME}); + // Abandon the session + InstallUtils.openPackageInstallerSession(sessionId).abandon(); + result = getPackageManagerNative().getStagedApexModuleNames(); + assertThat(result).hasLength(0); + } + + @Test + public void testGetStagedApexInfo() throws Exception { + // Ask for non-existing module + StagedApexInfo result = getPackageManagerNative().getStagedApexInfo("not found"); + assertThat(result).isNull(); + // Stage an apex + int sessionId = Install.single(APEX_V2).setStaged().commit(); + waitForSessionReady(sessionId); + // Query proper module name + result = getPackageManagerNative().getStagedApexInfo(SHIM_APEX_PACKAGE_NAME); + assertThat(result.moduleName).isEqualTo(SHIM_APEX_PACKAGE_NAME); + InstallUtils.openPackageInstallerSession(sessionId).abandon(); + } + + public static class MockStagedApexObserver extends IStagedApexObserver.Stub { + @Override + public void onApexStaged(ApexStagedEvent event) { + assertThat(event).isNotNull(); + } + } + + @Test + public void testStagedApexObserver() throws Exception { + MockStagedApexObserver realObserver = new MockStagedApexObserver(); + IStagedApexObserver observer = spy(realObserver); + assertThat(observer).isNotNull(); + getPackageManagerNative().registerStagedApexObserver(observer); + + // Stage an apex and verify observer was called + int sessionId = Install.single(APEX_V2).setStaged().commit(); + waitForSessionReady(sessionId); + ArgumentCaptor captor = ArgumentCaptor.forClass(ApexStagedEvent.class); + verify(observer, timeout(5000)).onApexStaged(captor.capture()); + assertThat(captor.getValue().stagedApexModuleNames).isEqualTo( + new String[] {SHIM_APEX_PACKAGE_NAME}); + + // Abandon and verify observer is called + Mockito.clearInvocations(observer); + InstallUtils.openPackageInstallerSession(sessionId).abandon(); + verify(observer, timeout(5000)).onApexStaged(captor.capture()); + assertThat(captor.getValue().stagedApexModuleNames).hasLength(0); + } + + private IPackageManagerNative getPackageManagerNative() { + IBinder binder = ServiceManager.waitForService("package_native"); + assertThat(binder).isNotNull(); + return IPackageManagerNative.Stub.asInterface(binder); + } + private static void assertSessionReady(int sessionId) { assertSessionState(sessionId, (session) -> assertThat(session.isStagedSessionReady()).isTrue()); diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java index dddb317f31e53..f92c31c0ba6a2 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -216,6 +216,21 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { assertThat(getStagingDirectories()).isEmpty(); } + @Test + public void testGetStagedModuleNames() throws Exception { + runPhase("testGetStagedModuleNames"); + } + + @Test + public void testGetStagedApexInfo() throws Exception { + runPhase("testGetStagedApexInfo"); + } + + @Test + public void testStagedApexObserver() throws Exception { + runPhase("testStagedApexObserver"); + } + private List getStagingDirectories() throws DeviceNotAvailableException { String baseDir = "/data/app-staging"; try {