diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index fe419a613b018..b34a3a2b8dcbf 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -261,6 +261,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements new Lifecycle(context, this)); } + 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 b44e2ed3dd886..ef7cad8428d7b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -163,6 +163,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.IncrementalStatesInfo; import android.content.pm.InstallSourceInfo; import android.content.pm.InstantAppInfo; @@ -202,6 +203,7 @@ import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningDetails.SignatureSchemeVersion; import android.content.pm.SigningInfo; +import android.content.pm.StagedApexInfo; import android.content.pm.SuspendDialogInfo; import android.content.pm.TestUtilityService; import android.content.pm.UserInfo; @@ -22996,6 +22998,29 @@ public class PackageManagerService extends IPackageManager.Stub public boolean hasSystemFeature(String featureName, int version) { return PackageManagerService.this.hasSystemFeature(featureName, version); } + + @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 AndroidPackage getPackage(String packageName) { @@ -24964,5 +24989,3 @@ public class PackageManagerService extends IPackageManager.Stub } } } - - diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index f22e0698e73f3..4dfb6b8ea4530 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -39,6 +39,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; import android.content.pm.SigningDetails.SignatureSchemeVersion; +import android.content.pm.StagedApexInfo; import android.content.pm.parsing.PackageInfoWithoutStateUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; @@ -215,6 +216,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); } @@ -1181,7 +1199,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 StagedSession session = mStagedSessions.valueAt(i); @@ -1189,9 +1207,14 @@ public class StagingManager { || session.hasParentSessionId() || !session.containsApexSession()) { 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/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index 5646da175f559..b60e0c38cf7a5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -42,6 +42,7 @@ import android.content.pm.IStagedApexObserver; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode; +import android.content.pm.StagedApexInfo; import android.os.Message; import android.os.SystemProperties; import android.os.storage.IStorageManager; @@ -701,12 +702,15 @@ public class StagingManagerTest { when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos); // Verify null is returned if module name is not found - ApexInfo result = mStagingManager.getStagedApexInfo("not found"); + StagedApexInfo result = mStagingManager.getStagedApexInfo("not found"); assertThat(result).isNull(); verify(mApexManager, times(1)).getStagedApexInfos(any()); // Otherwise, the correct object is returned result = mStagingManager.getStagedApexInfo("module1"); - assertThat(result).isEqualTo(fakeApexInfos[0]); + assertThat(result.moduleName).isEqualTo(fakeApexInfos[0].moduleName); + assertThat(result.diskImagePath).isEqualTo(fakeApexInfos[0].modulePath); + assertThat(result.versionCode).isEqualTo(fakeApexInfos[0].versionCode); + assertThat(result.versionName).isEqualTo(fakeApexInfos[0].versionName); verify(mApexManager, times(2)).getStagedApexInfos(any()); } diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index cac14a72a7061..558798dd8ec58 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -37,6 +37,7 @@ android_test_helper_app { ":test.rebootless_apex_v1", ":test.rebootless_apex_v2", ], + platform_apis: true, } java_test_host { 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 4684f0182d034..c610641932df8 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -17,16 +17,27 @@ 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.ApplicationInfo; +import android.content.pm.IPackageManagerNative; +import android.content.pm.IStagedApexObserver; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; +import android.content.pm.StagedApexInfo; +import android.os.IBinder; +import android.os.ServiceManager; import androidx.test.platform.app.InstrumentationRegistry; @@ -39,6 +50,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; @@ -401,9 +414,73 @@ public class StagedInstallInternalTest { AssertionError.class, "Staged session " + sessionId + " already contains " + SHIM_APEX_PACKAGE_NAME, Install.single(APEX_V2)); - } + @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 assertSessionApplied(int sessionId) { assertSessionState(sessionId, (session) -> { assertThat(session.isStagedSessionApplied()).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 5021009f65ae8..31021031b47aa 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -478,6 +478,21 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { runPhase("testRebootlessUpdate_hasStagedSessionWithSameApex_fails"); } + @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 {