blob: 27b16483057220376e87cb9abceea02a03ad5380 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.om;
import static android.app.AppGlobals.getPackageManager;
import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_REASON;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
import static android.content.pm.PackageManager.SIGNATURE_MATCH;
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
import static android.os.Trace.traceEnd;
import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.om.IOverlayManager;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManagerTransaction;
import android.content.om.OverlayableInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.pm.overlay.OverlayPaths;
import android.content.res.ApkAssets;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.FabricatedOverlayInternal;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Service to manage asset overlays.
*
* <p>Asset overlays are additional resources that come from apks loaded
* alongside the system and app apks. This service, the OverlayManagerService
* (OMS), tracks which installed overlays to use and provides methods to change
* this. Changes propagate to running applications as part of the Activity
* lifecycle. This allows Activities to reread their resources at a well
* defined point.</p>
*
* <p>By itself, the OMS will not change what overlays should be active.
* Instead, it is only responsible for making sure that overlays *can* be used
* from a technical and security point of view and to activate overlays in
* response to external requests. The responsibility to toggle overlays on and
* off lies within components that implement different use-cases such as themes
* or dynamic customization.</p>
*
* <p>The OMS receives input from three sources:</p>
*
* <ul>
* <li>Callbacks from the SystemService class, specifically when the
* Android framework is booting and when the end user switches Android
* users.</li>
*
* <li>Intents from the PackageManagerService (PMS). Overlays are regular
* apks, and whenever a package is installed (or removed, or has a
* component enabled or disabled), the PMS broadcasts this as an intent.
* When the OMS receives one of these intents, it updates its internal
* representation of the available overlays and, if there was a visible
* change, triggers an asset refresh in the affected apps.</li>
*
* <li>External requests via the {@link IOverlayManager AIDL interface}.
* The interface allows clients to read information about the currently
* available overlays, change whether an overlay should be used or not, and
* change the relative order in which overlay packages are loaded.
* Read-access is granted if the request targets the same Android user as
* the caller runs as, or if the caller holds the
* INTERACT_ACROSS_USERS_FULL permission. Write-access is granted if the
* caller is granted read-access and additionaly holds the
* CHANGE_OVERLAY_PACKAGES permission.</li>
* </ul>
*
* <p>The AIDL interface works with String package names, int user IDs, and
* {@link OverlayInfo} objects. OverlayInfo instances are used to track a
* specific pair of target and overlay packages and include information such as
* the current state of the overlay. OverlayInfo objects are immutable.</p>
*
* <p>Internally, OverlayInfo objects are maintained by the
* OverlayManagerSettings class. The OMS and its helper classes are notified of
* changes to the settings by the OverlayManagerSettings.ChangeListener
* callback interface. The file /data/system/overlays.xml is used to persist
* the settings.</p>
*
* <p>Creation and deletion of idmap files are handled by the IdmapManager
* class.</p>
*
* <p>The following is an overview of OMS and its related classes. Note how box
* (2) does the heavy lifting, box (1) interacts with the Android framework,
* and box (3) replaces box (1) during unit testing.</p>
*
* <pre>
* Android framework
* | ^
* . . . | . . . . | . . . .
* . | | .
* . AIDL, broadcasts .
* . intents | .
* . | | . . . . . . . . . . . .
* . v | . .
* . OverlayManagerService . OverlayManagerTests .
* . \ . / .
* . (1) \ . / (3) .
* . . . . . . . . . . \ . . . / . . . . . . . . .
* . \ / .
* . (2) \ / .
* . OverlayManagerServiceImpl .
* . | | .
* . | | .
* . OverlayManagerSettings IdmapManager .
* . .
* . . . . . . . . . . . . . . . . . . . . . .
* </pre>
*
* <p>To test the OMS, execute:
* <code>
* atest FrameworksServicesTests:com.android.server.om # internal tests
* atest OverlayDeviceTests OverlayHostTests # public API tests
* </code>
* </p>
*
* <p>Finally, here is a list of keywords used in the OMS context.</p>
*
* <ul>
* <li><b>target [package]</b> -- A regular apk that may have its resource
* pool extended by zero or more overlay packages.</li>
*
* <li><b>overlay [package]</b> -- An apk that provides additional
* resources to another apk.</li>
*
* <li><b>OMS</b> -- The OverlayManagerService, i.e. this class.</li>
*
* <li><b>approved</b> -- An overlay is approved if the OMS has verified
* that it can be used technically speaking (its target package is
* installed, at least one resource name in both packages match, the
* idmap was created, etc) and that it is secure to do so. External
* clients can not change this state.</li>
*
* <li><b>not approved</b> -- The opposite of approved.</li>
*
* <li><b>enabled</b> -- An overlay currently in active use and thus part
* of resource lookups. This requires the overlay to be approved. Only
* external clients can change this state.</li>
*
* <li><b>disabled</b> -- The opposite of enabled.</li>
*
* <li><b>idmap</b> -- A mapping of resource IDs between target and overlay
* used during resource lookup. Also the name of the binary that creates
* the mapping.</li>
* </ul>
*/
public final class OverlayManagerService extends SystemService {
static final String TAG = "OverlayManager";
static final boolean DEBUG = false;
/**
* The system property that specifies the default overlays to apply.
* This is a semicolon separated list of package names.
*
* Ex: com.android.vendor.overlay_one;com.android.vendor.overlay_two
*/
private static final String DEFAULT_OVERLAYS_PROP = "ro.boot.vendor.overlay.theme";
private final Object mLock = new Object();
private final AtomicFile mSettingsFile;
private final PackageManagerHelperImpl mPackageManager;
private final UserManagerService mUserManager;
private final OverlayManagerSettings mSettings;
private final OverlayManagerServiceImpl mImpl;
private final OverlayActorEnforcer mActorEnforcer;
public OverlayManagerService(@NonNull final Context context) {
super(context);
try {
traceBegin(TRACE_TAG_RRO, "OMS#OverlayManagerService");
mSettingsFile = new AtomicFile(
new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays");
mPackageManager = new PackageManagerHelperImpl(context);
mUserManager = UserManagerService.getInstance();
IdmapManager im = new IdmapManager(IdmapDaemon.getInstance(), mPackageManager);
mSettings = new OverlayManagerSettings();
mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
OverlayConfig.getSystemInstance(), getDefaultOverlayPackages());
mActorEnforcer = new OverlayActorEnforcer(mPackageManager);
HandlerThread packageReceiverThread = new HandlerThread(TAG);
packageReceiverThread.start();
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(ACTION_PACKAGE_ADDED);
packageFilter.addAction(ACTION_PACKAGE_CHANGED);
packageFilter.addAction(ACTION_PACKAGE_REMOVED);
packageFilter.addDataScheme("package");
getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL,
packageFilter, null, packageReceiverThread.getThreadHandler());
final IntentFilter userFilter = new IntentFilter();
userFilter.addAction(ACTION_USER_ADDED);
userFilter.addAction(ACTION_USER_REMOVED);
getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL,
userFilter, null, null);
restoreSettings();
initIfNeeded();
onSwitchUser(UserHandle.USER_SYSTEM);
publishBinderService(Context.OVERLAY_SERVICE, mService);
publishLocalService(OverlayManagerService.class, this);
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public void onStart() {
// Intentionally left empty.
}
private void initIfNeeded() {
final UserManager um = getContext().getSystemService(UserManager.class);
final List<UserInfo> users = um.getAliveUsers();
synchronized (mLock) {
final int userCount = users.size();
for (int i = 0; i < userCount; i++) {
final UserInfo userInfo = users.get(i);
if (!userInfo.supportsSwitchTo() && userInfo.id != UserHandle.USER_SYSTEM) {
// Initialize any users that can't be switched to, as their state would
// never be setup in onSwitchUser(). We will switch to the system user right
// after this, and its state will be setup there.
updatePackageManagerLocked(mImpl.updateOverlaysForUser(users.get(i).id));
}
}
}
}
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
onSwitchUser(to.getUserIdentifier());
}
private void onSwitchUser(@UserIdInt int newUserId) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onSwitchUser " + newUserId);
// ensure overlays in the settings are up-to-date, and propagate
// any asset changes to the rest of the system
synchronized (mLock) {
updateTargetPackagesLocked(mImpl.updateOverlaysForUser(newUserId));
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
private static String[] getDefaultOverlayPackages() {
final String str = SystemProperties.get(DEFAULT_OVERLAYS_PROP);
if (TextUtils.isEmpty(str)) {
return EmptyArray.STRING;
}
final ArraySet<String> defaultPackages = new ArraySet<>();
for (String packageName : str.split(";")) {
if (!TextUtils.isEmpty(packageName)) {
defaultPackages.add(packageName);
}
}
return defaultPackages.toArray(new String[defaultPackages.size()]);
}
private final class PackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
final String action = intent.getAction();
if (action == null) {
Slog.e(TAG, "Cannot handle package broadcast with null action");
return;
}
final Uri data = intent.getData();
if (data == null) {
Slog.e(TAG, "Cannot handle package broadcast with null data");
return;
}
final String packageName = data.getSchemeSpecificPart();
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
final int[] userIds;
final int extraUid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
if (extraUid == UserHandle.USER_NULL) {
userIds = mUserManager.getUserIds();
} else {
userIds = new int[] { UserHandle.getUserId(extraUid) };
}
switch (action) {
case ACTION_PACKAGE_ADDED:
if (replacing) {
onPackageReplaced(packageName, userIds);
} else {
onPackageAdded(packageName, userIds);
}
break;
case ACTION_PACKAGE_CHANGED:
// ignore the intent if it was sent by the package manager as a result of the
// overlay manager having sent ACTION_OVERLAY_CHANGED
if (!ACTION_OVERLAY_CHANGED.equals(intent.getStringExtra(EXTRA_REASON))) {
onPackageChanged(packageName, userIds);
}
break;
case ACTION_PACKAGE_REMOVED:
if (replacing) {
onPackageReplacing(packageName, userIds);
} else {
onPackageRemoved(packageName, userIds);
}
break;
default:
// do nothing
break;
}
}
private void onPackageAdded(@NonNull final String packageName,
@NonNull final int[] userIds) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName);
for (final int userId : userIds) {
synchronized (mLock) {
final AndroidPackage pkg = mPackageManager.onPackageAdded(
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
updateTargetPackagesLocked(
mImpl.onPackageAdded(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageAdded internal error", e);
}
}
}
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
private void onPackageChanged(@NonNull final String packageName,
@NonNull final int[] userIds) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
final AndroidPackage pkg = mPackageManager.onPackageUpdated(
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
updateTargetPackagesLocked(
mImpl.onPackageChanged(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageChanged internal error", e);
}
}
}
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
private void onPackageReplacing(@NonNull final String packageName,
@NonNull final int[] userIds) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
final AndroidPackage pkg = mPackageManager.onPackageUpdated(
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
updateTargetPackagesLocked(
mImpl.onPackageReplacing(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageReplacing internal error", e);
}
}
}
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
private void onPackageReplaced(@NonNull final String packageName,
@NonNull final int[] userIds) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
final AndroidPackage pkg = mPackageManager.onPackageUpdated(
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
updateTargetPackagesLocked(
mImpl.onPackageReplaced(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageReplaced internal error", e);
}
}
}
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
private void onPackageRemoved(@NonNull final String packageName,
@NonNull final int[] userIds) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
mPackageManager.onPackageRemoved(packageName, userId);
updateTargetPackagesLocked(mImpl.onPackageRemoved(packageName, userId));
}
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
}
private final class UserReceiver extends BroadcastReceiver {
@Override
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
switch (intent.getAction()) {
case ACTION_USER_ADDED:
if (userId != UserHandle.USER_NULL) {
try {
traceBegin(TRACE_TAG_RRO, "OMS ACTION_USER_ADDED");
synchronized (mLock) {
updatePackageManagerLocked(mImpl.updateOverlaysForUser(userId));
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
break;
case ACTION_USER_REMOVED:
if (userId != UserHandle.USER_NULL) {
try {
traceBegin(TRACE_TAG_RRO, "OMS ACTION_USER_REMOVED");
synchronized (mLock) {
mImpl.onUserRemoved(userId);
mPackageManager.forgetAllPackageInfos(userId);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
break;
default:
// do nothing
break;
}
}
}
private final IBinder mService = new IOverlayManager.Stub() {
@Override
public Map<String, List<OverlayInfo>> getAllOverlays(final int userIdArg) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#getAllOverlays " + userIdArg);
final int realUserId = handleIncomingUser(userIdArg, "getAllOverlays");
synchronized (mLock) {
return mImpl.getOverlaysForUser(realUserId);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public List<OverlayInfo> getOverlayInfosForTarget(@Nullable final String targetPackageName,
final int userIdArg) {
if (targetPackageName == null) {
return Collections.emptyList();
}
try {
traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfosForTarget " + targetPackageName);
final int realUserId = handleIncomingUser(userIdArg, "getOverlayInfosForTarget");
synchronized (mLock) {
return mImpl.getOverlayInfosForTarget(targetPackageName, realUserId);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public OverlayInfo getOverlayInfo(@Nullable final String packageName,
final int userIdArg) {
return getOverlayInfoByIdentifier(new OverlayIdentifier(packageName), userIdArg);
}
@Override
public OverlayInfo getOverlayInfoByIdentifier(@Nullable final OverlayIdentifier overlay,
final int userIdArg) {
if (overlay == null || overlay.getPackageName() == null) {
return null;
}
try {
traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + overlay);
final int realUserId = handleIncomingUser(userIdArg, "getOverlayInfo");
synchronized (mLock) {
return mImpl.getOverlayInfo(overlay, realUserId);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public boolean setEnabled(@Nullable final String packageName, final boolean enable,
int userIdArg) {
if (packageName == null) {
return false;
}
try {
traceBegin(TRACE_TAG_RRO, "OMS#setEnabled " + packageName + " " + enable);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setEnabled");
enforceActor(overlay, "setEnabled", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
updateTargetPackagesLocked(
mImpl.setEnabled(overlay, enable, realUserId));
return true;
} catch (OperationFailedException e) {
return false;
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public boolean setEnabledExclusive(@Nullable final String packageName, final boolean enable,
int userIdArg) {
if (packageName == null || !enable) {
return false;
}
try {
traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusive " + packageName + " " + enable);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setEnabledExclusive");
enforceActor(overlay, "setEnabledExclusive", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setEnabledExclusive(
overlay, false /* withinCategory */, realUserId)
.ifPresent(
OverlayManagerService.this::updateTargetPackagesLocked);
return true;
} catch (OperationFailedException e) {
return false;
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public boolean setEnabledExclusiveInCategory(@Nullable String packageName,
final int userIdArg) {
if (packageName == null) {
return false;
}
try {
traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusiveInCategory " + packageName);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg,
"setEnabledExclusiveInCategory");
enforceActor(overlay, "setEnabledExclusiveInCategory", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setEnabledExclusive(overlay,
true /* withinCategory */, realUserId)
.ifPresent(OverlayManagerService.this::updateTargetPackagesLocked);
return true;
} catch (OperationFailedException e) {
return false;
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public boolean setPriority(@Nullable final String packageName,
@Nullable final String parentPackageName, final int userIdArg) {
if (packageName == null || parentPackageName == null) {
return false;
}
try {
traceBegin(TRACE_TAG_RRO, "OMS#setPriority " + packageName + " "
+ parentPackageName);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final OverlayIdentifier parentOverlay = new OverlayIdentifier(parentPackageName);
final int realUserId = handleIncomingUser(userIdArg, "setPriority");
enforceActor(overlay, "setPriority", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setPriority(overlay, parentOverlay, realUserId)
.ifPresent(OverlayManagerService.this::updateTargetPackagesLocked);
return true;
} catch (OperationFailedException e) {
return false;
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public boolean setHighestPriority(@Nullable final String packageName, final int userIdArg) {
if (packageName == null) {
return false;
}
try {
traceBegin(TRACE_TAG_RRO, "OMS#setHighestPriority " + packageName);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setHighestPriority");
enforceActor(overlay, "setHighestPriority", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
updateTargetPackagesLocked(
mImpl.setHighestPriority(overlay, realUserId));
return true;
} catch (OperationFailedException e) {
return false;
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public boolean setLowestPriority(@Nullable final String packageName, final int userIdArg) {
if (packageName == null) {
return false;
}
try {
traceBegin(TRACE_TAG_RRO, "OMS#setLowestPriority " + packageName);
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setLowestPriority");
enforceActor(overlay, "setLowestPriority", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.setLowestPriority(overlay, realUserId)
.ifPresent(OverlayManagerService.this::updateTargetPackagesLocked);
return true;
} catch (OperationFailedException e) {
return false;
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public String[] getDefaultOverlayPackages() {
try {
traceBegin(TRACE_TAG_RRO, "OMS#getDefaultOverlayPackages");
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MODIFY_THEME_OVERLAY, null);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
return mImpl.getDefaultOverlayPackages();
}
} finally {
Binder.restoreCallingIdentity(ident);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
@Override
public void invalidateCachesForOverlay(@Nullable String packageName, final int userIdArg) {
if (packageName == null) {
return;
}
final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "invalidateCachesForOverlay");
enforceActor(overlay, "invalidateCachesForOverlay", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
mImpl.removeIdmapForOverlay(overlay, realUserId);
} catch (OperationFailedException e) {
Slog.w(TAG, "invalidate caches for overlay '" + overlay + "' failed", e);
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void commit(@NonNull final OverlayManagerTransaction transaction)
throws RemoteException {
try {
traceBegin(TRACE_TAG_RRO, "OMS#commit " + transaction);
try {
executeAllRequests(transaction);
} catch (Exception e) {
final long ident = Binder.clearCallingIdentity();
try {
restoreSettings();
} finally {
Binder.restoreCallingIdentity(ident);
}
Slog.d(TAG, "commit failed: " + e.getMessage(), e);
throw new SecurityException("commit failed"
+ (DEBUG ? ": " + e.getMessage() : ""));
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
private Set<PackageAndUser> executeRequest(
@NonNull final OverlayManagerTransaction.Request request)
throws OperationFailedException {
Objects.requireNonNull(request, "Transaction contains a null request");
Objects.requireNonNull(request.overlay,
"Transaction overlay identifier must be non-null");
final int callingUid = Binder.getCallingUid();
final int realUserId;
if (request.type == TYPE_REGISTER_FABRICATED
|| request.type == TYPE_UNREGISTER_FABRICATED) {
if (request.userId != UserHandle.USER_ALL) {
throw new IllegalArgumentException(request.typeToString()
+ " unsupported for user " + request.userId);
}
realUserId = UserHandle.USER_ALL;
// Enforce that the calling process can only register and unregister fabricated
// overlays using its package name.
final String pkgName = request.overlay.getPackageName();
if (callingUid != Process.ROOT_UID && !ArrayUtils.contains(
mPackageManager.getPackagesForUid(callingUid), pkgName)) {
throw new IllegalArgumentException("UID " + callingUid + " does own package"
+ "name " + pkgName);
}
} else {
// Enforce actor requirements for enabling, disabling, and reordering overlays.
realUserId = handleIncomingUser(request.userId, request.typeToString());
enforceActor(request.overlay, request.typeToString(), realUserId);
}
final long ident = Binder.clearCallingIdentity();
try {
switch (request.type) {
case TYPE_SET_ENABLED:
Set<PackageAndUser> result = null;
result = CollectionUtils.addAll(result,
mImpl.setEnabled(request.overlay, true, realUserId));
result = CollectionUtils.addAll(result,
mImpl.setHighestPriority(request.overlay, realUserId));
return CollectionUtils.emptyIfNull(result);
case TYPE_SET_DISABLED:
return mImpl.setEnabled(request.overlay, false, realUserId);
case TYPE_REGISTER_FABRICATED:
final FabricatedOverlayInternal fabricated =
request.extras.getParcelable(
OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY
);
Objects.requireNonNull(fabricated,
"no fabricated overlay attached to request");
return mImpl.registerFabricatedOverlay(fabricated);
case TYPE_UNREGISTER_FABRICATED:
return mImpl.unregisterFabricatedOverlay(request.overlay);
default:
throw new IllegalArgumentException("unsupported request: " + request);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction)
throws OperationFailedException {
if (DEBUG) {
Slog.d(TAG, "commit " + transaction);
}
if (transaction == null) {
throw new IllegalArgumentException("null transaction");
}
synchronized (mLock) {
// execute the requests (as calling user)
Set<PackageAndUser> affectedPackagesToUpdate = null;
for (final OverlayManagerTransaction.Request request : transaction) {
affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate,
executeRequest(request));
}
// past the point of no return: the entire transaction has been
// processed successfully, we can no longer fail: continue as
// system_server
final long ident = Binder.clearCallingIdentity();
try {
updateTargetPackagesLocked(affectedPackagesToUpdate);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@Override
public void onShellCommand(@NonNull final FileDescriptor in,
@NonNull final FileDescriptor out, @NonNull final FileDescriptor err,
@NonNull final String[] args, @NonNull final ShellCallback callback,
@NonNull final ResultReceiver resultReceiver) {
(new OverlayManagerShellCommand(getContext(), this)).exec(
this, in, out, err, args, callback, resultReceiver);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
final DumpState dumpState = new DumpState();
dumpState.setUserId(UserHandle.USER_ALL);
int opti = 0;
while (opti < args.length) {
final String opt = args[opti];
if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
break;
}
opti++;
if ("-h".equals(opt)) {
pw.println("dump [-h] [--verbose] [--user USER_ID] [[FIELD] PACKAGE]");
pw.println(" Print debugging information about the overlay manager.");
pw.println(" With optional parameter PACKAGE, limit output to the specified");
pw.println(" package. With optional parameter FIELD, limit output to");
pw.println(" the value of that SettingsItem field. Field names are");
pw.println(" case insensitive and out.println the m prefix can be omitted,");
pw.println(" so the following are equivalent: mState, mstate, State, state.");
return;
} else if ("--user".equals(opt)) {
if (opti >= args.length) {
pw.println("Error: user missing argument");
return;
}
try {
dumpState.setUserId(Integer.parseInt(args[opti]));
opti++;
} catch (NumberFormatException e) {
pw.println("Error: user argument is not a number: " + args[opti]);
return;
}
} else if ("--verbose".equals(opt)) {
dumpState.setVerbose(true);
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
}
}
if (opti < args.length) {
final String arg = args[opti];
opti++;
switch (arg) {
case "packagename":
case "userid":
case "targetpackagename":
case "targetoverlayablename":
case "basecodepath":
case "state":
case "isenabled":
case "ismutable":
case "priority":
case "category":
dumpState.setField(arg);
break;
default:
dumpState.setOverlyIdentifier(arg);
break;
}
}
if (dumpState.getPackageName() == null && opti < args.length) {
dumpState.setOverlyIdentifier(args[opti]);
opti++;
}
enforceDumpPermission("dump");
synchronized (mLock) {
mImpl.dump(pw, dumpState);
if (dumpState.getPackageName() == null) {
mPackageManager.dump(pw, dumpState);
}
}
}
/**
* Ensure that the caller has permission to interact with the given userId.
* If the calling user is not the same as the provided user, the caller needs
* to hold the INTERACT_ACROSS_USERS_FULL permission (or be system uid or
* root).
*
* @param userId the user to interact with
* @param message message for any SecurityException
*/
private int handleIncomingUser(final int userId, @NonNull final String message) {
return ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, message, null);
}
/**
* Enforce that the caller holds the DUMP permission (or is system or root).
*
* @param message used as message if SecurityException is thrown
* @throws SecurityException if the permission check fails
*/
private void enforceDumpPermission(@NonNull final String message) {
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, message);
}
private void enforceActor(@NonNull OverlayIdentifier overlay, @NonNull String methodName,
int realUserId) throws SecurityException {
OverlayInfo overlayInfo = mImpl.getOverlayInfo(overlay, realUserId);
if (overlayInfo == null) {
throw new IllegalArgumentException("Unable to retrieve overlay information for "
+ overlay);
}
int callingUid = Binder.getCallingUid();
mActorEnforcer.enforceActor(overlayInfo, methodName, callingUid, realUserId);
}
};
private static final class PackageManagerHelperImpl implements PackageManagerHelper {
private static class AndroidPackageUsers {
private AndroidPackage mPackage;
private final Set<Integer> mInstalledUsers = new ArraySet<>();
private AndroidPackageUsers(@NonNull AndroidPackage pkg) {
this.mPackage = pkg;
}
}
private final Context mContext;
private final IPackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
// Use a cache for performance and for consistency within OMS: because
// additional PACKAGE_* intents may be delivered while we process an
// intent, querying the PackageManagerService for the actual current
// state may lead to contradictions within OMS. Better then to lag
// behind until all pending intents have been processed.
private final ArrayMap<String, AndroidPackageUsers> mCache = new ArrayMap<>();
private final Set<Integer> mInitializedUsers = new ArraySet<>();
PackageManagerHelperImpl(Context context) {
mContext = context;
mPackageManager = getPackageManager();
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
/**
* Initializes the helper for the user. This only needs to be invoked one time before
* packages of this user are queried.
* @param userId the user id to initialize
* @return a map of package name to all packages installed in the user
*/
@NonNull
public ArrayMap<String, AndroidPackage> initializeForUser(final int userId) {
if (!mInitializedUsers.contains(userId)) {
mInitializedUsers.add(userId);
mPackageManagerInternal.forEachInstalledPackage(
(pkg) -> addPackageUser(pkg, userId), userId);
}
final ArrayMap<String, AndroidPackage> userPackages = new ArrayMap<>();
for (int i = 0, n = mCache.size(); i < n; i++) {
final AndroidPackageUsers pkg = mCache.valueAt(i);
if (pkg.mInstalledUsers.contains(userId)) {
userPackages.put(mCache.keyAt(i), pkg.mPackage);
}
}
return userPackages;
}
@Override
@Nullable
public AndroidPackage getPackageForUser(@NonNull final String packageName,
final int userId) {
final AndroidPackageUsers pkg = mCache.get(packageName);
if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
return pkg.mPackage;
}
try {
if (!mPackageManager.isPackageAvailable(packageName, userId)) {
return null;
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed to check availability of package '" + packageName
+ "' for user " + userId, e);
return null;
}
return addPackageUser(packageName, userId);
}
@NonNull
private AndroidPackage addPackageUser(@NonNull final String packageName,
final int user) {
final AndroidPackage pkg = mPackageManagerInternal.getPackage(packageName);
if (pkg == null) {
Slog.w(TAG, "Android package for '" + packageName + "' could not be found;"
+ " continuing as if package was never added", new Throwable());
return null;
}
return addPackageUser(pkg, user);
}
@NonNull
private AndroidPackage addPackageUser(@NonNull final AndroidPackage pkg,
final int user) {
AndroidPackageUsers pkgUsers = mCache.get(pkg.getPackageName());
if (pkgUsers == null) {
pkgUsers = new AndroidPackageUsers(pkg);
mCache.put(pkg.getPackageName(), pkgUsers);
} else {
pkgUsers.mPackage = pkg;
}
pkgUsers.mInstalledUsers.add(user);
return pkgUsers.mPackage;
}
@NonNull
private void removePackageUser(@NonNull final String packageName, final int user) {
final AndroidPackageUsers pkgUsers = mCache.get(packageName);
if (pkgUsers == null) {
return;
}
removePackageUser(pkgUsers, user);
}
@NonNull
private void removePackageUser(@NonNull final AndroidPackageUsers pkg, final int user) {
pkg.mInstalledUsers.remove(user);
if (pkg.mInstalledUsers.isEmpty()) {
mCache.remove(pkg.mPackage.getPackageName());
}
}
@Nullable
public AndroidPackage onPackageAdded(@NonNull final String packageName, final int userId) {
return addPackageUser(packageName, userId);
}
@Nullable
public AndroidPackage onPackageUpdated(@NonNull final String packageName,
final int userId) {
return addPackageUser(packageName, userId);
}
public void onPackageRemoved(@NonNull final String packageName, final int userId) {
removePackageUser(packageName, userId);
}
@Override
public boolean isInstantApp(@NonNull final String packageName, final int userId) {
return mPackageManagerInternal.isInstantApp(packageName, userId);
}
@NonNull
@Override
public Map<String, Map<String, String>> getNamedActors() {
return SystemConfig.getInstance().getNamedActors();
}
@Override
public boolean signaturesMatching(@NonNull final String packageName1,
@NonNull final String packageName2, final int userId) {
// The package manager does not support different versions of packages
// to be installed for different users: ignore userId for now.
try {
return mPackageManager.checkSignatures(
packageName1, packageName2) == SIGNATURE_MATCH;
} catch (RemoteException e) {
// Intentionally left blank
}
return false;
}
@Override
public String getConfigSignaturePackage() {
final String[] pkgs = mPackageManagerInternal.getKnownPackageNames(
PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE,
UserHandle.USER_SYSTEM);
return (pkgs.length == 0) ? null : pkgs[0];
}
@Nullable
@Override
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId)
throws IOException {
final AndroidPackage packageInfo = getPackageForUser(packageName, userId);
if (packageInfo == null) {
throw new IOException("Unable to get target package");
}
ApkAssets apkAssets = null;
try {
apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
return apkAssets.getOverlayableInfo(targetOverlayableName);
} finally {
if (apkAssets != null) {
try {
apkAssets.close();
} catch (Throwable ignored) {
}
}
}
}
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws IOException {
AndroidPackage packageInfo = getPackageForUser(targetPackageName, userId);
if (packageInfo == null) {
throw new IOException("Unable to get target package");
}
ApkAssets apkAssets = null;
try {
apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
return apkAssets.definesOverlayable();
} finally {
if (apkAssets != null) {
try {
apkAssets.close();
} catch (Throwable ignored) {
}
}
}
}
@Override
public void enforcePermission(String permission, String message) throws SecurityException {
mContext.enforceCallingOrSelfPermission(permission, message);
}
public void forgetAllPackageInfos(final int userId) {
// Iterate in reverse order since removing the package in all users will remove the
// package from the cache.
for (int i = mCache.size() - 1; i >= 0; i--) {
removePackageUser(mCache.valueAt(i), userId);
}
}
@Nullable
@Override
public String[] getPackagesForUid(int uid) {
try {
return mPackageManager.getPackagesForUid(uid);
} catch (RemoteException ignored) {
return null;
}
}
private static final String TAB1 = " ";
public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
pw.println("AndroidPackage cache");
if (!dumpState.isVerbose()) {
pw.println(TAB1 + mCache.size() + " package(s)");
return;
}
if (mCache.size() == 0) {
pw.println(TAB1 + "<empty>");
return;
}
for (int i = 0, n = mCache.size(); i < n; i++) {
final String packageName = mCache.keyAt(i);
final AndroidPackageUsers pkg = mCache.valueAt(i);
pw.print(TAB1 + packageName + ": " + pkg.mPackage + " users=");
pw.println(TextUtils.join(", ", pkg.mInstalledUsers));
}
}
}
private void updateTargetPackagesLocked(@Nullable PackageAndUser updatedTarget) {
if (updatedTarget != null) {
updateTargetPackagesLocked(Set.of(updatedTarget));
}
}
private void updateTargetPackagesLocked(@Nullable Set<PackageAndUser> updatedTargets) {
if (CollectionUtils.isEmpty(updatedTargets)) {
return;
}
persistSettingsLocked();
final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(updatedTargets);
for (int i = 0, n = userTargets.size(); i < n; i++) {
final ArraySet<String> targets = userTargets.valueAt(i);
final int userId = userTargets.keyAt(i);
final List<String> affectedPackages = updatePackageManagerLocked(targets, userId);
if (affectedPackages.isEmpty()) {
// The package manager paths are already up-to-date.
continue;
}
FgThread.getHandler().post(() -> {
// Send configuration changed events for all target packages that have been affected
// by overlay state changes.
updateActivityManager(affectedPackages, userId);
// Do not send broadcasts for all affected targets. Overlays targeting the framework
// or shared libraries may cause too many broadcasts to be sent at once.
broadcastActionOverlayChanged(targets, userId);
});
}
}
@Nullable
private static SparseArray<ArraySet<String>> groupTargetsByUserId(
@Nullable final Set<PackageAndUser> targetsAndUsers) {
final SparseArray<ArraySet<String>> userTargets = new SparseArray<>();
CollectionUtils.forEach(targetsAndUsers, target -> {
ArraySet<String> targets = userTargets.get(target.userId);
if (targets == null) {
targets = new ArraySet<>();
userTargets.put(target.userId, targets);
}
targets.add(target.packageName);
});
return userTargets;
}
// Helper methods to update other parts of the system or read/write
// settings: these methods should never call into each other!
private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
final int userId) {
CollectionUtils.forEach(targetPackages, target -> {
final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
Uri.fromParts("package", target, null));
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
try {
ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null,
null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId);
} catch (RemoteException e) {
Slog.e(TAG, "broadcastActionOverlayChanged remote exception", e);
}
});
}
/**
* Tell the activity manager to tell a set of packages to reload their
* resources.
*/
private void updateActivityManager(@NonNull List<String> targetPackageNames, final int userId) {
final IActivityManager am = ActivityManager.getService();
try {
am.scheduleApplicationInfoChanged(targetPackageNames, userId);
} catch (RemoteException e) {
Slog.e(TAG, "updateActivityManager remote exception", e);
}
}
@NonNull
private SparseArray<List<String>> updatePackageManagerLocked(
@Nullable Set<PackageAndUser> targets) {
if (CollectionUtils.isEmpty(targets)) {
return new SparseArray<>();
}
final SparseArray<List<String>> affectedTargets = new SparseArray<>();
final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(targets);
for (int i = 0, n = userTargets.size(); i < n; i++) {
final int userId = userTargets.keyAt(i);
affectedTargets.put(userId, updatePackageManagerLocked(userTargets.valueAt(i), userId));
}
return affectedTargets;
}
/**
* Updates the target packages' set of enabled overlays in PackageManager.
* @return the package names of affected targets (a superset of
* targetPackageNames: the target themselves and shared libraries)
*/
@NonNull
private List<String> updatePackageManagerLocked(@NonNull Collection<String> targetPackageNames,
final int userId) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#updatePackageManagerLocked " + targetPackageNames);
if (DEBUG) {
Slog.d(TAG, "Update package manager about changed overlays");
}
final PackageManagerInternal pm =
LocalServices.getService(PackageManagerInternal.class);
final boolean updateFrameworkRes = targetPackageNames.contains("android");
if (updateFrameworkRes) {
targetPackageNames = pm.getTargetPackageNames(userId);
}
final Map<String, OverlayPaths> pendingChanges =
new ArrayMap<>(targetPackageNames.size());
synchronized (mLock) {
final OverlayPaths frameworkOverlays =
mImpl.getEnabledOverlayPaths("android", userId);
for (final String targetPackageName : targetPackageNames) {
final OverlayPaths.Builder list = new OverlayPaths.Builder();
if (!"android".equals(targetPackageName)) {
list.addAll(frameworkOverlays);
}
list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId));
pendingChanges.put(targetPackageName, list.build());
}
}
final HashSet<String> updatedPackages = new HashSet<>();
for (final String targetPackageName : targetPackageNames) {
if (DEBUG) {
Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
+ pendingChanges.get(targetPackageName)
+ "] userId=" + userId);
}
if (!pm.setEnabledOverlayPackages(
userId, targetPackageName, pendingChanges.get(targetPackageName),
updatedPackages)) {
Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
targetPackageName, userId));
}
}
return new ArrayList<>(updatedPackages);
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
private void persistSettingsLocked() {
if (DEBUG) {
Slog.d(TAG, "Writing overlay settings");
}
FileOutputStream stream = null;
try {
stream = mSettingsFile.startWrite();
mSettings.persist(stream);
mSettingsFile.finishWrite(stream);
} catch (IOException | XmlPullParserException e) {
mSettingsFile.failWrite(stream);
Slog.e(TAG, "failed to persist overlay state", e);
}
}
private void restoreSettings() {
try {
traceBegin(TRACE_TAG_RRO, "OMS#restoreSettings");
synchronized (mLock) {
if (!mSettingsFile.getBaseFile().exists()) {
return;
}
try (FileInputStream stream = mSettingsFile.openRead()) {
mSettings.restore(stream);
// We might have data for dying users if the device was
// restarted before we received USER_REMOVED. Remove data for
// users that will not exist after the system is ready.
final List<UserInfo> liveUsers = mUserManager.getUsers(true /*excludeDying*/);
final int[] liveUserIds = new int[liveUsers.size()];
for (int i = 0; i < liveUsers.size(); i++) {
liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier();
}
Arrays.sort(liveUserIds);
for (int userId : mSettings.getUsers()) {
if (Arrays.binarySearch(liveUserIds, userId) < 0) {
mSettings.removeUser(userId);
}
}
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "failed to restore overlay state", e);
}
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
}