Factor out calls to apexservice in a class.

The ApexManager class provides a cleaner interface to the apex service,
as well as providing caching for active packages, which can't change on
a running system. The cache is populated at boot time.

This CL will also cause PackageManager to stop reporting APEX packages
on devices that ship with flattened APEXs.

Test: atest apex_e2e_tests; used small app to verify API calls still
work; checked output of dumpsys.
Test: checked that on marlin (target with flatten APEX) no APEXs are
reported and no crashes are experienced at boot.
Fix: 123052859
Fix: 122638509
Fix: 124299505
Bug: 122952270
Change-Id: Iefe4fb42e455a7479ff47eb776d3492de8395469
This commit is contained in:
Dario Freni
2019-02-06 14:55:16 +00:00
parent 1466e4a4e7
commit 2e8dffcb72
4 changed files with 253 additions and 153 deletions

View File

@@ -0,0 +1,221 @@
/*
* 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.s
*/
package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.IApexService;
import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
import com.android.internal.util.IndentingPrintWriter;
import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* ApexManager class handles communications with the apex service to perform operation and queries,
* as well as providing caching to avoid unnecessary calls to the service.
*/
class ApexManager {
static final String TAG = "ApexManager";
private final IApexService mApexService;
private final Map<String, PackageInfo> mActivePackagesCache;
ApexManager() {
mApexService = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
mActivePackagesCache = populateActivePackagesCache();
}
@NonNull
private Map<String, PackageInfo> populateActivePackagesCache() {
try {
List<PackageInfo> list = new ArrayList<>();
final ApexInfo[] activePkgs = mApexService.getActivePackages();
for (ApexInfo ai : activePkgs) {
// If the device is using flattened APEX, don't report any APEX
// packages since they won't be managed or updated by PackageManager.
if ((new File(ai.packagePath)).isDirectory()) {
break;
}
try {
list.add(PackageParser.generatePackageInfoFromApex(
new File(ai.packagePath), true /* collect certs */));
} catch (PackageParserException pe) {
throw new IllegalStateException("Unable to parse: " + ai, pe);
}
}
return list.stream().collect(Collectors.toMap(p -> p.packageName, Function.identity()));
} catch (RemoteException re) {
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
throw new RuntimeException(re);
}
}
/**
* Retrieves information about an active APEX package.
*
* @param packageName the package name to look for. Note that this is the package name reported
* in the APK container manifest (i.e. AndroidManifest.xml), which might
* differ from the one reported in the APEX manifest (i.e.
* apex_manifest.json).
* @return a PackageInfo object with the information about the package, or null if the package
* is not found.
*/
@Nullable PackageInfo getActivePackage(String packageName) {
return mActivePackagesCache.get(packageName);
}
/**
* Retrieves information about all active APEX packages.
*
* @return a Collection of PackageInfo object, each one containing information about a different
* active package.
*/
Collection<PackageInfo> getActivePackages() {
return mActivePackagesCache.values();
}
/**
* Retrieves information about an apexd staged session i.e. the internal state used by apexd to
* track the different states of a session.
*
* @param sessionId the identifier of the session.
* @return an ApexSessionInfo object, or null if the session is not known.
*/
@Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
try {
ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
if (apexSessionInfo.isUnknown) {
return null;
}
return apexSessionInfo;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
}
}
/**
* Submit a staged session to apex service. This causes the apex service to perform some initial
* verification and accept or reject the session. Submitting a session successfully is not
* enough for it to be activated at the next boot, the caller needs to call
* {@link #markStagedSessionReady(int)}.
*
* @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
* @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
* an array of identifiers of all the child sessions. Otherwise it should
* be an empty array.
* @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
* and will be filled with a list of {@link ApexInfo} objects, each of which
* contains metadata about one of the packages being submitted as part of
* the session.
* @return whether the submission of the session was successful.
*/
boolean submitStagedSession(
int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
try {
return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
}
}
/**
* Mark a staged session previously submitted using {@cde submitStagedSession} as ready to be
* applied at next reboot.
*
* @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
* @return true upon success, false if the session is unknown.
*/
boolean markStagedSessionReady(int sessionId) {
try {
return mApexService.markStagedSessionReady(sessionId);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
}
}
/**
* Dumps various state information to the provided {@link PrintWriter} object.
*
* @param pw the {@link PrintWriter} object to send information to.
* @param packageName a {@link String} containing a package name, or {@code null}. If set, only
* information about that specific package will be dumped.
*/
void dump(PrintWriter pw, @Nullable String packageName) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
ipw.println();
ipw.println("Active APEX packages:");
ipw.increaseIndent();
try {
populateActivePackagesCache();
for (PackageInfo pi : mActivePackagesCache.values()) {
if (packageName != null && !packageName.equals(pi.packageName)) {
continue;
}
ipw.println(pi.packageName);
ipw.increaseIndent();
ipw.println("Version: " + pi.versionCode);
ipw.println("Path: " + pi.applicationInfo.sourceDir);
ipw.decreaseIndent();
}
ipw.decreaseIndent();
ipw.println();
ipw.println("APEX session state:");
ipw.increaseIndent();
final ApexSessionInfo[] sessions = mApexService.getSessions();
for (ApexSessionInfo si : sessions) {
ipw.println("Session ID: " + Integer.toString(si.sessionId));
ipw.increaseIndent();
if (si.isUnknown) {
ipw.println("State: UNKNOWN");
} else if (si.isVerified) {
ipw.println("State: VERIFIED");
} else if (si.isStaged) {
ipw.println("State: STAGED");
} else if (si.isActivated) {
ipw.println("State: ACTIVATED");
} else if (si.isActivationPendingRetry) {
ipw.println("State: ACTIVATION PENDING RETRY");
} else if (si.isActivationFailed) {
ipw.println("State: ACTIVATION FAILED");
}
ipw.decreaseIndent();
}
ipw.decreaseIndent();
} catch (RemoteException e) {
ipw.println("Couldn't communicate with apexd.");
}
}
}

