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.

Bug: 187444679
Test: atest StagingManagerTest
Test: atest StagedInstallInternalTest
Change-Id: Ie2e01b01690a5882574282f3158e454a9b6056e7
Merged-In: Ie2e01b01690a5882574282f3158e454a9b6056e7
(cherry picked from commit 5ac0ee8278)
This commit is contained in:
Samiul Islam
2021-08-04 20:05:40 +01:00
parent fe58e6f7d1
commit 818cbce3ad
7 changed files with 156 additions and 7 deletions

View File

@@ -260,6 +260,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
new Lifecycle(context, this));
}
StagingManager getStagingManager() {
return mStagingManager;
}
boolean okToSendBroadcasts() {
return mOkToSendBroadcasts;
}

View File

@@ -186,6 +186,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;
@@ -227,6 +228,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.TestUtilityService;
import android.content.pm.UserInfo;
@@ -27003,6 +27005,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) {

View File

@@ -41,6 +41,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.RollbackInfo;
import android.content.rollback.RollbackManager;
@@ -209,6 +210,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);
}
@@ -1222,7 +1240,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);
@@ -1230,9 +1248,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;
}
}
}

View File

@@ -40,6 +40,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;
@@ -693,12 +694,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());
}

View File

@@ -37,6 +37,7 @@ android_test_helper_app {
":test.rebootless_apex_v1",
":test.rebootless_apex_v2",
],
platform_apis: true,
}
java_test_host {

View File

@@ -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<ApexStagedEvent> 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();

View File

@@ -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<String> getStagingDirectories() throws DeviceNotAvailableException {
String baseDir = "/data/app-staging";
try {