Merge changes from topic "new_aidl_required"

* changes:
  Hook the new APIs in StagingManager to PackageManagerNative service
  Notify StagedApexObservers when there is a change in set of staged APEX
  Open up new API in StagingManager to get information about staged APEX
  Connect the new API from ApexService to ApexManager
This commit is contained in:
Mohammad Islam
2021-08-27 18:16:39 +00:00
committed by Gerrit Code Review
8 changed files with 330 additions and 9 deletions

View File

@@ -237,6 +237,14 @@ public abstract class ApexManager {
abstract ApexInfoList submitStagedSession(ApexSessionParams params)
throws PackageManagerException;
/**
* Returns {@code ApeInfo} about apex sessions that have been marked ready via
* {@link #markStagedSessionReady(int)}
*
* Returns empty array if there is no staged apex session or if there is any error.
*/
abstract ApexInfo[] getStagedApexInfos(ApexSessionParams params);
/**
* Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
* applied at next reboot.
@@ -706,6 +714,19 @@ public abstract class ApexManager {
}
}
@Override
ApexInfo[] getStagedApexInfos(ApexSessionParams params) {
try {
return waitForApexService().getStagedApexInfos(params);
} catch (RemoteException re) {
Slog.w(TAG, "Unable to contact apexservice" + re.getMessage());
throw new RuntimeException(re);
} catch (Exception e) {
Slog.w(TAG, "Failed to collect staged apex infos" + e.getMessage());
return new ApexInfo[0];
}
}
@Override
void markStagedSessionReady(int sessionId) throws PackageManagerException {
try {
@@ -1099,6 +1120,11 @@ public abstract class ApexManager {
"Device doesn't support updating APEX");
}
@Override
ApexInfo[] getStagedApexInfos(ApexSessionParams params) {
throw new UnsupportedOperationException();
}
@Override
void markStagedSessionReady(int sessionId) {
throw new UnsupportedOperationException();

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -29,7 +29,9 @@ import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApexStagedEvent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IStagedApexObserver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
@@ -38,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;
@@ -58,6 +61,7 @@ import android.os.UserManagerInternal;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -66,8 +70,10 @@ import android.util.SparseIntArray;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageHelper;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
@@ -84,7 +90,9 @@ import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -103,7 +111,8 @@ public class StagingManager {
private final ApexManager mApexManager;
private final PowerManager mPowerManager;
private final Context mContext;
private final PreRebootVerificationHandler mPreRebootVerificationHandler;
@VisibleForTesting
final PreRebootVerificationHandler mPreRebootVerificationHandler;
private final Supplier<PackageParser2> mPackageParserSupplier;
private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt");
@@ -122,6 +131,9 @@ public class StagingManager {
@GuardedBy("mSuccessfulStagedSessionIds")
private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>();
@GuardedBy("mStagedApexObservers")
private final List<IStagedApexObserver> mStagedApexObservers = new ArrayList<>();
StagingManager(PackageInstallerService pi, Context context,
Supplier<PackageParser2> packageParserSupplier) {
mPi = pi;
@@ -184,6 +196,35 @@ public class StagingManager {
mApexManager.markBootCompleted();
}
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);
}
}
void unregisterStagedApexObserver(IStagedApexObserver observer) {
synchronized (mStagedApexObservers) {
mStagedApexObservers.remove(observer);
}
}
/**
* Validates the signature used to sign the container of the new apex package
*
@@ -1075,6 +1116,9 @@ public class StagingManager {
Slog.w(TAG, "Could not contact apexd to abort staged session " + sessionId);
}
}
if (sessionContainsApex(session)) {
notifyStagedApexObservers();
}
}
// Session was successfully aborted from apexd (if required) and pre-reboot verification
@@ -1299,7 +1343,107 @@ public class StagingManager {
return session;
}
private final class PreRebootVerificationHandler extends Handler {
/**
* Returns ApexInfo about APEX contained inside the session as a {@code Map<String, ApexInfo>},
* where the key of the map is the module name of the ApexInfo.
*
* Returns an empty map if there is any error.
*/
@VisibleForTesting
@NonNull
Map<String, ApexInfo> getStagedApexInfos(@NonNull PackageInstallerSession session) {
Preconditions.checkArgument(session != null, "Session is null");
Preconditions.checkArgument(!session.hasParentSessionId(),
session.sessionId + " session has parent session");
Preconditions.checkArgument(sessionContainsApex(session),
session.sessionId + " session does not contain apex");
// Even if caller calls this method on ready session, the session could be abandoned
// right after this method is called.
if (!session.isStagedSessionReady() || session.isDestroyed()) {
return Collections.emptyMap();
}
ApexSessionParams params = new ApexSessionParams();
params.sessionId = session.sessionId;
final IntArray childSessionIds = new IntArray();
if (session.isMultiPackage()) {
for (int id : session.getChildSessionIds()) {
if (isApexSession(getStagedSession(id))) {
childSessionIds.add(id);
}
}
}
params.childSessionIds = childSessionIds.toArray();
ApexInfo[] infos = mApexManager.getStagedApexInfos(params);
Map<String, ApexInfo> result = new ArrayMap<>();
for (ApexInfo info : infos) {
result.put(info.moduleName, info);
}
return result;
}
/**
* Returns apex module names of all packages that are staged ready
*/
List<String> getStagedApexModuleNames() {
List<String> result = new ArrayList<>();
synchronized (mStagedSessions) {
for (int i = 0; i < mStagedSessions.size(); i++) {
final PackageInstallerSession session = mStagedSessions.valueAt(i);
if (!session.isStagedSessionReady() || session.isDestroyed()
|| session.hasParentSessionId() || !sessionContainsApex(session)) {
continue;
}
result.addAll(getStagedApexInfos(session).keySet());
}
}
return result;
}
/**
* Returns ApexInfo of the {@code moduleInfo} provided if it is staged, otherwise returns null.
*/
@Nullable
StagedApexInfo getStagedApexInfo(String moduleName) {
synchronized (mStagedSessions) {
for (int i = 0; i < mStagedSessions.size(); i++) {
final PackageInstallerSession session = mStagedSessions.valueAt(i);
if (!session.isStagedSessionReady() || session.isDestroyed()
|| session.hasParentSessionId() || !sessionContainsApex(session)) {
continue;
}
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;
}
}
}
return null;
}
private void notifyStagedApexObservers() {
synchronized (mStagedApexObservers) {
for (IStagedApexObserver observer : mStagedApexObservers) {
ApexStagedEvent event = new ApexStagedEvent();
event.stagedApexModuleNames = getStagedApexModuleNames().toArray(new String[0]);
try {
observer.onApexStaged(event);
} catch (RemoteException re) {
Slog.w(TAG, "Failed to contact the observer " + re.getMessage());
}
}
}
}
@VisibleForTesting
final class PreRebootVerificationHandler extends Handler {
// Hold session ids before handler gets ready to do the verification.
private IntArray mPendingSessionIds;
private boolean mIsReady;
@@ -1327,7 +1471,8 @@ public class StagingManager {
private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
@VisibleForTesting
static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
@Override
public void handleMessage(Message msg) {
@@ -1579,6 +1724,7 @@ public class StagingManager {
if (hasApex) {
try {
mApexManager.markStagedSessionReady(session.sessionId);
notifyStagedApexObservers();
} catch (PackageManagerException e) {
session.setStagedSessionFailed(e.error, e.getMessage());
return;

View File

@@ -37,6 +37,7 @@ import android.apex.IApexService;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -227,6 +228,21 @@ public class ApexManagerTest {
() -> mApexManager.submitStagedSession(testParamsWithChildren()));
}
@Test
public void testGetStagedApexInfos_throwRunTimeException() throws RemoteException {
doThrow(RemoteException.class).when(mApexService).getStagedApexInfos(any());
assertThrows(RuntimeException.class,
() -> mApexManager.getStagedApexInfos(testParamsWithChildren()));
}
@Test
public void testGetStagedApexInfos_returnsEmptyArrayOnError() throws RemoteException {
doThrow(ServiceSpecificException.class).when(mApexService).getStagedApexInfos(any());
assertThat(mApexManager.getStagedApexInfos(testParamsWithChildren())).hasLength(0);
}
@Test
public void testMarkStagedSessionReady_throwPackageManagerException() throws RemoteException {
doAnswer(invocation -> {

View File

@@ -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",

View File

@@ -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<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 assertSessionReady(int sessionId) {
assertSessionState(sessionId,
(session) -> assertThat(session.isStagedSessionReady()).isTrue());

View File

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