View File

@@ -186,7 +186,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
};
public PackageInstallerService(Context context, PackageManagerService pm) {
public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) {
mContext = context;
mPm = pm;
mPermissionManager = LocalServices.getService(PermissionManagerInternal.class);
@@ -204,7 +204,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
mSessionsDir.mkdirs();
mStagingManager = new StagingManager(pm, this);
mStagingManager = new StagingManager(pm, this, am);
}
private void setBootCompleted() {

View File

@@ -119,9 +119,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.apex.ApexInfo;
import android.apex.ApexSessionInfo;
import android.apex.IApexService;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppDetailsActivity;
@@ -733,10 +730,10 @@ public class PackageManagerService extends IPackageManager.Stub
@GuardedBy("mPackages")
final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>();
private PackageManager mPackageManager;
private final ModuleInfoProvider mModuleInfoProvider;
private final ApexManager mApexManager;
class PackageParserCallback implements PackageParser.Callback {
@Override public final boolean hasFeature(String feature) {
return PackageManagerService.this.hasSystemFeature(feature, 0);
@@ -3074,7 +3071,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
mInstallerService = new PackageInstallerService(context, this);
mApexManager = new ApexManager();
mInstallerService = new PackageInstallerService(context, this, mApexManager);
final Pair<ComponentName, String> instantAppResolverComponent =
getInstantAppResolverLPr();
if (instantAppResolverComponent != null) {
@@ -3934,27 +3932,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
//
if (!matchFactoryOnly && (flags & MATCH_APEX) != 0) {
//TODO(b/123052859) Don't do file operations every time there is a query.
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
if (apex != null) {
try {
final ApexInfo activePkg = apex.getActivePackage(packageName);
if (activePkg != null && !TextUtils.isEmpty(activePkg.packagePath)) {
try {
return PackageParser.generatePackageInfoFromApex(
new File(activePkg.packagePath), true /* collect certs */);
} catch (PackageParserException pe) {
Log.e(TAG, "Unable to parse package at "
+ activePkg.packagePath, pe);
}
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to retrieve packages from apexservice: " + e.toString());
}
} else {
Log.e(TAG, "Unable to connect to apexservice for querying packages.");
}
return mApexManager.getActivePackage(packageName);
}
}
return null;
@@ -7851,25 +7829,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (listApex) {
// TODO(b/119767311): include uninstalled/inactive APEX if
// MATCH_UNINSTALLED_PACKAGES is set.
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
if (apex != null) {
try {
final ApexInfo[] activePkgs = apex.getActivePackages();
for (ApexInfo ai : activePkgs) {
try {
list.add(PackageParser.generatePackageInfoFromApex(
new File(ai.packagePath), true /* collect certs */));
} catch (PackageParserException pe) {
throw new IllegalStateException("Unable to parse: " + ai, pe);
}
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to retrieve packages from apexservice: " + e.toString());
}
} else {
Log.e(TAG, "Unable to connect to apexservice for querying packages.");
}
list.addAll(mApexManager.getActivePackages());
}
return new ParceledListSlice<>(list);
}
@@ -21319,51 +21279,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_APEX)) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
ipw.println();
ipw.println("Active APEX packages:");
ipw.increaseIndent();
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
try {
final ApexInfo[] activeApexes = apex.getActivePackages();
for (ApexInfo ai : activeApexes) {
if (packageName != null && !packageName.equals(ai.packageName)) {
continue;
}
ipw.println(ai.packageName);
ipw.increaseIndent();
ipw.println("Version: " + Long.toString(ai.versionCode));
ipw.println("Path: " + ai.packagePath);
ipw.decreaseIndent();
}
ipw.decreaseIndent();
ipw.println();
ipw.println("APEX session state:");
ipw.increaseIndent();
final ApexSessionInfo[] sessions = apex.getSessions();
for (ApexSessionInfo si : sessions) {
ipw.println("Session ID: " + Integer.toString(si.sessionId));
ipw.increaseIndent();
if (si.isUnknown) {
ipw.println("State: UNKNOWN");
} else if (si.isVerified) {
ipw.println("State: VERIFIED");
} else if (si.isStaged) {
ipw.println("State: STAGED");
} else if (si.isActivated) {
ipw.println("State: ACTIVATED");
} else if (si.isActivationPendingRetry) {
ipw.println("State: ACTIVATION PENDING RETRY");
} else if (si.isActivationFailed) {
ipw.println("State: ACTIVATION FAILED");
}
ipw.decreaseIndent();
}
ipw.decreaseIndent();
} catch (RemoteException e) {
ipw.println("Couldn't communicate with apexd.");
}
mApexManager.dump(pw, packageName);
}
}

