perms = mSystemPermissions.get(uid);
if (perms != null) {
if (perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
.contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
}
return PackageManager.PERMISSION_DENIED;
}
@Override
public boolean isPermissionRevokedByPolicy(String permission, String packageName, int userId) {
if (UserHandle.getCallingUserId() != userId) {
mContext.enforceCallingPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"isPermissionRevokedByPolicy for user " + userId);
}
if (checkPermission(permission, packageName, userId)
== PackageManager.PERMISSION_GRANTED) {
return false;
}
final long identity = Binder.clearCallingIdentity();
try {
final int flags = getPermissionFlags(permission, packageName, userId);
return (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public String getPermissionControllerPackageName() {
synchronized (mPackages) {
return mRequiredInstallerPackage;
}
}
/**
* Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
* or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
* @param checkShell whether to prevent shell from access if there's a debugging restriction
* @param message the message to log on security exception
*/
void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
boolean checkShell, String message) {
if (userId < 0) {
throw new IllegalArgumentException("Invalid userId " + userId);
}
if (checkShell) {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
}
if (userId == UserHandle.getUserId(callingUid)) return;
if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
if (requireFullPermission) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
} else {
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
} catch (SecurityException se) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS, message);
}
}
}
}
void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
if (callingUid == Process.SHELL_UID) {
if (userHandle >= 0
&& sUserManager.hasUserRestriction(restriction, userHandle)) {
throw new SecurityException("Shell does not have permission to access user "
+ userHandle);
} else if (userHandle < 0) {
Slog.e(TAG, "Unable to check shell permission for user " + userHandle + "\n\t"
+ Debug.getCallers(3));
}
}
}
private BasePermission findPermissionTreeLP(String permName) {
for(BasePermission bp : mSettings.mPermissionTrees.values()) {
if (permName.startsWith(bp.name) &&
permName.length() > bp.name.length() &&
permName.charAt(bp.name.length()) == '.') {
return bp;
}
}
return null;
}
private BasePermission checkPermissionTreeLP(String permName) {
if (permName != null) {
BasePermission bp = findPermissionTreeLP(permName);
if (bp != null) {
if (bp.uid == UserHandle.getAppId(Binder.getCallingUid())) {
return bp;
}
throw new SecurityException("Calling uid "
+ Binder.getCallingUid()
+ " is not allowed to add to permission tree "
+ bp.name + " owned by uid " + bp.uid);
}
}
throw new SecurityException("No permission tree found for " + permName);
}
static boolean compareStrings(CharSequence s1, CharSequence s2) {
if (s1 == null) {
return s2 == null;
}
if (s2 == null) {
return false;
}
if (s1.getClass() != s2.getClass()) {
return false;
}
return s1.equals(s2);
}
static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
if (pi1.icon != pi2.icon) return false;
if (pi1.logo != pi2.logo) return false;
if (pi1.protectionLevel != pi2.protectionLevel) return false;
if (!compareStrings(pi1.name, pi2.name)) return false;
if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
// We'll take care of setting this one.
if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
// These are not currently stored in settings.
//if (!compareStrings(pi1.group, pi2.group)) return false;
//if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
//if (pi1.labelRes != pi2.labelRes) return false;
//if (pi1.descriptionRes != pi2.descriptionRes) return false;
return true;
}
int permissionInfoFootprint(PermissionInfo info) {
int size = info.name.length();
if (info.nonLocalizedLabel != null) size += info.nonLocalizedLabel.length();
if (info.nonLocalizedDescription != null) size += info.nonLocalizedDescription.length();
return size;
}
int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
int size = 0;
for (BasePermission perm : mSettings.mPermissions.values()) {
if (perm.uid == tree.uid) {
size += perm.name.length() + permissionInfoFootprint(perm.perm.info);
}
}
return size;
}
void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
// We calculate the max size of permissions defined by this uid and throw
// if that plus the size of 'info' would exceed our stated maximum.
if (tree.uid != Process.SYSTEM_UID) {
final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
if (curTreeSize + permissionInfoFootprint(info) > MAX_PERMISSION_TREE_FOOTPRINT) {
throw new SecurityException("Permission tree size cap exceeded");
}
}
}
boolean addPermissionLocked(PermissionInfo info, boolean async) {
if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
throw new SecurityException("Label must be specified in permission");
}
BasePermission tree = checkPermissionTreeLP(info.name);
BasePermission bp = mSettings.mPermissions.get(info.name);
boolean added = bp == null;
boolean changed = true;
int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
if (added) {
enforcePermissionCapLocked(info, tree);
bp = new BasePermission(info.name, tree.sourcePackage,
BasePermission.TYPE_DYNAMIC);
} else if (bp.type != BasePermission.TYPE_DYNAMIC) {
throw new SecurityException(
"Not allowed to modify non-dynamic permission "
+ info.name);
} else {
if (bp.protectionLevel == fixedLevel
&& bp.perm.owner.equals(tree.perm.owner)
&& bp.uid == tree.uid
&& comparePermissionInfos(bp.perm.info, info)) {
changed = false;
}
}
bp.protectionLevel = fixedLevel;
info = new PermissionInfo(info);
info.protectionLevel = fixedLevel;
bp.perm = new PackageParser.Permission(tree.perm.owner, info);
bp.perm.info.packageName = tree.perm.info.packageName;
bp.uid = tree.uid;
if (added) {
mSettings.mPermissions.put(info.name, bp);
}
if (changed) {
if (!async) {
mSettings.writeLPr();
} else {
scheduleWriteSettingsLocked();
}
}
return added;
}
@Override
public boolean addPermission(PermissionInfo info) {
synchronized (mPackages) {
return addPermissionLocked(info, false);
}
}
@Override
public boolean addPermissionAsync(PermissionInfo info) {
synchronized (mPackages) {
return addPermissionLocked(info, true);
}
}
@Override
public void removePermission(String name) {
synchronized (mPackages) {
checkPermissionTreeLP(name);
BasePermission bp = mSettings.mPermissions.get(name);
if (bp != null) {
if (bp.type != BasePermission.TYPE_DYNAMIC) {
throw new SecurityException(
"Not allowed to modify non-dynamic permission "
+ name);
}
mSettings.mPermissions.remove(name);
mSettings.writeLPr();
}
}
}
private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(PackageParser.Package pkg,
BasePermission bp) {
int index = pkg.requestedPermissions.indexOf(bp.name);
if (index == -1) {
throw new SecurityException("Package " + pkg.packageName
+ " has not requested permission " + bp.name);
}
if (!bp.isRuntime() && !bp.isDevelopment()) {
throw new SecurityException("Permission " + bp.name
+ " is not a changeable permission type");
}
}
@Override
public void grantRuntimePermission(String packageName, String name, final int userId) {
grantRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
}
private void grantRuntimePermission(String packageName, String name, final int userId,
boolean overridePolicy) {
if (!sUserManager.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
"grantRuntimePermission");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"grantRuntimePermission");
final int uid;
final SettingBase sb;
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + name);
}
enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
// If a permission review is required for legacy apps we represent
// their permissions as always granted runtime ones since we need
// to keep the review required permission flag per user while an
// install permission's state is shared across all users.
if (mPermissionReviewRequired
&& pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
&& bp.isRuntime()) {
return;
}
uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
sb = (SettingBase) pkg.mExtras;
if (sb == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
final PermissionsState permissionsState = sb.getPermissionsState();
final int flags = permissionsState.getPermissionFlags(name, userId);
if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
throw new SecurityException("Cannot grant system fixed permission "
+ name + " for package " + packageName);
}
if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
throw new SecurityException("Cannot grant policy fixed permission "
+ name + " for package " + packageName);
}
if (bp.isDevelopment()) {
// Development permissions must be handled specially, since they are not
// normal runtime permissions. For now they apply to all users.
if (permissionsState.grantInstallPermission(bp) !=
PermissionsState.PERMISSION_OPERATION_FAILURE) {
scheduleWriteSettingsLocked();
}
return;
}
if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
throw new SecurityException("Cannot grant non-ephemeral permission"
+ name + " for package " + packageName);
}
if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
return;
}
final int result = permissionsState.grantRuntimePermission(bp, userId);
switch (result) {
case PermissionsState.PERMISSION_OPERATION_FAILURE: {
return;
}
case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
mHandler.post(new Runnable() {
@Override
public void run() {
killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
}
});
}
break;
}
if (bp.isRuntime()) {
logPermissionGranted(mContext, name, packageName);
}
mOnPermissionChangeListeners.onPermissionsChanged(uid);
// Not critical if that is lost - app has to request again.
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
}
// Only need to do this if user is initialized. Otherwise it's a new user
// and there are no processes running as the user yet and there's no need
// to make an expensive call to remount processes for the changed permissions.
if (READ_EXTERNAL_STORAGE.equals(name)
|| WRITE_EXTERNAL_STORAGE.equals(name)) {
final long token = Binder.clearCallingIdentity();
try {
if (sUserManager.isInitialized(userId)) {
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
@Override
public void revokeRuntimePermission(String packageName, String name, int userId) {
revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
}
private void revokeRuntimePermission(String packageName, String name, int userId,
boolean overridePolicy) {
if (!sUserManager.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
"revokeRuntimePermission");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"revokeRuntimePermission");
final int appId;
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + name);
}
enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
// If a permission review is required for legacy apps we represent
// their permissions as always granted runtime ones since we need
// to keep the review required permission flag per user while an
// install permission's state is shared across all users.
if (mPermissionReviewRequired
&& pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
&& bp.isRuntime()) {
return;
}
SettingBase sb = (SettingBase) pkg.mExtras;
if (sb == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
final PermissionsState permissionsState = sb.getPermissionsState();
final int flags = permissionsState.getPermissionFlags(name, userId);
if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
throw new SecurityException("Cannot revoke system fixed permission "
+ name + " for package " + packageName);
}
if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
throw new SecurityException("Cannot revoke policy fixed permission "
+ name + " for package " + packageName);
}
if (bp.isDevelopment()) {
// Development permissions must be handled specially, since they are not
// normal runtime permissions. For now they apply to all users.
if (permissionsState.revokeInstallPermission(bp) !=
PermissionsState.PERMISSION_OPERATION_FAILURE) {
scheduleWriteSettingsLocked();
}
return;
}
if (permissionsState.revokeRuntimePermission(bp, userId) ==
PermissionsState.PERMISSION_OPERATION_FAILURE) {
return;
}
if (bp.isRuntime()) {
logPermissionRevoked(mContext, name, packageName);
}
mOnPermissionChangeListeners.onPermissionsChanged(pkg.applicationInfo.uid);
// Critical, after this call app should never have the permission.
mSettings.writeRuntimePermissionsForUserLPr(userId, true);
appId = UserHandle.getAppId(pkg.applicationInfo.uid);
}
killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
}
/**
* Get the first event id for the permission.
*
* There are four events for each permission:
* - Request permission: first id + 0
* - Grant permission: first id + 1
* - Request for permission denied: first id + 2
* - Revoke permission: first id + 3
*
*
* @param name name of the permission
*
* @return The first event id for the permission
*/
private static int getBaseEventId(@NonNull String name) {
int eventIdIndex = ALL_DANGEROUS_PERMISSIONS.indexOf(name);
if (eventIdIndex == -1) {
if (AppOpsManager.permissionToOpCode(name) == AppOpsManager.OP_NONE
|| "user".equals(Build.TYPE)) {
Log.i(TAG, "Unknown permission " + name);
return MetricsEvent.ACTION_PERMISSION_REQUEST_UNKNOWN;
} else {
// Most likely #ALL_DANGEROUS_PERMISSIONS needs to be updated.
//
// Also update
// - EventLogger#ALL_DANGEROUS_PERMISSIONS
// - metrics_constants.proto
throw new IllegalStateException("Unknown permission " + name);
}
}
return MetricsEvent.ACTION_PERMISSION_REQUEST_READ_CALENDAR + eventIdIndex * 4;
}
/**
* Log that a permission was revoked.
*
* @param context Context of the caller
* @param name name of the permission
* @param packageName package permission if for
*/
private static void logPermissionRevoked(@NonNull Context context, @NonNull String name,
@NonNull String packageName) {
MetricsLogger.action(context, getBaseEventId(name) + 3, packageName);
}
/**
* Log that a permission request was granted.
*
* @param context Context of the caller
* @param name name of the permission
* @param packageName package permission if for
*/
private static void logPermissionGranted(@NonNull Context context, @NonNull String name,
@NonNull String packageName) {
MetricsLogger.action(context, getBaseEventId(name) + 1, packageName);
}
@Override
public void resetRuntimePermissions() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
"revokeRuntimePermission");
int callingUid = Binder.getCallingUid();
if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"resetRuntimePermissions");
}
synchronized (mPackages) {
updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL);
for (int userId : UserManagerService.getInstance().getUserIds()) {
final int packageCount = mPackages.size();
for (int i = 0; i < packageCount; i++) {
PackageParser.Package pkg = mPackages.valueAt(i);
if (!(pkg.mExtras instanceof PackageSetting)) {
continue;
}
PackageSetting ps = (PackageSetting) pkg.mExtras;
resetUserChangesToRuntimePermissionsAndFlagsLPw(ps, userId);
}
}
}
}
@Override
public int getPermissionFlags(String name, String packageName, int userId) {
if (!sUserManager.exists(userId)) {
return 0;
}
enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"getPermissionFlags");
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
return 0;
}
final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
return 0;
}
SettingBase sb = (SettingBase) pkg.mExtras;
if (sb == null) {
return 0;
}
PermissionsState permissionsState = sb.getPermissionsState();
return permissionsState.getPermissionFlags(name, userId);
}
}
@Override
public void updatePermissionFlags(String name, String packageName, int flagMask,
int flagValues, int userId) {
if (!sUserManager.exists(userId)) {
return;
}
enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"updatePermissionFlags");
// Only the system can change these flags and nothing else.
if (getCallingUid() != Process.SYSTEM_UID) {
flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
}
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + name);
}
SettingBase sb = (SettingBase) pkg.mExtras;
if (sb == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
PermissionsState permissionsState = sb.getPermissionsState();
boolean hadState = permissionsState.getRuntimePermissionState(name, userId) != null;
if (permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues)) {
// Install and runtime permissions are stored in different places,
// so figure out what permission changed and persist the change.
if (permissionsState.getInstallPermissionState(name) != null) {
scheduleWriteSettingsLocked();
} else if (permissionsState.getRuntimePermissionState(name, userId) != null
|| hadState) {
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
}
}
}
}
/**
* Update the permission flags for all packages and runtime permissions of a user in order
* to allow device or profile owner to remove POLICY_FIXED.
*/
@Override
public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
if (!sUserManager.exists(userId)) {
return;
}
enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlagsForAllApps");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"updatePermissionFlagsForAllApps");
// Only the system can change system fixed flags.
if (getCallingUid() != Process.SYSTEM_UID) {
flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
}
synchronized (mPackages) {
boolean changed = false;
final int packageCount = mPackages.size();
for (int pkgIndex = 0; pkgIndex < packageCount; pkgIndex++) {
final PackageParser.Package pkg = mPackages.valueAt(pkgIndex);
SettingBase sb = (SettingBase) pkg.mExtras;
if (sb == null) {
continue;
}
PermissionsState permissionsState = sb.getPermissionsState();
changed |= permissionsState.updatePermissionFlagsForAllPermissions(
userId, flagMask, flagValues);
}
if (changed) {
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
}
}
}
private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
!= PackageManager.PERMISSION_GRANTED
&& mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(message + " requires "
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
}
}
@Override
public boolean shouldShowRequestPermissionRationale(String permissionName,
String packageName, int userId) {
if (UserHandle.getCallingUserId() != userId) {
mContext.enforceCallingPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"canShowRequestPermissionRationale for user " + userId);
}
final int uid = getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId);
if (UserHandle.getAppId(getCallingUid()) != UserHandle.getAppId(uid)) {
return false;
}
if (checkPermission(permissionName, packageName, userId)
== PackageManager.PERMISSION_GRANTED) {
return false;
}
final int flags;
final long identity = Binder.clearCallingIdentity();
try {
flags = getPermissionFlags(permissionName,
packageName, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED
| PackageManager.FLAG_PERMISSION_USER_FIXED;
if ((flags & fixedFlags) != 0) {
return false;
}
return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
}
@Override
public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
"addOnPermissionsChangeListener");
synchronized (mPackages) {
mOnPermissionChangeListeners.addListenerLocked(listener);
}
}
@Override
public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
synchronized (mPackages) {
mOnPermissionChangeListeners.removeListenerLocked(listener);
}
}
@Override
public boolean isProtectedBroadcast(String actionName) {
synchronized (mPackages) {
if (mProtectedBroadcasts.contains(actionName)) {
return true;
} else if (actionName != null) {
// TODO: remove these terrible hacks
if (actionName.startsWith("android.net.netmon.lingerExpired")
|| actionName.startsWith("com.android.server.sip.SipWakeupTimer")
|| actionName.startsWith("com.android.internal.telephony.data-reconnect")
|| actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) {
return true;
}
}
}
return false;
}
@Override
public int checkSignatures(String pkg1, String pkg2) {
synchronized (mPackages) {
final PackageParser.Package p1 = mPackages.get(pkg1);
final PackageParser.Package p2 = mPackages.get(pkg2);
if (p1 == null || p1.mExtras == null
|| p2 == null || p2.mExtras == null) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
return compareSignatures(p1.mSignatures, p2.mSignatures);
}
}
@Override
public int checkUidSignatures(int uid1, int uid2) {
// Map to base uids.
uid1 = UserHandle.getAppId(uid1);
uid2 = UserHandle.getAppId(uid2);
// reader
synchronized (mPackages) {
Signature[] s1;
Signature[] s2;
Object obj = mSettings.getUserIdLPr(uid1);
if (obj != null) {
if (obj instanceof SharedUserSetting) {
s1 = ((SharedUserSetting)obj).signatures.mSignatures;
} else if (obj instanceof PackageSetting) {
s1 = ((PackageSetting)obj).signatures.mSignatures;
} else {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
} else {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
obj = mSettings.getUserIdLPr(uid2);
if (obj != null) {
if (obj instanceof SharedUserSetting) {
s2 = ((SharedUserSetting)obj).signatures.mSignatures;
} else if (obj instanceof PackageSetting) {
s2 = ((PackageSetting)obj).signatures.mSignatures;
} else {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
} else {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
return compareSignatures(s1, s2);
}
}
/**
* This method should typically only be used when granting or revoking
* permissions, since the app may immediately restart after this call.
*
* If you're doing surgery on app code/data, use {@link PackageFreezer} to
* guard your work against the app being relaunched.
*/
private void killUid(int appId, int userId, String reason) {
final long identity = Binder.clearCallingIdentity();
try {
IActivityManager am = ActivityManager.getService();
if (am != null) {
try {
am.killUid(appId, userId, reason);
} catch (RemoteException e) {
/* ignore - same process */
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Compares two sets of signatures. Returns:
*
* {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
*
* {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
*
* {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
*
* {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
*
* {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
*/
static int compareSignatures(Signature[] s1, Signature[] s2) {
if (s1 == null) {
return s2 == null
? PackageManager.SIGNATURE_NEITHER_SIGNED
: PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
}
if (s2 == null) {
return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
}
if (s1.length != s2.length) {
return PackageManager.SIGNATURE_NO_MATCH;
}
// Since both signature sets are of size 1, we can compare without HashSets.
if (s1.length == 1) {
return s1[0].equals(s2[0]) ?
PackageManager.SIGNATURE_MATCH :
PackageManager.SIGNATURE_NO_MATCH;
}
ArraySet set1 = new ArraySet();
for (Signature sig : s1) {
set1.add(sig);
}
ArraySet set2 = new ArraySet();
for (Signature sig : s2) {
set2.add(sig);
}
// Make sure s2 contains all signatures in s1.
if (set1.equals(set2)) {
return PackageManager.SIGNATURE_MATCH;
}
return PackageManager.SIGNATURE_NO_MATCH;
}
/**
* If the database version for this type of package (internal storage or
* external storage) is less than the version where package signatures
* were updated, return true.
*/
private boolean isCompatSignatureUpdateNeeded(PackageParser.Package scannedPkg) {
final VersionInfo ver = getSettingsVersionForPackage(scannedPkg);
return ver.databaseVersion < DatabaseVersion.SIGNATURE_END_ENTITY;
}
/**
* Used for backward compatibility to make sure any packages with
* certificate chains get upgraded to the new style. {@code existingSigs}
* will be in the old format (since they were stored on disk from before the
* system upgrade) and {@code scannedSigs} will be in the newer format.
*/
private int compareSignaturesCompat(PackageSignatures existingSigs,
PackageParser.Package scannedPkg) {
if (!isCompatSignatureUpdateNeeded(scannedPkg)) {
return PackageManager.SIGNATURE_NO_MATCH;
}
ArraySet existingSet = new ArraySet();
for (Signature sig : existingSigs.mSignatures) {
existingSet.add(sig);
}
ArraySet scannedCompatSet = new ArraySet();
for (Signature sig : scannedPkg.mSignatures) {
try {
Signature[] chainSignatures = sig.getChainSignatures();
for (Signature chainSig : chainSignatures) {
scannedCompatSet.add(chainSig);
}
} catch (CertificateEncodingException e) {
scannedCompatSet.add(sig);
}
}
/*
* Make sure the expanded scanned set contains all signatures in the
* existing one.
*/
if (scannedCompatSet.equals(existingSet)) {
// Migrate the old signatures to the new scheme.
existingSigs.assignSignatures(scannedPkg.mSignatures);
// The new KeySets will be re-added later in the scanning process.
synchronized (mPackages) {
mSettings.mKeySetManagerService.removeAppKeySetDataLPw(scannedPkg.packageName);
}
return PackageManager.SIGNATURE_MATCH;
}
return PackageManager.SIGNATURE_NO_MATCH;
}
private boolean isRecoverSignatureUpdateNeeded(PackageParser.Package scannedPkg) {
final VersionInfo ver = getSettingsVersionForPackage(scannedPkg);
return ver.databaseVersion < DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
}
private int compareSignaturesRecover(PackageSignatures existingSigs,
PackageParser.Package scannedPkg) {
if (!isRecoverSignatureUpdateNeeded(scannedPkg)) {
return PackageManager.SIGNATURE_NO_MATCH;
}
String msg = null;
try {
if (Signature.areEffectiveMatch(existingSigs.mSignatures, scannedPkg.mSignatures)) {
logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for "
+ scannedPkg.packageName);
return PackageManager.SIGNATURE_MATCH;
}
} catch (CertificateException e) {
msg = e.getMessage();
}
logCriticalInfo(Log.INFO,
"Failed to recover certificates for " + scannedPkg.packageName + ": " + msg);
return PackageManager.SIGNATURE_NO_MATCH;
}
@Override
public List getAllPackages() {
synchronized (mPackages) {
return new ArrayList(mPackages.keySet());
}
}
@Override
public String[] getPackagesForUid(int uid) {
final int userId = UserHandle.getUserId(uid);
uid = UserHandle.getAppId(uid);
// reader
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(uid);
if (obj instanceof SharedUserSetting) {
final SharedUserSetting sus = (SharedUserSetting) obj;
final int N = sus.packages.size();
String[] res = new String[N];
final Iterator it = sus.packages.iterator();
int i = 0;
while (it.hasNext()) {
PackageSetting ps = it.next();
if (ps.getInstalled(userId)) {
res[i++] = ps.name;
} else {
res = ArrayUtils.removeElement(String.class, res, res[i]);
}
}
return res;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
if (ps.getInstalled(userId)) {
return new String[]{ps.name};
}
}
}
return null;
}
@Override
public String getNameForUid(int uid) {
// reader
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj instanceof SharedUserSetting) {
final SharedUserSetting sus = (SharedUserSetting) obj;
return sus.name + ":" + sus.userId;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
return ps.name;
}
}
return null;
}
@Override
public int getUidForSharedUser(String sharedUserName) {
if(sharedUserName == null) {
return -1;
}
// reader
synchronized (mPackages) {
SharedUserSetting suid;
try {
suid = mSettings.getSharedUserLPw(sharedUserName, 0, 0, false);
if (suid != null) {
return suid.userId;
}
} catch (PackageManagerException ignore) {
// can't happen, but, still need to catch it
}
return -1;
}
}
@Override
public int getFlagsForUid(int uid) {
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj instanceof SharedUserSetting) {
final SharedUserSetting sus = (SharedUserSetting) obj;
return sus.pkgFlags;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
return ps.pkgFlags;
}
}
return 0;
}
@Override
public int getPrivateFlagsForUid(int uid) {
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj instanceof SharedUserSetting) {
final SharedUserSetting sus = (SharedUserSetting) obj;
return sus.pkgPrivateFlags;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
return ps.pkgPrivateFlags;
}
}
return 0;
}
@Override
public boolean isUidPrivileged(int uid) {
uid = UserHandle.getAppId(uid);
// reader
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(uid);
if (obj instanceof SharedUserSetting) {
final SharedUserSetting sus = (SharedUserSetting) obj;
final Iterator it = sus.packages.iterator();
while (it.hasNext()) {
if (it.next().isPrivileged()) {
return true;
}
}
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
return ps.isPrivileged();
}
}
return false;
}
@Override
public String[] getAppOpPermissionPackages(String permissionName) {
synchronized (mPackages) {
ArraySet pkgs = mAppOpPermissionPackages.get(permissionName);
if (pkgs == null) {
return null;
}
return pkgs.toArray(new String[pkgs.size()]);
}
}
@Override
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
int flags, int userId) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForResolve(flags, userId, intent);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /*requireFullPermission*/, false /*checkShell*/, "resolve intent");
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
final List query = queryIntentActivitiesInternal(intent, resolvedType,
flags, userId);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
final ResolveInfo bestChoice =
chooseBestActivity(intent, resolvedType, flags, query, userId);
return bestChoice;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
@Override
public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) {
if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID)) {
throw new SecurityException(
"findPersistentPreferredActivity can only be run by the system");
}
if (!sUserManager.exists(userId)) {
return null;
}
intent = updateIntentForResolve(intent);
final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
final int flags = updateFlagsForResolve(0, userId, intent);
final List query = queryIntentActivitiesInternal(intent, resolvedType, flags,
userId);
synchronized (mPackages) {
return findPersistentPreferredActivityLP(intent, resolvedType, flags, query, false,
userId);
}
}
@Override
public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
IntentFilter filter, int match, ComponentName activity) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG_PREFERRED) {
Log.v(TAG, "setLastChosenActivity intent=" + intent
+ " resolvedType=" + resolvedType
+ " flags=" + flags
+ " filter=" + filter
+ " match=" + match
+ " activity=" + activity);
filter.dump(new PrintStreamPrinter(System.out), " ");
}
intent.setComponent(null);
final List query = queryIntentActivitiesInternal(intent, resolvedType, flags,
userId);
// Find any earlier preferred or last chosen entries and nuke them
findPreferredActivity(intent, resolvedType,
flags, query, 0, false, true, false, userId);
// Add the new activity as the last chosen for this filter
addPreferredActivityInternal(filter, match, null, activity, false, userId,
"Setting last chosen");
}
@Override
public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG_PREFERRED) Log.v(TAG, "Querying last chosen activity for " + intent);
final List query = queryIntentActivitiesInternal(intent, resolvedType, flags,
userId);
return findPreferredActivity(intent, resolvedType, flags, query, 0,
false, false, false, userId);
}
private boolean isEphemeralDisabled() {
// ephemeral apps have been disabled across the board
if (DISABLE_EPHEMERAL_APPS) {
return true;
}
// system isn't up yet; can't read settings, so, assume no ephemeral apps
if (!mSystemReady) {
return true;
}
// we can't get a content resolver until the system is ready; these checks must happen last
final ContentResolver resolver = mContext.getContentResolver();
if (Global.getInt(resolver, Global.ENABLE_EPHEMERAL_FEATURE, 1) == 0) {
return true;
}
return Secure.getInt(resolver, Secure.WEB_ACTION_ENABLED, 1) == 0;
}
private boolean isEphemeralAllowed(
Intent intent, List resolvedActivities, int userId,
boolean skipPackageCheck) {
// Short circuit and return early if possible.
if (isEphemeralDisabled()) {
return false;
}
final int callingUser = UserHandle.getCallingUserId();
if (callingUser != UserHandle.USER_SYSTEM) {
return false;
}
if (mEphemeralResolverConnection == null) {
return false;
}
if (mEphemeralInstallerComponent == null) {
return false;
}
if (intent.getComponent() != null) {
return false;
}
if ((intent.getFlags() & Intent.FLAG_IGNORE_EPHEMERAL) != 0) {
return false;
}
if (!skipPackageCheck && intent.getPackage() != null) {
return false;
}
final boolean isWebUri = hasWebURI(intent);
if (!isWebUri || intent.getData().getHost() == null) {
return false;
}
// Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution.
synchronized (mPackages) {
final int count = (resolvedActivities == null ? 0 : resolvedActivities.size());
for (int n = 0; n < count; n++) {
ResolveInfo info = resolvedActivities.get(n);
String packageName = info.activityInfo.packageName;
PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
// Try to get the status from User settings first
long packedStatus = getDomainVerificationStatusLPr(ps, userId);
int status = (int) (packedStatus >> 32);
if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
|| status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
if (DEBUG_EPHEMERAL) {
Slog.v(TAG, "DENY ephemeral apps;"
+ " pkg: " + packageName + ", status: " + status);
}
return false;
}
}
}
}
// We've exhausted all ways to deny ephemeral application; let the system look for them.
return true;
}
private void requestEphemeralResolutionPhaseTwo(EphemeralResponse responseObj,
Intent origIntent, String resolvedType, Intent launchIntent, String callingPackage,
int userId) {
final Message msg = mHandler.obtainMessage(EPHEMERAL_RESOLUTION_PHASE_TWO,
new EphemeralRequest(responseObj, origIntent, resolvedType, launchIntent,
callingPackage, userId));
mHandler.sendMessage(msg);
}
private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
int flags, List query, int userId) {
if (query != null) {
final int N = query.size();
if (N == 1) {
return query.get(0);
} else if (N > 1) {
final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
// If there is more than one activity with the same priority,
// then let the user decide between them.
ResolveInfo r0 = query.get(0);
ResolveInfo r1 = query.get(1);
if (DEBUG_INTENT_MATCHING || debug) {
Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs "
+ r1.activityInfo.name + "=" + r1.priority);
}
// If the first activity has a higher priority, or a different
// default, then it is always desirable to pick it.
if (r0.priority != r1.priority
|| r0.preferredOrder != r1.preferredOrder
|| r0.isDefault != r1.isDefault) {
return query.get(0);
}
// If we have saved a preference for a preferred activity for
// this Intent, use that.
ResolveInfo ri = findPreferredActivity(intent, resolvedType,
flags, query, r0.priority, true, false, debug, userId);
if (ri != null) {
return ri;
}
ri = new ResolveInfo(mResolveInfo);
ri.activityInfo = new ActivityInfo(ri.activityInfo);
ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
// If all of the options come from the same package, show the application's
// label and icon instead of the generic resolver's.
// Some calls like Intent.resolveActivityInfo query the ResolveInfo from here
// and then throw away the ResolveInfo itself, meaning that the caller loses
// the resolvePackageName. Therefore the activityInfo.labelRes above provides
// a fallback for this case; we only set the target package's resources on
// the ResolveInfo, not the ActivityInfo.
final String intentPackage = intent.getPackage();
if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {
final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
ri.resolvePackageName = intentPackage;
if (userNeedsBadging(userId)) {
ri.noResourceId = true;
} else {
ri.icon = appi.icon;
}
ri.iconResourceId = appi.icon;
ri.labelRes = appi.labelRes;
}
ri.activityInfo.applicationInfo = new ApplicationInfo(
ri.activityInfo.applicationInfo);
if (userId != 0) {
ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
}
// Make sure that the resolver is displayable in car mode
if (ri.activityInfo.metaData == null) ri.activityInfo.metaData = new Bundle();
ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true);
return ri;
}
}
return null;
}
/**
* Return true if the given list is not empty and all of its contents have
* an activityInfo with the given package name.
*/
private boolean allHavePackage(List list, String packageName) {
if (ArrayUtils.isEmpty(list)) {
return false;
}
for (int i = 0, N = list.size(); i < N; i++) {
final ResolveInfo ri = list.get(i);
final ActivityInfo ai = ri != null ? ri.activityInfo : null;
if (ai == null || !packageName.equals(ai.packageName)) {
return false;
}
}
return true;
}
private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType,
int flags, List query, boolean debug, int userId) {
final int N = query.size();
PersistentPreferredIntentResolver ppir = mSettings.mPersistentPreferredActivities
.get(userId);
// Get the list of persistent preferred activities that handle the intent
if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for presistent preferred activities...");
List pprefs = ppir != null
? ppir.queryIntent(intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
(flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0,
(flags & PackageManager.MATCH_EPHEMERAL) != 0, userId)
: null;
if (pprefs != null && pprefs.size() > 0) {
final int M = pprefs.size();
for (int i=0; i")
+ "\n component=" + ppa.mComponent);
ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
}
final ActivityInfo ai = getActivityInfo(ppa.mComponent,
flags | MATCH_DISABLED_COMPONENTS, userId);
if (DEBUG_PREFERRED || debug) {
Slog.v(TAG, "Found persistent preferred activity:");
if (ai != null) {
ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
} else {
Slog.v(TAG, " null");
}
}
if (ai == null) {
// This previously registered persistent preferred activity
// component is no longer known. Ignore it and do NOT remove it.
continue;
}
for (int j=0; j query, int priority, boolean always,
boolean removeMatches, boolean debug, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForResolve(flags, userId, intent);
intent = updateIntentForResolve(intent);
// writer
synchronized (mPackages) {
// Try to find a matching persistent preferred activity.
ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query,
debug, userId);
// If a persistent preferred activity matched, use it.
if (pri != null) {
return pri;
}
PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId);
// Get the list of preferred activities that handle the intent
if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
List prefs = pir != null
? pir.queryIntent(intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
(flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0,
(flags & PackageManager.MATCH_EPHEMERAL) != 0, userId)
: null;
if (prefs != null && prefs.size() > 0) {
boolean changed = false;
try {
// First figure out how good the original match set is.
// We will only allow preferred activities that came
// from the same match quality.
int match = 0;
if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
final int N = query.size();
for (int j=0; j match) {
match = ri.match;
}
}
if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Best match: 0x"
+ Integer.toHexString(match));
match &= IntentFilter.MATCH_CATEGORY_MASK;
final int M = prefs.size();
for (int i=0; i")
+ "\n component=" + pa.mPref.mComponent);
pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
}
if (pa.mPref.mMatch != match) {
if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping bad match "
+ Integer.toHexString(pa.mPref.mMatch));
continue;
}
// If it's not an "always" type preferred activity and that's what we're
// looking for, skip it.
if (always && !pa.mPref.mAlways) {
if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
continue;
}
final ActivityInfo ai = getActivityInfo(
pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
| MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
userId);
if (DEBUG_PREFERRED || debug) {
Slog.v(TAG, "Found preferred activity:");
if (ai != null) {
ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
} else {
Slog.v(TAG, " null");
}
}
if (ai == null) {
// This previously registered preferred activity
// component is no longer known. Most likely an update
// to the app was installed and in the new version this
// component no longer exists. Clean it up by removing
// it from the preferred activities list, and skip it.
Slog.w(TAG, "Removing dangling preferred activity: "
+ pa.mPref.mComponent);
pir.removeFilter(pa);
changed = true;
continue;
}
for (int j=0; j matches =
getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
if (matches != null) {
int size = matches.size();
for (int i = 0; i < size; i++) {
if (matches.get(i).getTargetUserId() == targetUserId) return true;
}
}
if (hasWebURI(intent)) {
// cross-profile app linking works only towards the parent.
final UserInfo parent = getProfileParent(sourceUserId);
synchronized(mPackages) {
int flags = updateFlagsForResolve(0, parent.id, intent);
CrossProfileDomainInfo xpDomainInfo = getCrossProfileDomainPreferredLpr(
intent, resolvedType, flags, sourceUserId, parent.id);
return xpDomainInfo != null;
}
}
return false;
}
private UserInfo getProfileParent(int userId) {
final long identity = Binder.clearCallingIdentity();
try {
return sUserManager.getProfileParent(userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private List getMatchingCrossProfileIntentFilters(Intent intent,
String resolvedType, int userId) {
CrossProfileIntentResolver resolver = mSettings.mCrossProfileIntentResolvers.get(userId);
if (resolver != null) {
return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/,
false /*visibleToEphemeral*/, false /*isInstant*/, userId);
}
return null;
}
@Override
public @NonNull ParceledListSlice queryIntentActivities(Intent intent,
String resolvedType, int flags, int userId) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
return new ParceledListSlice<>(
queryIntentActivitiesInternal(intent, resolvedType, flags, userId));
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
/**
* Returns the package name of the calling Uid if it's an ephemeral app. If it isn't
* ephemeral, returns {@code null}.
*/
private String getEphemeralPackageName(int callingUid) {
final int appId = UserHandle.getAppId(callingUid);
synchronized (mPackages) {
final Object obj = mSettings.getUserIdLPr(appId);
if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
return ps.pkg.applicationInfo.isInstantApp() ? ps.pkg.packageName : null;
}
}
return null;
}
private @NonNull List queryIntentActivitiesInternal(Intent intent,
String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
final String ephemeralPkgName = getEphemeralPackageName(Binder.getCallingUid());
flags = updateFlagsForResolve(flags, userId, intent);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
"query intent activities");
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
comp = intent.getComponent();
}
}
if (comp != null) {
final List list = new ArrayList(1);
final ActivityInfo ai = getActivityInfo(comp, flags, userId);
if (ai != null) {
// When specifying an explicit component, we prevent the activity from being
// used when either 1) the calling package is normal and the activity is within
// an ephemeral application or 2) the calling package is ephemeral and the
// activity is not visible to ephemeral applications.
boolean matchEphemeral =
(flags & PackageManager.MATCH_EPHEMERAL) != 0;
boolean ephemeralVisibleOnly =
(flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0;
boolean blockResolution =
(!matchEphemeral && ephemeralPkgName == null
&& (ai.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_EPHEMERAL) != 0)
|| (ephemeralVisibleOnly && ephemeralPkgName != null
&& (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) == 0);
if (!blockResolution) {
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
list.add(ri);
}
}
return list;
}
// reader
boolean sortResult = false;
boolean addEphemeral = false;
List result;
final String pkgName = intent.getPackage();
synchronized (mPackages) {
if (pkgName == null) {
List matchingFilters =
getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
// Check for results that need to skip the current profile.
ResolveInfo xpResolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent,
resolvedType, flags, userId);
if (xpResolveInfo != null) {
List xpResult = new ArrayList(1);
xpResult.add(xpResolveInfo);
return filterForEphemeral(
filterIfNotSystemUser(xpResult, userId), ephemeralPkgName);
}
// Check for results in the current profile.
result = filterIfNotSystemUser(mActivities.queryIntent(
intent, resolvedType, flags, userId), userId);
addEphemeral =
isEphemeralAllowed(intent, result, userId, false /*skipPackageCheck*/);
// Check for cross profile results.
boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
xpResolveInfo = queryCrossProfileIntents(
matchingFilters, intent, resolvedType, flags, userId,
hasNonNegativePriorityResult);
if (xpResolveInfo != null && isUserEnabled(xpResolveInfo.targetUserId)) {
boolean isVisibleToUser = filterIfNotSystemUser(
Collections.singletonList(xpResolveInfo), userId).size() > 0;
if (isVisibleToUser) {
result.add(xpResolveInfo);
sortResult = true;
}
}
if (hasWebURI(intent)) {
CrossProfileDomainInfo xpDomainInfo = null;
final UserInfo parent = getProfileParent(userId);
if (parent != null) {
xpDomainInfo = getCrossProfileDomainPreferredLpr(intent, resolvedType,
flags, userId, parent.id);
}
if (xpDomainInfo != null) {
if (xpResolveInfo != null) {
// If we didn't remove it, the cross-profile ResolveInfo would be twice
// in the result.
result.remove(xpResolveInfo);
}
if (result.size() == 0 && !addEphemeral) {
// No result in current profile, but found candidate in parent user.
// And we are not going to add emphemeral app, so we can return the
// result straight away.
result.add(xpDomainInfo.resolveInfo);
return filterForEphemeral(result, ephemeralPkgName);
}
} else if (result.size() <= 1 && !addEphemeral) {
// No result in parent user and <= 1 result in current profile, and we
// are not going to add emphemeral app, so we can return the result without
// further processing.
return filterForEphemeral(result, ephemeralPkgName);
}
// We have more than one candidate (combining results from current and parent
// profile), so we need filtering and sorting.
result = filterCandidatesWithDomainPreferredActivitiesLPr(
intent, flags, result, xpDomainInfo, userId);
sortResult = true;
}
} else {
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
result = filterForEphemeral(filterIfNotSystemUser(
mActivities.queryIntentForPackage(
intent, resolvedType, flags, pkg.activities, userId),
userId), ephemeralPkgName);
} else {
// the caller wants to resolve for a particular package; however, there
// were no installed results, so, try to find an ephemeral result
addEphemeral = isEphemeralAllowed(
intent, null /*result*/, userId, true /*skipPackageCheck*/);
result = new ArrayList();
}
}
}
if (addEphemeral) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral");
final EphemeralRequest requestObject = new EphemeralRequest(
null /*responseObj*/, intent /*origIntent*/, resolvedType,
null /*launchIntent*/, null /*callingPackage*/, userId);
final EphemeralResponse intentInfo = EphemeralResolver.doEphemeralResolutionPhaseOne(
mContext, mEphemeralResolverConnection, requestObject);
if (intentInfo != null) {
if (DEBUG_EPHEMERAL) {
Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
}
final ResolveInfo ephemeralInstaller = new ResolveInfo(mEphemeralInstallerInfo);
ephemeralInstaller.ephemeralResponse = intentInfo;
// make sure this resolver is the default
ephemeralInstaller.isDefault = true;
ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
| IntentFilter.MATCH_ADJUSTMENT_NORMAL;
// add a non-generic filter
ephemeralInstaller.filter = new IntentFilter(intent.getAction());
ephemeralInstaller.filter.addDataPath(
intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL);
result.add(ephemeralInstaller);
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
if (sortResult) {
Collections.sort(result, mResolvePrioritySorter);
}
return filterForEphemeral(result, ephemeralPkgName);
}
private static class CrossProfileDomainInfo {
/* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
ResolveInfo resolveInfo;
/* Best domain verification status of the activities found in the other profile */
int bestDomainVerificationStatus;
}
private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
String resolvedType, int flags, int sourceUserId, int parentUserId) {
if (!sUserManager.hasUserRestriction(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
sourceUserId)) {
return null;
}
List resultTargetUser = mActivities.queryIntent(intent,
resolvedType, flags, parentUserId);
if (resultTargetUser == null || resultTargetUser.isEmpty()) {
return null;
}
CrossProfileDomainInfo result = null;
int size = resultTargetUser.size();
for (int i = 0; i < size; i++) {
ResolveInfo riTargetUser = resultTargetUser.get(i);
// Intent filter verification is only for filters that specify a host. So don't return
// those that handle all web uris.
if (riTargetUser.handleAllWebDataURI) {
continue;
}
String packageName = riTargetUser.activityInfo.packageName;
PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps == null) {
continue;
}
long verificationState = getDomainVerificationStatusLPr(ps, parentUserId);
int status = (int)(verificationState >> 32);
if (result == null) {
result = new CrossProfileDomainInfo();
result.resolveInfo = createForwardingResolveInfoUnchecked(new IntentFilter(),
sourceUserId, parentUserId);
result.bestDomainVerificationStatus = status;
} else {
result.bestDomainVerificationStatus = bestDomainVerificationStatus(status,
result.bestDomainVerificationStatus);
}
}
// Don't consider matches with status NEVER across profiles.
if (result != null && result.bestDomainVerificationStatus
== INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
return null;
}
return result;
}
/**
* Verification statuses are ordered from the worse to the best, except for
* INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, which is the worse.
*/
private int bestDomainVerificationStatus(int status1, int status2) {
if (status1 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
return status2;
}
if (status2 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
return status1;
}
return (int) MathUtils.max(status1, status2);
}
private boolean isUserEnabled(int userId) {
long callingId = Binder.clearCallingIdentity();
try {
UserInfo userInfo = sUserManager.getUserInfo(userId);
return userInfo != null && userInfo.isEnabled();
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
/**
* Filter out activities with systemUserOnly flag set, when current user is not System.
*
* @return filtered list
*/
private List filterIfNotSystemUser(List resolveInfos, int userId) {
if (userId == UserHandle.USER_SYSTEM) {
return resolveInfos;
}
for (int i = resolveInfos.size() - 1; i >= 0; i--) {
ResolveInfo info = resolveInfos.get(i);
if ((info.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
resolveInfos.remove(i);
}
}
return resolveInfos;
}
/**
* Filters out ephemeral activities.
* When resolving for an ephemeral app, only activities that 1) are defined in the
* ephemeral app or 2) marked with {@code visibleToEphemeral} are returned.
*
* @param resolveInfos The pre-filtered list of resolved activities
* @param ephemeralPkgName The ephemeral package name. If {@code null}, no filtering
* is performed.
* @return A filtered list of resolved activities.
*/
private List filterForEphemeral(List resolveInfos,
String ephemeralPkgName) {
if (ephemeralPkgName == null) {
return resolveInfos;
}
for (int i = resolveInfos.size() - 1; i >= 0; i--) {
ResolveInfo info = resolveInfos.get(i);
final boolean isEphemeralApp = info.activityInfo.applicationInfo.isInstantApp();
// allow activities that are defined in the provided package
if (isEphemeralApp && ephemeralPkgName.equals(info.activityInfo.packageName)) {
continue;
}
// allow activities that have been explicitly exposed to ephemeral apps
if (!isEphemeralApp
&& ((info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) != 0)) {
continue;
}
resolveInfos.remove(i);
}
return resolveInfos;
}
/**
* @param resolveInfos list of resolve infos in descending priority order
* @return if the list contains a resolve info with non-negative priority
*/
private boolean hasNonNegativePriority(List resolveInfos) {
return resolveInfos.size() > 0 && resolveInfos.get(0).priority >= 0;
}
private static boolean hasWebURI(Intent intent) {
if (intent.getData() == null) {
return false;
}
final String scheme = intent.getScheme();
if (TextUtils.isEmpty(scheme)) {
return false;
}
return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS);
}
private List filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent,
int matchFlags, List candidates, CrossProfileDomainInfo xpDomainInfo,
int userId) {
final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0;
if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
Slog.v(TAG, "Filtering results with preferred activities. Candidates count: " +
candidates.size());
}
ArrayList result = new ArrayList();
ArrayList alwaysList = new ArrayList();
ArrayList undefinedList = new ArrayList();
ArrayList alwaysAskList = new ArrayList();
ArrayList neverList = new ArrayList();
ArrayList matchAllList = new ArrayList();
synchronized (mPackages) {
final int count = candidates.size();
// First, try to use linked apps. Partition the candidates into four lists:
// one for the final results, one for the "do not use ever", one for "undefined status"
// and finally one for "browser app type".
for (int n=0; n> 32);
int linkGeneration = (int)(packedStatus & 0xFFFFFFFF);
if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
if (DEBUG_DOMAIN_VERIFICATION || debug) {
Slog.i(TAG, " + always: " + info.activityInfo.packageName
+ " : linkgen=" + linkGeneration);
}
// Use link-enabled generation as preferredOrder, i.e.
// prefer newly-enabled over earlier-enabled.
info.preferredOrder = linkGeneration;
alwaysList.add(info);
} else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
if (DEBUG_DOMAIN_VERIFICATION || debug) {
Slog.i(TAG, " + never: " + info.activityInfo.packageName);
}
neverList.add(info);
} else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
if (DEBUG_DOMAIN_VERIFICATION || debug) {
Slog.i(TAG, " + always-ask: " + info.activityInfo.packageName);
}
alwaysAskList.add(info);
} else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED ||
status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) {
if (DEBUG_DOMAIN_VERIFICATION || debug) {
Slog.i(TAG, " + ask: " + info.activityInfo.packageName);
}
undefinedList.add(info);
}
}
}
// We'll want to include browser possibilities in a few cases
boolean includeBrowser = false;
// First try to add the "always" resolution(s) for the current user, if any
if (alwaysList.size() > 0) {
result.addAll(alwaysList);
} else {
// Add all undefined apps as we want them to appear in the disambiguation dialog.
result.addAll(undefinedList);
// Maybe add one for the other profile.
if (xpDomainInfo != null && (
xpDomainInfo.bestDomainVerificationStatus
!= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) {
result.add(xpDomainInfo.resolveInfo);
}
includeBrowser = true;
}
// The presence of any 'always ask' alternatives means we'll also offer browsers.
// If there were 'always' entries their preferred order has been set, so we also
// back that off to make the alternatives equivalent
if (alwaysAskList.size() > 0) {
for (ResolveInfo i : result) {
i.preferredOrder = 0;
}
result.addAll(alwaysAskList);
includeBrowser = true;
}
if (includeBrowser) {
// Also add browsers (all of them or only the default one)
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.v(TAG, " ...including browsers in candidate set");
}
if ((matchFlags & MATCH_ALL) != 0) {
result.addAll(matchAllList);
} else {
// Browser/generic handling case. If there's a default browser, go straight
// to that (but only if there is no other higher-priority match).
final String defaultBrowserPackageName = getDefaultBrowserPackageName(userId);
int maxMatchPrio = 0;
ResolveInfo defaultBrowserMatch = null;
final int numCandidates = matchAllList.size();
for (int n = 0; n < numCandidates; n++) {
ResolveInfo info = matchAllList.get(n);
// track the highest overall match priority...
if (info.priority > maxMatchPrio) {
maxMatchPrio = info.priority;
}
// ...and the highest-priority default browser match
if (info.activityInfo.packageName.equals(defaultBrowserPackageName)) {
if (defaultBrowserMatch == null
|| (defaultBrowserMatch.priority < info.priority)) {
if (debug) {
Slog.v(TAG, "Considering default browser match " + info);
}
defaultBrowserMatch = info;
}
}
}
if (defaultBrowserMatch != null
&& defaultBrowserMatch.priority >= maxMatchPrio
&& !TextUtils.isEmpty(defaultBrowserPackageName))
{
if (debug) {
Slog.v(TAG, "Default browser match " + defaultBrowserMatch);
}
result.add(defaultBrowserMatch);
} else {
result.addAll(matchAllList);
}
}
// If there is nothing selected, add all candidates and remove the ones that the user
// has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
if (result.size() == 0) {
result.addAll(candidates);
result.removeAll(neverList);
}
}
}
if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
Slog.v(TAG, "Filtered results with preferred activities. New candidates count: " +
result.size());
for (ResolveInfo info : result) {
Slog.v(TAG, " + " + info.activityInfo);
}
}
return result;
}
// Returns a packed value as a long:
//
// high 'int'-sized word: link status: undefined/ask/never/always.
// low 'int'-sized word: relative priority among 'always' results.
private long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
long result = ps.getDomainVerificationStatusForUser(userId);
// if none available, get the master status
if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
if (ps.getIntentFilterVerificationInfo() != null) {
result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32;
}
}
return result;
}
private ResolveInfo querySkipCurrentProfileIntents(
List matchingFilters, Intent intent, String resolvedType,
int flags, int sourceUserId) {
if (matchingFilters != null) {
int size = matchingFilters.size();
for (int i = 0; i < size; i ++) {
CrossProfileIntentFilter filter = matchingFilters.get(i);
if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
// Checking if there are activities in the target user that can handle the
// intent.
ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent,
resolvedType, flags, sourceUserId);
if (resolveInfo != null) {
return resolveInfo;
}
}
}
}
return null;
}
// Return matching ResolveInfo in target user if any.
private ResolveInfo queryCrossProfileIntents(
List matchingFilters, Intent intent, String resolvedType,
int flags, int sourceUserId, boolean matchInCurrentProfile) {
if (matchingFilters != null) {
// Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
// match the same intent. For performance reasons, it is better not to
// run queryIntent twice for the same userId
SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray();
int size = matchingFilters.size();
for (int i = 0; i < size; i++) {
CrossProfileIntentFilter filter = matchingFilters.get(i);
int targetUserId = filter.getTargetUserId();
boolean skipCurrentProfile =
(filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0;
boolean skipCurrentProfileIfNoMatchFound =
(filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0;
if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId)
&& (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) {
// Checking if there are activities in the target user that can handle the
// intent.
ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent,
resolvedType, flags, sourceUserId);
if (resolveInfo != null) return resolveInfo;
alreadyTriedUserIds.put(targetUserId, true);
}
}
}
return null;
}
/**
* If the filter's target user can handle the intent and is enabled: returns a ResolveInfo that
* will forward the intent to the filter's target user.
* Otherwise, returns null.
*/
private ResolveInfo createForwardingResolveInfo(CrossProfileIntentFilter filter, Intent intent,
String resolvedType, int flags, int sourceUserId) {
int targetUserId = filter.getTargetUserId();
List resultTargetUser = mActivities.queryIntent(intent,
resolvedType, flags, targetUserId);
if (resultTargetUser != null && isUserEnabled(targetUserId)) {
// If all the matches in the target profile are suspended, return null.
for (int i = resultTargetUser.size() - 1; i >= 0; i--) {
if ((resultTargetUser.get(i).activityInfo.applicationInfo.flags
& ApplicationInfo.FLAG_SUSPENDED) == 0) {
return createForwardingResolveInfoUnchecked(filter, sourceUserId,
targetUserId);
}
}
}
return null;
}
private ResolveInfo createForwardingResolveInfoUnchecked(IntentFilter filter,
int sourceUserId, int targetUserId) {
ResolveInfo forwardingResolveInfo = new ResolveInfo();
long ident = Binder.clearCallingIdentity();
boolean targetIsProfile;
try {
targetIsProfile = sUserManager.getUserInfo(targetUserId).isManagedProfile();
} finally {
Binder.restoreCallingIdentity(ident);
}
String className;
if (targetIsProfile) {
className = FORWARD_INTENT_TO_MANAGED_PROFILE;
} else {
className = FORWARD_INTENT_TO_PARENT;
}
ComponentName forwardingActivityComponentName = new ComponentName(
mAndroidApplication.packageName, className);
ActivityInfo forwardingActivityInfo = getActivityInfo(forwardingActivityComponentName, 0,
sourceUserId);
if (!targetIsProfile) {
forwardingActivityInfo.showUserIcon = targetUserId;
forwardingResolveInfo.noResourceId = true;
}
forwardingResolveInfo.activityInfo = forwardingActivityInfo;
forwardingResolveInfo.priority = 0;
forwardingResolveInfo.preferredOrder = 0;
forwardingResolveInfo.match = 0;
forwardingResolveInfo.isDefault = true;
forwardingResolveInfo.filter = filter;
forwardingResolveInfo.targetUserId = targetUserId;
return forwardingResolveInfo;
}
@Override
public @NonNull ParceledListSlice queryIntentActivityOptions(ComponentName caller,
Intent[] specifics, String[] specificTypes, Intent intent,
String resolvedType, int flags, int userId) {
return new ParceledListSlice<>(queryIntentActivityOptionsInternal(caller, specifics,
specificTypes, intent, resolvedType, flags, userId));
}
private @NonNull List queryIntentActivityOptionsInternal(ComponentName caller,
Intent[] specifics, String[] specificTypes, Intent intent,
String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
flags = updateFlagsForResolve(flags, userId, intent);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
"query intent activity options");
final String resultsAction = intent.getAction();
final List results = queryIntentActivitiesInternal(intent, resolvedType, flags
| PackageManager.GET_RESOLVED_FILTER, userId);
if (DEBUG_INTENT_MATCHING) {
Log.v(TAG, "Query " + intent + ": " + results);
}
int specificsPos = 0;
int N;
// todo: note that the algorithm used here is O(N^2). This
// isn't a problem in our current environment, but if we start running
// into situations where we have more than 5 or 10 matches then this
// should probably be changed to something smarter...
// First we go through and resolve each of the specific items
// that were supplied, taking care of removing any corresponding
// duplicate items in the generic resolve list.
if (specifics != null) {
for (int i=0; i it = rii.filter.actionsIterator();
if (it == null) {
continue;
}
while (it.hasNext()) {
final String action = it.next();
if (resultsAction != null && resultsAction.equals(action)) {
// If this action was explicitly requested, then don't
// remove things that have it.
continue;
}
for (int j=i+1; j queryIntentReceivers(Intent intent,
String resolvedType, int flags, int userId) {
return new ParceledListSlice<>(
queryIntentReceiversInternal(intent, resolvedType, flags, userId));
}
private @NonNull List queryIntentReceiversInternal(Intent intent,
String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
flags = updateFlagsForResolve(flags, userId, intent);
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
comp = intent.getComponent();
}
}
if (comp != null) {
List list = new ArrayList(1);
ActivityInfo ai = getReceiverInfo(comp, flags, userId);
if (ai != null) {
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
list.add(ri);
}
return list;
}
// reader
synchronized (mPackages) {
String pkgName = intent.getPackage();
if (pkgName == null) {
return mReceivers.queryIntent(intent, resolvedType, flags, userId);
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,
userId);
}
return Collections.emptyList();
}
}
@Override
public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForResolve(flags, userId, intent);
List query = queryIntentServicesInternal(intent, resolvedType, flags, userId);
if (query != null) {
if (query.size() >= 1) {
// If there is more than one service with the same priority,
// just arbitrarily pick the first one.
return query.get(0);
}
}
return null;
}
@Override
public @NonNull ParceledListSlice queryIntentServices(Intent intent,
String resolvedType, int flags, int userId) {
return new ParceledListSlice<>(
queryIntentServicesInternal(intent, resolvedType, flags, userId));
}
private @NonNull List queryIntentServicesInternal(Intent intent,
String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
flags = updateFlagsForResolve(flags, userId, intent);
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
comp = intent.getComponent();
}
}
if (comp != null) {
final List list = new ArrayList(1);
final ServiceInfo si = getServiceInfo(comp, flags, userId);
if (si != null) {
final ResolveInfo ri = new ResolveInfo();
ri.serviceInfo = si;
list.add(ri);
}
return list;
}
// reader
synchronized (mPackages) {
String pkgName = intent.getPackage();
if (pkgName == null) {
return mServices.queryIntent(intent, resolvedType, flags, userId);
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
return mServices.queryIntentForPackage(intent, resolvedType, flags, pkg.services,
userId);
}
return Collections.emptyList();
}
}
@Override
public @NonNull ParceledListSlice queryIntentContentProviders(Intent intent,
String resolvedType, int flags, int userId) {
return new ParceledListSlice<>(
queryIntentContentProvidersInternal(intent, resolvedType, flags, userId));
}
private @NonNull List queryIntentContentProvidersInternal(
Intent intent, String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
flags = updateFlagsForResolve(flags, userId, intent);
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
comp = intent.getComponent();
}
}
if (comp != null) {
final List list = new ArrayList(1);
final ProviderInfo pi = getProviderInfo(comp, flags, userId);
if (pi != null) {
final ResolveInfo ri = new ResolveInfo();
ri.providerInfo = pi;
list.add(ri);
}
return list;
}
// reader
synchronized (mPackages) {
String pkgName = intent.getPackage();
if (pkgName == null) {
return mProviders.queryIntent(intent, resolvedType, flags, userId);
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
return mProviders.queryIntentForPackage(
intent, resolvedType, flags, pkg.providers, userId);
}
return Collections.emptyList();
}
}
@Override
public ParceledListSlice getInstalledPackages(int flags, int userId) {
if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForPackage(flags, userId, null);
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"get installed packages");
// writer
synchronized (mPackages) {
ArrayList list;
if (listUninstalled) {
list = new ArrayList<>(mSettings.mPackages.size());
for (PackageSetting ps : mSettings.mPackages.values()) {
if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId)) {
continue;
}
final PackageInfo pi = generatePackageInfo(ps, flags, userId);
if (pi != null) {
list.add(pi);
}
}
} else {
list = new ArrayList<>(mPackages.size());
for (PackageParser.Package p : mPackages.values()) {
if (filterSharedLibPackageLPr((PackageSetting) p.mExtras,
Binder.getCallingUid(), userId)) {
continue;
}
final PackageInfo pi = generatePackageInfo((PackageSetting)
p.mExtras, flags, userId);
if (pi != null) {
list.add(pi);
}
}
}
return new ParceledListSlice<>(list);
}
}
private void addPackageHoldingPermissions(ArrayList list, PackageSetting ps,
String[] permissions, boolean[] tmp, int flags, int userId) {
int numMatch = 0;
final PermissionsState permissionsState = ps.getPermissionsState();
for (int i=0; i getPackagesHoldingPermissions(
String[] permissions, int flags, int userId) {
if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForPackage(flags, userId, permissions);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"get packages holding permissions");
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
// writer
synchronized (mPackages) {
ArrayList list = new ArrayList();
boolean[] tmpBools = new boolean[permissions.length];
if (listUninstalled) {
for (PackageSetting ps : mSettings.mPackages.values()) {
addPackageHoldingPermissions(list, ps, permissions, tmpBools, flags,
userId);
}
} else {
for (PackageParser.Package pkg : mPackages.values()) {
PackageSetting ps = (PackageSetting)pkg.mExtras;
if (ps != null) {
addPackageHoldingPermissions(list, ps, permissions, tmpBools, flags,
userId);
}
}
}
return new ParceledListSlice(list);
}
}
@Override
public ParceledListSlice getInstalledApplications(int flags, int userId) {
if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForApplication(flags, userId, null);
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
// writer
synchronized (mPackages) {
ArrayList list;
if (listUninstalled) {
list = new ArrayList<>(mSettings.mPackages.size());
for (PackageSetting ps : mSettings.mPackages.values()) {
ApplicationInfo ai;
int effectiveFlags = flags;
if (ps.isSystem()) {
effectiveFlags |= PackageManager.MATCH_ANY_USER;
}
if (ps.pkg != null) {
if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId)) {
continue;
}
ai = PackageParser.generateApplicationInfo(ps.pkg, effectiveFlags,
ps.readUserState(userId), userId);
if (ai != null) {
ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
}
} else {
// Shared lib filtering done in generateApplicationInfoFromSettingsLPw
// and already converts to externally visible package name
ai = generateApplicationInfoFromSettingsLPw(ps.name,
Binder.getCallingUid(), effectiveFlags, userId);
}
if (ai != null) {
list.add(ai);
}
}
} else {
list = new ArrayList<>(mPackages.size());
for (PackageParser.Package p : mPackages.values()) {
if (p.mExtras != null) {
PackageSetting ps = (PackageSetting) p.mExtras;
if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId)) {
continue;
}
ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
ps.readUserState(userId), userId);
if (ai != null) {
ai.packageName = resolveExternalPackageNameLPr(p);
list.add(ai);
}
}
}
}
return new ParceledListSlice<>(list);
}
}
@Override
public ParceledListSlice getInstantApps(int userId) {
if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return null;
}
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
"getEphemeralApplications");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"getEphemeralApplications");
synchronized (mPackages) {
List instantApps = mInstantAppRegistry
.getInstantAppsLPr(userId);
if (instantApps != null) {
return new ParceledListSlice<>(instantApps);
}
}
return null;
}
@Override
public boolean isInstantApp(String packageName, int userId) {
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"isInstantApp");
if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return false;
}
if (!isCallerSameApp(packageName)) {
return false;
}
synchronized (mPackages) {
PackageParser.Package pkg = mPackages.get(packageName);
if (pkg != null) {
return pkg.applicationInfo.isInstantApp();
}
}
return false;
}
@Override
public byte[] getInstantAppCookie(String packageName, int userId) {
if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return null;
}
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"getInstantAppCookie");
if (!isCallerSameApp(packageName)) {
return null;
}
synchronized (mPackages) {
return mInstantAppRegistry.getInstantAppCookieLPw(
packageName, userId);
}
}
@Override
public boolean setInstantAppCookie(String packageName, byte[] cookie, int userId) {
if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return true;
}
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"setInstantAppCookie");
if (!isCallerSameApp(packageName)) {
return false;
}
synchronized (mPackages) {
return mInstantAppRegistry.setInstantAppCookieLPw(
packageName, cookie, userId);
}
}
@Override
public Bitmap getInstantAppIcon(String packageName, int userId) {
if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return null;
}
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
"getInstantAppIcon");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"getInstantAppIcon");
synchronized (mPackages) {
return mInstantAppRegistry.getInstantAppIconLPw(
packageName, userId);
}
}
private boolean isCallerSameApp(String packageName) {
PackageParser.Package pkg = mPackages.get(packageName);
return pkg != null
&& UserHandle.getAppId(Binder.getCallingUid()) == pkg.applicationInfo.uid;
}
@Override
public @NonNull ParceledListSlice getPersistentApplications(int flags) {
return new ParceledListSlice<>(getPersistentApplicationsInternal(flags));
}
private @NonNull List getPersistentApplicationsInternal(int flags) {
final ArrayList finalList = new ArrayList();
// reader
synchronized (mPackages) {
final Iterator i = mPackages.values().iterator();
final int userId = UserHandle.getCallingUserId();
while (i.hasNext()) {
final PackageParser.Package p = i.next();
if (p.applicationInfo == null) continue;
final boolean matchesUnaware = ((flags & MATCH_DIRECT_BOOT_UNAWARE) != 0)
&& !p.applicationInfo.isDirectBootAware();
final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0)
&& p.applicationInfo.isDirectBootAware();
if ((p.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0
&& (!mSafeMode || isSystemApp(p))
&& (matchesUnaware || matchesAware)) {
PackageSetting ps = mSettings.mPackages.get(p.packageName);
if (ps != null) {
ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
ps.readUserState(userId), userId);
if (ai != null) {
finalList.add(ai);
}
}
}
}
}
return finalList;
}
@Override
public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId, name);
// reader
synchronized (mPackages) {
final PackageParser.Provider provider = mProvidersByAuthority.get(name);
PackageSetting ps = provider != null
? mSettings.mPackages.get(provider.owner.packageName)
: null;
return ps != null
&& mSettings.isEnabledAndMatchLPr(provider.info, flags, userId)
? PackageParser.generateProviderInfo(provider, flags,
ps.readUserState(userId), userId)
: null;
}
}
/**
* @deprecated
*/
@Deprecated
public void querySyncProviders(List outNames, List outInfo) {
// reader
synchronized (mPackages) {
final Iterator> i = mProvidersByAuthority
.entrySet().iterator();
final int userId = UserHandle.getCallingUserId();
while (i.hasNext()) {
Map.Entry entry = i.next();
PackageParser.Provider p = entry.getValue();
PackageSetting ps = mSettings.mPackages.get(p.owner.packageName);
if (ps != null && p.syncable
&& (!mSafeMode || (p.info.applicationInfo.flags
&ApplicationInfo.FLAG_SYSTEM) != 0)) {
ProviderInfo info = PackageParser.generateProviderInfo(p, 0,
ps.readUserState(userId), userId);
if (info != null) {
outNames.add(entry.getKey());
outInfo.add(info);
}
}
}
}
}
@Override
public @NonNull ParceledListSlice queryContentProviders(String processName,
int uid, int flags) {
final int userId = processName != null ? UserHandle.getUserId(uid)
: UserHandle.getCallingUserId();
if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForComponent(flags, userId, processName);
ArrayList finalList = null;
// reader
synchronized (mPackages) {
final Iterator i = mProviders.mProviders.values().iterator();
while (i.hasNext()) {
final PackageParser.Provider p = i.next();
PackageSetting ps = mSettings.mPackages.get(p.owner.packageName);
if (ps != null && p.info.authority != null
&& (processName == null
|| (p.info.processName.equals(processName)
&& UserHandle.isSameApp(p.info.applicationInfo.uid, uid)))
&& mSettings.isEnabledAndMatchLPr(p.info, flags, userId)) {
if (finalList == null) {
finalList = new ArrayList(3);
}
ProviderInfo info = PackageParser.generateProviderInfo(p, flags,
ps.readUserState(userId), userId);
if (info != null) {
finalList.add(info);
}
}
}
}
if (finalList != null) {
Collections.sort(finalList, mProviderInitOrderSorter);
return new ParceledListSlice(finalList);
}
return ParceledListSlice.emptyList();
}
@Override
public InstrumentationInfo getInstrumentationInfo(ComponentName name, int flags) {
// reader
synchronized (mPackages) {
final PackageParser.Instrumentation i = mInstrumentation.get(name);
return PackageParser.generateInstrumentationInfo(i, flags);
}
}
@Override
public @NonNull ParceledListSlice queryInstrumentation(
String targetPackage, int flags) {
return new ParceledListSlice<>(queryInstrumentationInternal(targetPackage, flags));
}
private @NonNull List queryInstrumentationInternal(String targetPackage,
int flags) {
ArrayList finalList = new ArrayList();
// reader
synchronized (mPackages) {
final Iterator i = mInstrumentation.values().iterator();
while (i.hasNext()) {
final PackageParser.Instrumentation p = i.next();
if (targetPackage == null
|| targetPackage.equals(p.info.targetPackage)) {
InstrumentationInfo ii = PackageParser.generateInstrumentationInfo(p,
flags);
if (ii != null) {
finalList.add(ii);
}
}
}
}
return finalList;
}
private void createIdmapsForPackageLI(PackageParser.Package pkg) {
ArrayMap overlays = mOverlays.get(pkg.packageName);
if (overlays == null) {
Slog.w(TAG, "Unable to create idmap for " + pkg.packageName + ": no overlay packages");
return;
}
for (PackageParser.Package opkg : overlays.values()) {
// Not much to do if idmap fails: we already logged the error
// and we certainly don't want to abort installation of pkg simply
// because an overlay didn't fit properly. For these reasons,
// ignore the return value of createIdmapForPackagePairLI.
createIdmapForPackagePairLI(pkg, opkg);
}
}
private boolean createIdmapForPackagePairLI(PackageParser.Package pkg,
PackageParser.Package opkg) {
if (!opkg.mTrustedOverlay) {
Slog.w(TAG, "Skipping target and overlay pair " + pkg.baseCodePath + " and " +
opkg.baseCodePath + ": overlay not trusted");
return false;
}
ArrayMap overlaySet = mOverlays.get(pkg.packageName);
if (overlaySet == null) {
Slog.e(TAG, "was about to create idmap for " + pkg.baseCodePath + " and " +
opkg.baseCodePath + " but target package has no known overlays");
return false;
}
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
// TODO: generate idmap for split APKs
try {
mInstaller.idmap(pkg.baseCodePath, opkg.baseCodePath, sharedGid);
} catch (InstallerException e) {
Slog.e(TAG, "Failed to generate idmap for " + pkg.baseCodePath + " and "
+ opkg.baseCodePath);
return false;
}
PackageParser.Package[] overlayArray =
overlaySet.values().toArray(new PackageParser.Package[0]);
Comparator cmp = new Comparator() {
public int compare(PackageParser.Package p1, PackageParser.Package p2) {
return p1.mOverlayPriority - p2.mOverlayPriority;
}
};
Arrays.sort(overlayArray, cmp);
pkg.applicationInfo.resourceDirs = new String[overlayArray.length];
int i = 0;
for (PackageParser.Package p : overlayArray) {
pkg.applicationInfo.resourceDirs[i++] = p.baseCodePath;
}
return true;
}
private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + dir.getAbsolutePath() + "]");
try {
scanDirLI(dir, parseFlags, scanFlags, currentTime);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();
if (ArrayUtils.isEmpty(files)) {
Log.d(TAG, "No files in app dir " + dir);
return;
}
if (DEBUG_PACKAGE_SCANNING) {
Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
+ " flags=0x" + Integer.toHexString(parseFlags));
}
ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir);
// Submit files for parsing in parallel
int fileCount = 0;
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
parallelPackageParser.submit(file, parseFlags);
fileCount++;
}
// Process results one by one
for (; fileCount > 0; fileCount--) {
ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
Throwable throwable = parseResult.throwable;
int errorCode = PackageManager.INSTALL_SUCCEEDED;
if (throwable == null) {
// Static shared libraries have synthetic package names
if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
renameStaticSharedLibraryPackage(parseResult.pkg);
}
try {
if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
currentTime, null);
}
} catch (PackageManagerException e) {
errorCode = e.error;
Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
}
} else if (throwable instanceof PackageParser.PackageParserException) {
PackageParser.PackageParserException e = (PackageParser.PackageParserException)
throwable;
errorCode = e.error;
Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
} else {
throw new IllegalStateException("Unexpected exception occurred while parsing "
+ parseResult.scanFile, throwable);
}
// Delete invalid userdata apps
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
logCriticalInfo(Log.WARN,
"Deleting invalid package at " + parseResult.scanFile);
removeCodePathLI(parseResult.scanFile);
}
}
parallelPackageParser.close();
}
private static File getSettingsProblemFile() {
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
File fname = new File(systemDir, "uiderrors.txt");
return fname;
}
static void reportSettingsProblem(int priority, String msg) {
logCriticalInfo(priority, msg);
}
static void logCriticalInfo(int priority, String msg) {
Slog.println(priority, TAG, msg);
EventLogTags.writePmCriticalInfo(msg);
try {
File fname = getSettingsProblemFile();
FileOutputStream out = new FileOutputStream(fname, true);
PrintWriter pw = new FastPrintWriter(out);
SimpleDateFormat formatter = new SimpleDateFormat();
String dateString = formatter.format(new Date(System.currentTimeMillis()));
pw.println(dateString + ": " + msg);
pw.close();
FileUtils.setPermissions(
fname.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
-1, -1);
} catch (java.io.IOException e) {
}
}
private long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
if (srcFile.isDirectory()) {
final File baseFile = new File(pkg.baseCodePath);
long maxModifiedTime = baseFile.lastModified();
if (pkg.splitCodePaths != null) {
for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
final File splitFile = new File(pkg.splitCodePaths[i]);
maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
}
}
return maxModifiedTime;
}
return srcFile.lastModified();
}
private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
final int policyFlags) throws PackageManagerException {
// When upgrading from pre-N MR1, verify the package time stamp using the package
// directory and not the APK file.
final long lastModifiedTime = mIsPreNMR1Upgrade
? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg, srcFile);
if (ps != null
&& ps.codePath.equals(srcFile)
&& ps.timeStamp == lastModifiedTime
&& !isCompatSignatureUpdateNeeded(pkg)
&& !isRecoverSignatureUpdateNeeded(pkg)) {
long mSigningKeySetId = ps.keySetData.getProperSigningKeySet();
KeySetManagerService ksms = mSettings.mKeySetManagerService;
ArraySet signingKs;
synchronized (mPackages) {
signingKs = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
}
if (ps.signatures.mSignatures != null
&& ps.signatures.mSignatures.length != 0
&& signingKs != null) {
// Optimization: reuse the existing cached certificates
// if the package appears to be unchanged.
pkg.mSignatures = ps.signatures.mSignatures;
pkg.mSigningKeys = signingKs;
return;
}
Slog.w(TAG, "PackageSetting for " + ps.name
+ " is missing signatures. Collecting certs again to recover them.");
} else {
Slog.i(TAG, srcFile.toString() + " changed; collecting certs");
}
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
PackageParser.collectCertificates(pkg, policyFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
/**
* Traces a package scan.
* @see #scanPackageLI(File, int, int, long, UserHandle)
*/
private PackageParser.Package scanPackageTracedLI(File scanFile, final int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]");
try {
return scanPackageLI(scanFile, parseFlags, scanFlags, currentTime, user);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
/**
* Scans a package and returns the newly parsed package.
* Returns {@code null} in case of errors and the error code is stored in mLastScanError
*/
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
}
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
// Static shared libraries have synthetic package names
if (pkg.applicationInfo.isStaticSharedLibrary()) {
renameStaticSharedLibraryPackage(pkg);
}
return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
}
/**
* Scans a package and returns the newly parsed package.
* @throws PackageManagerException on a parse error.
*/
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
final int policyFlags, int scanFlags, long currentTime, UserHandle user)
throws PackageManagerException {
// If the package has children and this is the first dive in the function
// we scan the package with the SCAN_CHECK_ONLY flag set to see whether all
// packages (parent and children) would be successfully scanned before the
// actual scan since scanning mutates internal state and we want to atomically
// install the package and its children.
if ((scanFlags & SCAN_CHECK_ONLY) == 0) {
if (pkg.childPackages != null && pkg.childPackages.size() > 0) {
scanFlags |= SCAN_CHECK_ONLY;
}
} else {
scanFlags &= ~SCAN_CHECK_ONLY;
}
// Scan the parent
PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags,
scanFlags, currentTime, user);
// Scan the children
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPackage = pkg.childPackages.get(i);
scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags,
currentTime, user);
}
if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user);
}
return scannedPkg;
}
/**
* Scans a package and returns the newly parsed package.
* @throws PackageManagerException on a parse error.
*/
private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile,
int policyFlags, int scanFlags, long currentTime, UserHandle user)
throws PackageManagerException {
PackageSetting ps = null;
PackageSetting updatedPkg;
// reader
synchronized (mPackages) {
// Look to see if we already know about this package.
String oldName = mSettings.getRenamedPackageLPr(pkg.packageName);
if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
// This package has been renamed to its original name. Let's
// use that.
ps = mSettings.getPackageLPr(oldName);
}
// If there was no original package, see one for the real package name.
if (ps == null) {
ps = mSettings.getPackageLPr(pkg.packageName);
}
// Check to see if this package could be hiding/updating a system
// package. Must look for it either under the original or real
// package name depending on our state.
updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
// If this is a package we don't know about on the system partition, we
// may need to remove disabled child packages on the system partition
// or may need to not add child packages if the parent apk is updated
// on the data partition and no longer defines this child package.
if ((policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {
// If this is a parent package for an updated system app and this system
// app got an OTA update which no longer defines some of the child packages
// we have to prune them from the disabled system packages.
PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);
if (disabledPs != null) {
final int scannedChildCount = (pkg.childPackages != null)
? pkg.childPackages.size() : 0;
final int disabledChildCount = disabledPs.childPackageNames != null
? disabledPs.childPackageNames.size() : 0;
for (int i = 0; i < disabledChildCount; i++) {
String disabledChildPackageName = disabledPs.childPackageNames.get(i);
boolean disabledPackageAvailable = false;
for (int j = 0; j < scannedChildCount; j++) {
PackageParser.Package childPkg = pkg.childPackages.get(j);
if (childPkg.packageName.equals(disabledChildPackageName)) {
disabledPackageAvailable = true;
break;
}
}
if (!disabledPackageAvailable) {
mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);
}
}
}
}
}
boolean updatedPkgBetter = false;
// First check if this is a system package that may involve an update
if (updatedPkg != null && (policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {
// If new package is not located in "/system/priv-app" (e.g. due to an OTA),
// it needs to drop FLAG_PRIVILEGED.
if (locationIsPrivileged(scanFile)) {
updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
} else {
updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
if (ps != null && !ps.codePath.equals(scanFile)) {
// The path has changed from what was last scanned... check the
// version of the new path against what we have stored to determine
// what to do.
if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
if (pkg.mVersionCode <= ps.versionCode) {
// The system package has been updated and the code path does not match
// Ignore entry. Skip it.
if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile
+ " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
if (!updatedPkg.codePath.equals(scanFile)) {
Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg "
+ ps.name + " changing from " + updatedPkg.codePathString
+ " to " + scanFile);
updatedPkg.codePath = scanFile;
updatedPkg.codePathString = scanFile.toString();
updatedPkg.resourcePath = scanFile;
updatedPkg.resourcePathString = scanFile.toString();
}
updatedPkg.pkg = pkg;
updatedPkg.versionCode = pkg.mVersionCode;
// Update the disabled system child packages to point to the package too.
final int childCount = updatedPkg.childPackageNames != null
? updatedPkg.childPackageNames.size() : 0;
for (int i = 0; i < childCount; i++) {
String childPackageName = updatedPkg.childPackageNames.get(i);
PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr(
childPackageName);
if (updatedChildPkg != null) {
updatedChildPkg.pkg = pkg;
updatedChildPkg.versionCode = pkg.mVersionCode;
}
}
throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "
+ scanFile + " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
} else {
// The current app on the system partition is better than
// what we have updated to on the data partition; switch
// back to the system partition version.
// At this point, its safely assumed that package installation for
// apps in system partition will go through. If not there won't be a working
// version of the app
// writer
synchronized (mPackages) {
// Just remove the loaded entries from package lists.
mPackages.remove(ps.name);
}
logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ " reverting from " + ps.codePathString
+ ": new version " + pkg.mVersionCode
+ " better than installed " + ps.versionCode);
InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
synchronized (mInstallLock) {
args.cleanUpResourcesLI();
}
synchronized (mPackages) {
mSettings.enableSystemPackageLPw(ps.name);
}
updatedPkgBetter = true;
}
}
}
if (updatedPkg != null) {
// An updated system app will not have the PARSE_IS_SYSTEM flag set
// initially
policyFlags |= PackageParser.PARSE_IS_SYSTEM;
// An updated privileged app will not have the PARSE_IS_PRIVILEGED
// flag set initially
if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
policyFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
}
// Verify certificates against what was last scanned
collectCertificatesLI(ps, pkg, scanFile, policyFlags);
/*
* A new system app appeared, but we already had a non-system one of the
* same name installed earlier.
*/
boolean shouldHideSystemApp = false;
if (updatedPkg == null && ps != null
&& (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
/*
* Check to make sure the signatures match first. If they don't,
* wipe the installed application and its data.
*/
if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but"
+ " signatures don't match existing userdata copy; removing");
try (PackageFreezer freezer = freezePackage(pkg.packageName,
"scanPackageInternalLI")) {
deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);
}
ps = null;
} else {
/*
* If the newly-added system app is an older version than the
* already installed version, hide it. It will be scanned later
* and re-added like an update.
*/
if (pkg.mVersionCode <= ps.versionCode) {
shouldHideSystemApp = true;
logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile
+ " but new version " + pkg.mVersionCode + " better than installed "
+ ps.versionCode + "; hiding system");
} else {
/*
* The newly found system app is a newer version that the
* one previously installed. Simply remove the
* already-installed application and replace it with our own
* while keeping the application data.
*/
logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ " reverting from " + ps.codePathString + ": new version "
+ pkg.mVersionCode + " better than installed " + ps.versionCode);
InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
synchronized (mInstallLock) {
args.cleanUpResourcesLI();
}
}
}
}
// The apk is forward locked (not public) if its code and resources
// are kept in different files. (except for app in either system or
// vendor path).
// TODO grab this value from PackageSettings
if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
policyFlags |= PackageParser.PARSE_FORWARD_LOCK;
}
}
// TODO: extend to support forward-locked splits
String resourcePath = null;
String baseResourcePath = null;
if ((policyFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !updatedPkgBetter) {
if (ps != null && ps.resourcePathString != null) {
resourcePath = ps.resourcePathString;
baseResourcePath = ps.resourcePathString;
} else {
// Should not happen at all. Just log an error.
Slog.e(TAG, "Resource path not set for package " + pkg.packageName);
}
} else {
resourcePath = pkg.codePath;
baseResourcePath = pkg.baseCodePath;
}
// Set application objects path explicitly.
pkg.setApplicationVolumeUuid(pkg.volumeUuid);
pkg.setApplicationInfoCodePath(pkg.codePath);
pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
pkg.setApplicationInfoResourcePath(resourcePath);
pkg.setApplicationInfoBaseResourcePath(baseResourcePath);
pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
// Note that we invoke the following method only if we are about to unpack an application
PackageParser.Package scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
/*
* If the system app should be overridden by a previously installed
* data, hide the system app now and let the /data/app scan pick it up
* again.
*/
if (shouldHideSystemApp) {
synchronized (mPackages) {
mSettings.disableSystemPackageLPw(pkg.packageName, true);
}
}
return scannedPkg;
}
private void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
// Derive the new package synthetic package name
pkg.setPackageName(pkg.packageName + STATIC_SHARED_LIB_DELIMITER
+ pkg.staticSharedLibVersion);
}
private static String fixProcessName(String defProcessName,
String processName) {
if (processName == null) {
return defProcessName;
}
return processName;
}
private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)
throws PackageManagerException {
if (pkgSetting.signatures.mSignatures != null) {
// Already existing package. Make sure signatures match
boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
== PackageManager.SIGNATURE_MATCH;
if (!match) {
match = compareSignaturesCompat(pkgSetting.signatures, pkg)
== PackageManager.SIGNATURE_MATCH;
}
if (!match) {
match = compareSignaturesRecover(pkgSetting.signatures, pkg)
== PackageManager.SIGNATURE_MATCH;
}
if (!match) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+ pkg.packageName + " signatures do not match the "
+ "previously installed version; ignoring!");
}
}
// Check for shared user signatures
if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
// Already existing package. Make sure signatures match
boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
if (!match) {
match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
== PackageManager.SIGNATURE_MATCH;
}
if (!match) {
match = compareSignaturesRecover(pkgSetting.sharedUser.signatures, pkg)
== PackageManager.SIGNATURE_MATCH;
}
if (!match) {
throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
"Package " + pkg.packageName
+ " has no signatures that match those in shared user "
+ pkgSetting.sharedUser.name + "; ignoring!");
}
}
}
/**
* Enforces that only the system UID or root's UID can call a method exposed
* via Binder.
*
* @param message used as message if SecurityException is thrown
* @throws SecurityException if the caller is not system or root
*/
private static final void enforceSystemOrRoot(String message) {
final int uid = Binder.getCallingUid();
if (uid != Process.SYSTEM_UID && uid != 0) {
throw new SecurityException(message);
}
}
@Override
public void performFstrimIfNeeded() {
enforceSystemOrRoot("Only the system can request fstrim");
// Before everything else, see whether we need to fstrim.
try {
IStorageManager sm = PackageHelper.getStorageManager();
if (sm != null) {
boolean doTrim = false;
final long interval = android.provider.Settings.Global.getLong(
mContext.getContentResolver(),
android.provider.Settings.Global.FSTRIM_MANDATORY_INTERVAL,
DEFAULT_MANDATORY_FSTRIM_INTERVAL);
if (interval > 0) {
final long timeSinceLast = System.currentTimeMillis() - sm.lastMaintenance();
if (timeSinceLast > interval) {
doTrim = true;
Slog.w(TAG, "No disk maintenance in " + timeSinceLast
+ "; running immediately");
}
}
if (doTrim) {
final boolean dexOptDialogShown;
synchronized (mPackages) {
dexOptDialogShown = mDexOptDialogShown;
}
if (!isFirstBoot() && dexOptDialogShown) {
try {
ActivityManager.getService().showBootMessage(
mContext.getResources().getString(
R.string.android_upgrading_fstrim), true);
} catch (RemoteException e) {
}
}
sm.runMaintenance();
}
} else {
Slog.e(TAG, "storageManager service unavailable!");
}
} catch (RemoteException e) {
// Can't happen; StorageManagerService is local
}
}
@Override
public void updatePackagesIfNeeded() {
enforceSystemOrRoot("Only the system can request package update");
// We need to re-extract after an OTA.
boolean causeUpgrade = isUpgrade();
// First boot or factory reset.
// Note: we also handle devices that are upgrading to N right now as if it is their
// first boot, as they do not have profile data.
boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;
// We need to re-extract after a pruned cache, as AoT-ed files will be out of date.
boolean causePrunedCache = VMRuntime.didPruneDalvikCache();
if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {
return;
}
List pkgs;
synchronized (mPackages) {
pkgs = PackageManagerServiceUtils.getPackagesForDexopt(mPackages.values(), this);
}
final long startTime = System.nanoTime();
final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
getCompilerFilterForReason(causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT));
final int elapsedTimeSeconds =
(int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
MetricsLogger.histogram(mContext, "opt_dialog_num_dexopted", stats[0]);
MetricsLogger.histogram(mContext, "opt_dialog_num_skipped", stats[1]);
MetricsLogger.histogram(mContext, "opt_dialog_num_failed", stats[2]);
MetricsLogger.histogram(mContext, "opt_dialog_num_total", getOptimizablePackages().size());
MetricsLogger.histogram(mContext, "opt_dialog_time_s", elapsedTimeSeconds);
}
/**
* Performs dexopt on the set of packages in {@code packages} and returns an int array
* containing statistics about the invocation. The array consists of three elements,
* which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
* and {@code numberOfPackagesFailed}.
*/
private int[] performDexOptUpgrade(List pkgs, boolean showDialog,
String compilerFilter) {
int numberOfPackagesVisited = 0;
int numberOfPackagesOptimized = 0;
int numberOfPackagesSkipped = 0;
int numberOfPackagesFailed = 0;
final int numberOfPackagesToDexopt = pkgs.size();
for (PackageParser.Package pkg : pkgs) {
numberOfPackagesVisited++;
if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Skipping update of of non-optimizable app " + pkg.packageName);
}
numberOfPackagesSkipped++;
continue;
}
if (DEBUG_DEXOPT) {
Log.i(TAG, "Updating app " + numberOfPackagesVisited + " of " +
numberOfPackagesToDexopt + ": " + pkg.packageName);
}
if (showDialog) {
try {
ActivityManager.getService().showBootMessage(
mContext.getResources().getString(R.string.android_upgrading_apk,
numberOfPackagesVisited, numberOfPackagesToDexopt), true);
} catch (RemoteException e) {
}
synchronized (mPackages) {
mDexOptDialogShown = true;
}
}
// If the OTA updates a system app which was previously preopted to a non-preopted state
// the app might end up being verified at runtime. That's because by default the apps
// are verify-profile but for preopted apps there's no profile.
// Do a hacky check to ensure that if we have no profiles (a reasonable indication
// that before the OTA the app was preopted) the app gets compiled with a non-profile
// filter (by default interpret-only).
// Note that at this stage unused apps are already filtered.
if (isSystemApp(pkg) &&
DexFile.isProfileGuidedCompilerFilter(compilerFilter) &&
!Environment.getReferenceProfile(pkg.packageName).exists()) {
compilerFilter = getNonProfileGuidedCompilerFilter(compilerFilter);
}
// checkProfiles is false to avoid merging profiles during boot which
// might interfere with background compilation (b/28612421).
// Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
// behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
// trade-off worth doing to save boot time work.
int dexOptStatus = performDexOptTraced(pkg.packageName,
false /* checkProfiles */,
compilerFilter,
false /* force */);
switch (dexOptStatus) {
case PackageDexOptimizer.DEX_OPT_PERFORMED:
numberOfPackagesOptimized++;
break;
case PackageDexOptimizer.DEX_OPT_SKIPPED:
numberOfPackagesSkipped++;
break;
case PackageDexOptimizer.DEX_OPT_FAILED:
numberOfPackagesFailed++;
break;
default:
Log.e(TAG, "Unexpected dexopt return code " + dexOptStatus);
break;
}
}
return new int[] { numberOfPackagesOptimized, numberOfPackagesSkipped,
numberOfPackagesFailed };
}
@Override
public void notifyPackageUse(String packageName, int reason) {
synchronized (mPackages) {
PackageParser.Package p = mPackages.get(packageName);
if (p == null) {
return;
}
p.mLastPackageUsageTimeInMills[reason] = System.currentTimeMillis();
}
}
@Override
public void notifyDexLoad(String loadingPackageName, List dexPaths, String loaderIsa) {
int userId = UserHandle.getCallingUserId();
ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
if (ai == null) {
Slog.w(TAG, "Loading a package that does not exist for the calling user. package="
+ loadingPackageName + ", user=" + userId);
return;
}
mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId);
}
// TODO: this is not used nor needed. Delete it.
@Override
public boolean performDexOptIfNeeded(String packageName) {
int dexOptStatus = performDexOptTraced(packageName,
false /* checkProfiles */, getFullCompilerFilter(), false /* force */);
return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
}
@Override
public boolean performDexOpt(String packageName,
boolean checkProfiles, int compileReason, boolean force) {
int dexOptStatus = performDexOptTraced(packageName, checkProfiles,
getCompilerFilterForReason(compileReason), force);
return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
}
@Override
public boolean performDexOptMode(String packageName,
boolean checkProfiles, String targetCompilerFilter, boolean force) {
int dexOptStatus = performDexOptTraced(packageName, checkProfiles,
targetCompilerFilter, force);
return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
}
private int performDexOptTraced(String packageName,
boolean checkProfiles, String targetCompilerFilter, boolean force) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
try {
return performDexOptInternal(packageName, checkProfiles,
targetCompilerFilter, force);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
// Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
// if the package can now be considered up to date for the given filter.
private int performDexOptInternal(String packageName,
boolean checkProfiles, String targetCompilerFilter, boolean force) {
PackageParser.Package p;
synchronized (mPackages) {
p = mPackages.get(packageName);
if (p == null) {
// Package could not be found. Report failure.
return PackageDexOptimizer.DEX_OPT_FAILED;
}
mPackageUsage.maybeWriteAsync(mPackages);
mCompilerStats.maybeWriteAsync();
}
long callingId = Binder.clearCallingIdentity();
try {
synchronized (mInstallLock) {
return performDexOptInternalWithDependenciesLI(p, checkProfiles,
targetCompilerFilter, force);
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
public ArraySet getOptimizablePackages() {
ArraySet pkgs = new ArraySet();
synchronized (mPackages) {
for (PackageParser.Package p : mPackages.values()) {
if (PackageDexOptimizer.canOptimizePackage(p)) {
pkgs.add(p.packageName);
}
}
}
return pkgs;
}
private int performDexOptInternalWithDependenciesLI(PackageParser.Package p,
boolean checkProfiles, String targetCompilerFilter,
boolean force) {
// Select the dex optimizer based on the force parameter.
// Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
// allocate an object here.
PackageDexOptimizer pdo = force
? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
: mPackageDexOptimizer;
// Optimize all dependencies first. Note: we ignore the return value and march on
// on errors.
Collection deps = findSharedNonSystemLibraries(p);
final String[] instructionSets = getAppDexInstructionSets(p.applicationInfo);
if (!deps.isEmpty()) {
for (PackageParser.Package depPackage : deps) {
// TODO: Analyze and investigate if we (should) profile libraries.
// Currently this will do a full compilation of the library by default.
pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
false /* checkProfiles */,
getCompilerFilterForReason(REASON_NON_SYSTEM_LIBRARY),
getOrCreateCompilerPackageStats(depPackage));
}
}
return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
targetCompilerFilter, getOrCreateCompilerPackageStats(p));
}
// Performs dexopt on the used secondary dex files belonging to the given package.
// Returns true if all dex files were process successfully (which could mean either dexopt or
// skip). Returns false if any of the files caused errors.
@Override
public boolean performDexOptSecondary(String packageName, String compilerFilter,
boolean force) {
return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force);
}
/**
* Reconcile the information we have about the secondary dex files belonging to
* {@code packagName} and the actual dex files. For all dex files that were
* deleted, update the internal records and delete the generated oat files.
*/
@Override
public void reconcileSecondaryDexFiles(String packageName) {
mDexManager.reconcileSecondaryDexFiles(packageName);
}
// TODO(calin): this is only needed for BackgroundDexOptService. Find a cleaner way to inject
// a reference there.
/*package*/ DexManager getDexManager() {
return mDexManager;
}
/**
* Execute the background dexopt job immediately.
*/
@Override
public boolean runBackgroundDexoptJob() {
return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext);
}
List findSharedNonSystemLibraries(PackageParser.Package p) {
if (p.usesLibraries != null || p.usesOptionalLibraries != null
|| p.usesStaticLibraries != null) {
ArrayList retValue = new ArrayList<>();
Set collectedNames = new HashSet<>();
findSharedNonSystemLibrariesRecursive(p, retValue, collectedNames);
retValue.remove(p);
return retValue;
} else {
return Collections.emptyList();
}
}
private void findSharedNonSystemLibrariesRecursive(PackageParser.Package p,
ArrayList collected, Set collectedNames) {
if (!collectedNames.contains(p.packageName)) {
collectedNames.add(p.packageName);
collected.add(p);
if (p.usesLibraries != null) {
findSharedNonSystemLibrariesRecursive(p.usesLibraries,
null, collected, collectedNames);
}
if (p.usesOptionalLibraries != null) {
findSharedNonSystemLibrariesRecursive(p.usesOptionalLibraries,
null, collected, collectedNames);
}
if (p.usesStaticLibraries != null) {
findSharedNonSystemLibrariesRecursive(p.usesStaticLibraries,
p.usesStaticLibrariesVersions, collected, collectedNames);
}
}
}
private void findSharedNonSystemLibrariesRecursive(ArrayList