| /* |
| * 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_PACKAGE_NAME; |
| import static android.content.Intent.EXTRA_REASON; |
| import static android.content.Intent.EXTRA_USER_ID; |
| 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.ActivityManagerInternal; |
| 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.OverlayManagerTransaction.Request; |
| import android.content.om.OverlayableInfo; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageManagerInternal; |
| import android.content.pm.UserInfo; |
| import android.content.pm.UserPackage; |
| import android.content.pm.overlay.OverlayPaths; |
| import android.content.res.ApkAssets; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Bundle; |
| 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.EventLog; |
| 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.KnownPackages; |
| import com.android.server.pm.UserManagerService; |
| import com.android.server.pm.pkg.PackageState; |
| |
| 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.Iterator; |
| 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(); |
| |
| // Wipe all shell overlays on boot, to recover from a potentially broken device |
| String shellPkgName = TextUtils.emptyIfNull( |
| getContext().getString(android.R.string.config_systemShell)); |
| mSettings.removeIf(overlayInfo -> overlayInfo.isFabricated |
| && shellPkgName.equals(overlayInfo.packageName)); |
| |
| initIfNeeded(); |
| onStartUser(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 onStartUser(). 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 onUserStarting(TargetUser user) { |
| onStartUser(user.getUserIdentifier()); |
| } |
| |
| private void onStartUser(@UserIdInt int newUserId) { |
| try { |
| traceBegin(TRACE_TAG_RRO, "OMS#onStartUser " + 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 boolean systemUpdateUninstall = |
| intent.getBooleanExtra(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, 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, systemUpdateUninstall, 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) { |
| var packageState = mPackageManager.onPackageAdded(packageName, userId); |
| if (packageState != 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) { |
| var packageState = mPackageManager.onPackageUpdated(packageName, userId); |
| if (packageState != 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, |
| boolean systemUpdateUninstall, @NonNull final int[] userIds) { |
| try { |
| traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName); |
| for (int userId : userIds) { |
| synchronized (mLock) { |
| var packageState = mPackageManager.onPackageUpdated(packageName, userId); |
| if (packageState != null && !mPackageManager.isInstantApp(packageName, |
| userId)) { |
| try { |
| updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName, |
| systemUpdateUninstall, 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) { |
| var packageState = mPackageManager.onPackageUpdated(packageName, userId); |
| if (packageState != 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 || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : "")); |
| } |
| } finally { |
| traceEnd(TRACE_TAG_RRO); |
| } |
| } |
| |
| private Set<UserPackage> 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); |
| } |
| |
| // Normal apps are blocked from accessing OMS via SELinux, so to block non-root, |
| // non privileged callers, a simple check against the shell UID is sufficient, since |
| // that's the only exception from the other categories. This is enough while OMS |
| // is not a public API, but this will have to be changed if it's ever exposed. |
| if (callingUid == Process.SHELL_UID) { |
| EventLog.writeEvent(0x534e4554, "202768292", -1, ""); |
| throw new IllegalArgumentException("Non-root shell cannot fabricate overlays"); |
| } |
| |
| 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<UserPackage> 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 |
| , android.os.FabricatedOverlayInternal.class); |
| 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<UserPackage> affectedPackagesToUpdate = null; |
| for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) { |
| Request request = it.next(); |
| 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 ("-a".equals(opt)) { |
| // dumpsys will pass in -a; silently ignore it |
| } else 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 PackageStateUsers { |
| private PackageState mPackageState; |
| private final Set<Integer> mInstalledUsers = new ArraySet<>(); |
| private PackageStateUsers(@NonNull PackageState packageState) { |
| this.mPackageState = packageState; |
| } |
| } |
| 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, PackageStateUsers> 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, PackageState> initializeForUser(final int userId) { |
| if (!mInitializedUsers.contains(userId)) { |
| mInitializedUsers.add(userId); |
| mPackageManagerInternal.forEachPackageState((packageState -> { |
| if (packageState.getPkg() != null |
| && packageState.getUserStateOrDefault(userId).isInstalled()) { |
| addPackageUser(packageState, userId); |
| } |
| })); |
| } |
| |
| final ArrayMap<String, PackageState> userPackages = new ArrayMap<>(); |
| for (int i = 0, n = mCache.size(); i < n; i++) { |
| final PackageStateUsers pkg = mCache.valueAt(i); |
| if (pkg.mInstalledUsers.contains(userId)) { |
| userPackages.put(mCache.keyAt(i), pkg.mPackageState); |
| } |
| } |
| return userPackages; |
| } |
| |
| @Override |
| @Nullable |
| public PackageState getPackageStateForUser(@NonNull final String packageName, |
| final int userId) { |
| final PackageStateUsers pkg = mCache.get(packageName); |
| if (pkg != null && pkg.mInstalledUsers.contains(userId)) { |
| return pkg.mPackageState; |
| } |
| 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 PackageState addPackageUser(@NonNull final String packageName, |
| final int user) { |
| final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(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 PackageState addPackageUser(@NonNull final PackageState pkg, |
| final int user) { |
| PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName()); |
| if (pkgUsers == null) { |
| pkgUsers = new PackageStateUsers(pkg); |
| mCache.put(pkg.getPackageName(), pkgUsers); |
| } else { |
| pkgUsers.mPackageState = pkg; |
| } |
| pkgUsers.mInstalledUsers.add(user); |
| return pkgUsers.mPackageState; |
| } |
| |
| |
| @NonNull |
| private void removePackageUser(@NonNull final String packageName, final int user) { |
| final PackageStateUsers pkgUsers = mCache.get(packageName); |
| if (pkgUsers == null) { |
| return; |
| } |
| removePackageUser(pkgUsers, user); |
| } |
| |
| @NonNull |
| private void removePackageUser(@NonNull final PackageStateUsers pkg, final int user) { |
| pkg.mInstalledUsers.remove(user); |
| if (pkg.mInstalledUsers.isEmpty()) { |
| mCache.remove(pkg.mPackageState.getPackageName()); |
| } |
| } |
| |
| @Nullable |
| public PackageState onPackageAdded(@NonNull final String packageName, final int userId) { |
| return addPackageUser(packageName, userId); |
| } |
| |
| @Nullable |
| public PackageState 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, userId) == SIGNATURE_MATCH; |
| } catch (RemoteException e) { |
| // Intentionally left blank |
| } |
| return false; |
| } |
| |
| @Override |
| public String getConfigSignaturePackage() { |
| final String[] pkgs = mPackageManagerInternal.getKnownPackageNames( |
| KnownPackages.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 { |
| var packageState = getPackageStateForUser(packageName, userId); |
| var pkg = packageState == null ? null : packageState.getAndroidPackage(); |
| if (pkg == null) { |
| throw new IOException("Unable to get target package"); |
| } |
| |
| ApkAssets apkAssets = null; |
| try { |
| apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath()); |
| return apkAssets.getOverlayableInfo(targetOverlayableName); |
| } finally { |
| if (apkAssets != null) { |
| try { |
| apkAssets.close(); |
| } catch (Throwable ignored) { |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean doesTargetDefineOverlayable(String targetPackageName, int userId) |
| throws IOException { |
| var packageState = getPackageStateForUser(targetPackageName, userId); |
| var pkg = packageState == null ? null : packageState.getAndroidPackage(); |
| if (pkg == null) { |
| throw new IOException("Unable to get target package"); |
| } |
| |
| ApkAssets apkAssets = null; |
| try { |
| apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath()); |
| 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 PackageStateUsers pkg = mCache.valueAt(i); |
| pw.print(TAB1 + packageName + ": " + pkg.mPackageState + " users="); |
| pw.println(TextUtils.join(", ", pkg.mInstalledUsers)); |
| } |
| } |
| } |
| |
| private void updateTargetPackagesLocked(@Nullable UserPackage updatedTarget) { |
| if (updatedTarget != null) { |
| updateTargetPackagesLocked(Set.of(updatedTarget)); |
| } |
| } |
| |
| private void updateTargetPackagesLocked(@Nullable Set<UserPackage> 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<UserPackage> 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) { |
| final ActivityManagerInternal amInternal = |
| LocalServices.getService(ActivityManagerInternal.class); |
| 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); |
| intent.putExtra(EXTRA_PACKAGE_NAME, target); |
| intent.putExtra(EXTRA_USER_ID, userId); |
| amInternal.broadcastIntent(intent, null /* resultTo */, null /* requiredPermissions */, |
| false /* serialized */, userId, null /* appIdAllowList */, |
| OverlayManagerService::filterReceiverAccess, null /* bOptions */); |
| }); |
| } |
| |
| /** |
| * A callback from the broadcast queue to determine whether the intent |
| * {@link Intent#ACTION_OVERLAY_CHANGED} is visible to the receiver. |
| * |
| * @param callingUid The receiver's uid. |
| * @param extras The extras of intent that contains {@link Intent#EXTRA_PACKAGE_NAME} and |
| * {@link Intent#EXTRA_USER_ID} to check. |
| * @return {@code null} if the intent is not visible to the receiver. |
| */ |
| @Nullable |
| private static Bundle filterReceiverAccess(int callingUid, @NonNull Bundle extras) { |
| final String packageName = extras.getString(EXTRA_PACKAGE_NAME); |
| final int userId = extras.getInt(EXTRA_USER_ID); |
| if (LocalServices.getService(PackageManagerInternal.class).filterAppAccess( |
| packageName, callingUid, userId, false /* filterUninstalled */)) { |
| return null; |
| } |
| return extras; |
| } |
| |
| /** |
| * 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<UserPackage> 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 ArrayMap<String, OverlayPaths> pendingChanges = |
| new ArrayMap<>(targetPackageNames.size()); |
| synchronized (mLock) { |
| final OverlayPaths frameworkOverlays = |
| mImpl.getEnabledOverlayPaths("android", userId, false); |
| for (final String targetPackageName : targetPackageNames) { |
| final OverlayPaths.Builder list = new OverlayPaths.Builder(); |
| list.addAll(frameworkOverlays); |
| if (!"android".equals(targetPackageName)) { |
| list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true)); |
| } |
| pendingChanges.put(targetPackageName, list.build()); |
| } |
| } |
| |
| final HashSet<String> updatedPackages = new HashSet<>(); |
| final HashSet<String> invalidPackages = new HashSet<>(); |
| pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages); |
| |
| for (final String targetPackageName : targetPackageNames) { |
| if (DEBUG) { |
| Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=[" |
| + pendingChanges.get(targetPackageName) |
| + "] userId=" + userId); |
| } |
| |
| if (invalidPackages.contains(targetPackageName)) { |
| Slog.e(TAG, TextUtils.formatSimple( |
| "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); |
| } |
| } |
| } |