View File

@@ -20,12 +20,12 @@ import android.annotation.NonNull;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.IApexService;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
@@ -41,7 +41,6 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.apk.ApkSignatureVerifier;
@@ -68,14 +67,16 @@ public class StagingManager {
private final PackageInstallerService mPi;
private final PackageManagerService mPm;
private final ApexManager mApexManager;
private final Handler mBgHandler;
@GuardedBy("mStagedSessions")
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
StagingManager(PackageManagerService pm, PackageInstallerService pi) {
StagingManager(PackageManagerService pm, PackageInstallerService pi, ApexManager am) {
mPm = pm;
mPi = pi;
mApexManager = am;
mBgHandler = BackgroundThread.getHandler();
}
@@ -100,7 +101,7 @@ public class StagingManager {
return new ParceledListSlice<>(result);
}
private static boolean validateApexSignature(String apexPath, String packageName) {
private boolean validateApexSignature(String apexPath, String packageName) {
final SigningDetails signingDetails;
try {
signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
@@ -109,17 +110,9 @@ public class StagingManager {
return false;
}
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
final ApexInfo apexInfo;
try {
apexInfo = apex.getActivePackage(packageName);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact APEXD", re);
return false;
}
final PackageInfo packageInfo = mApexManager.getActivePackage(packageName);
if (apexInfo == null || TextUtils.isEmpty(apexInfo.packageName)) {
if (packageInfo == null) {
// TODO: What is the right thing to do here ? This implies there's no active package
// with the given name. This should never be the case in production (where we only
// accept updates to existing APEXes) but may be required for testing.
@@ -129,9 +122,10 @@ public class StagingManager {
final SigningDetails existingSigningDetails;
try {
existingSigningDetails = ApkSignatureVerifier.verify(
apexInfo.packagePath, SignatureSchemeVersion.JAR);
packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
} catch (PackageParserException e) {
Slog.e(TAG, "Unable to parse APEX package: " + apexInfo.packagePath, e);
Slog.e(TAG, "Unable to parse APEX package: "
+ packageInfo.applicationInfo.sourceDir, e);
return false;
}
@@ -143,10 +137,10 @@ public class StagingManager {
return false;
}
private static boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
List<PackageInstallerSession> childSessions,
ApexInfoList apexInfoList) {
return sendSubmitStagedSessionRequest(
private boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
List<PackageInstallerSession> childSessions,
ApexInfoList apexInfoList) {
return mApexManager.submitStagedSession(
session.sessionId,
childSessions != null
? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
@@ -154,33 +148,6 @@ public class StagingManager {
apexInfoList);
}
private static boolean sendSubmitStagedSessionRequest(
int sessionId, int[] childSessionIds, ApexInfoList apexInfoList) {
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
boolean success;
try {
success = apex.submitStagedSession(sessionId, childSessionIds, apexInfoList);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
return false;
}
return success;
}
private static boolean sendMarkStagedSessionReadyRequest(int sessionId) {
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
boolean success;
try {
success = apex.markStagedSessionReady(sessionId);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
return false;
}
return success;
}
private static boolean isApexSession(@NonNull PackageInstallerSession session) {
return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
}
@@ -260,7 +227,7 @@ public class StagingManager {
}
session.setStagedSessionReady();
if (!sendMarkStagedSessionReadyRequest(session.sessionId)) {
if (!mApexManager.markStagedSessionReady(session.sessionId)) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"APEX staging failed, check logcat messages from apexd for more "
+ "details.");
@@ -284,16 +251,12 @@ public class StagingManager {
private void resumeSession(@NonNull PackageInstallerSession session) {
if (sessionContainsApex(session)) {
// Check with apexservice whether the apex
// packages have been activated.
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
ApexSessionInfo apexSessionInfo;
try {
apexSessionInfo = apex.getStagedSessionInfo(session.sessionId);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
// TODO should we retry here? Mark the session as failed?
// Check with apexservice whether the apex packages have been activated.
ApexSessionInfo apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
if (apexSessionInfo == null) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"apexd did not know anything about a staged session supposed to be"
+ "activated");
return;
}
if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) {
@@ -323,8 +286,8 @@ public class StagingManager {
// The APEX part of the session is activated, proceed with the installation of APKs.
if (!installApksInSession(session)) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"APEX activation failed. Check logcat messages from apexd for "
+ "more information.");
"Staged installation of APKs failed. Check logcat messages for"
+ "more information.");
return;
}
session.setStagedSessionApplied();