All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.om.OverlayManagerService Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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.
 *
 * 

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.

* *

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.

* *

The OMS receives input from three sources:

* *
    *
  • Callbacks from the SystemService class, specifically when the * Android framework is booting and when the end user switches Android * users.
  • * *
  • 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.
  • * *
  • 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.
  • *
* *

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.

* *

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.

* *

Creation and deletion of idmap files are handled by the IdmapManager * class.

* *

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.

* *
 *         Android framework
 *            |         ^
 *      . . . | . . . . | . . . .
 *     .      |         |       .
 *     .    AIDL,   broadcasts  .
 *     .   intents      |       .
 *     .      |         |       . . . . . . . . . . . .
 *     .      v         |       .                     .
 *     .  OverlayManagerService . OverlayManagerTests .
 *     .                  \     .     /               .
 *     . (1)               \    .    /            (3) .
 *      . . . . . . . . . . \ . . . / . . . . . . . . .
 *     .                     \     /              .
 *     . (2)                  \   /               .
 *     .           OverlayManagerServiceImpl      .
 *     .                  |            |          .
 *     .                  |            |          .
 *     . OverlayManagerSettings     IdmapManager  .
 *     .                                          .
 *     . . . .  . . . . . . . . . . . . . . . . . .
 * 
* *

To test the OMS, execute: * * atest FrameworksServicesTests:com.android.server.om # internal tests * atest OverlayDeviceTests OverlayHostTests # public API tests * *

* *

Finally, here is a list of keywords used in the OMS context.

* *
    *
  • target [package] -- A regular apk that may have its resource * pool extended by zero or more overlay packages.
  • * *
  • overlay [package] -- An apk that provides additional * resources to another apk.
  • * *
  • OMS -- The OverlayManagerService, i.e. this class.
  • * *
  • approved -- 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.
  • * *
  • not approved -- The opposite of approved.
  • * *
  • enabled -- 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.
  • * *
  • disabled -- The opposite of enabled.
  • * *
  • idmap -- A mapping of resource IDs between target and overlay * used during resource lookup. Also the name of the binary that creates * the mapping.
  • *
*/ 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 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 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> 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 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 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 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 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 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 mCache = new ArrayMap<>(); private final Set 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 initializeForUser(final int userId) { if (!mInitializedUsers.contains(userId)) { mInitializedUsers.add(userId); mPackageManagerInternal.forEachInstalledPackage( (pkg) -> addPackageUser(pkg, userId), userId); } final ArrayMap 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> 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 + ""); 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 updatedTargets) { if (CollectionUtils.isEmpty(updatedTargets)) { return; } persistSettingsLocked(); final SparseArray> userTargets = groupTargetsByUserId(updatedTargets); for (int i = 0, n = userTargets.size(); i < n; i++) { final ArraySet targets = userTargets.valueAt(i); final int userId = userTargets.keyAt(i); final List 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> groupTargetsByUserId( @Nullable final Set targetsAndUsers) { final SparseArray> userTargets = new SparseArray<>(); CollectionUtils.forEach(targetsAndUsers, target -> { ArraySet 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 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 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> updatePackageManagerLocked( @Nullable Set targets) { if (CollectionUtils.isEmpty(targets)) { return new SparseArray<>(); } final SparseArray> affectedTargets = new SparseArray<>(); final SparseArray> 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 updatePackageManagerLocked(@NonNull Collection 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 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 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 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); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy