content: /*
   * 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_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.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 android.annotation.NonNull;
  import android.annotation.Nullable;
  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.OverlayInfo;
  import android.content.pm.IPackageManager;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManagerInternal;
  import android.content.pm.UserInfo;
  import android.net.Uri;
  import android.os.Binder;
  import android.os.Environment;
  import android.os.IBinder;
  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.server.FgThread;
  import com.android.server.IoThread;
  import com.android.server.LocalServices;
  import com.android.server.SystemService;
  import com.android.server.pm.Installer;
  import com.android.server.pm.UserManagerService;
  
  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.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.atomic.AtomicBoolean;
  
  /**
   * 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 PackageManagerHelper mPackageManager;
  
      private final UserManagerService mUserManager;
  
      private final OverlayManagerSettings mSettings;
  
      private final OverlayManagerServiceImpl mImpl;
  
      private final AtomicBoolean mPersistSettingsScheduled = new AtomicBoolean(false);
  
      public OverlayManagerService(@NonNull final Context context,
              @NonNull final Installer installer) {
          super(context);
          try {
              traceBegin(TRACE_TAG_RRO, "OMS#OverlayManagerService");
              mSettingsFile = new AtomicFile(
                      new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays");
              mPackageManager = new PackageManagerHelper();
              mUserManager = UserManagerService.getInstance();
              IdmapManager im = new IdmapManager(installer, mPackageManager);
              mSettings = new OverlayManagerSettings();
              mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
                      getDefaultOverlayPackages(), new OverlayChangeListener());
  
              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, null);
  
              final IntentFilter userFilter = new IntentFilter();
              userFilter.addAction(ACTION_USER_ADDED);
              userFilter.addAction(ACTION_USER_REMOVED);
              getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL,
                      userFilter, null, null);
  
              restoreSettings();
  
              initIfNeeded();
              onSwitchUser(UserHandle.USER_SYSTEM);
  
              publishBinderService(Context.OVERLAY_SERVICE, mService);
              publishLocalService(OverlayManagerService.class, this);
          } finally {
              traceEnd(TRACE_TAG_RRO);
          }
      }
  
      @Override
      public void onStart() {
          // Intentionally left empty.
      }
  
      private void initIfNeeded() {
          final UserManager um = getContext().getSystemService(UserManager.class);
          final List<UserInfo> users = um.getUsers(true /*excludeDying*/);
          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 there state would
                      // never be setup in onSwitchUser(). We will switch to the system user right
                      // after this, and its state will be setup there.
                      final List<String> targets = mImpl.updateOverlaysForUser(users.get(i).id);
                      updateOverlayPaths(users.get(i).id, targets);
                  }
              }
          }
      }
  
      @Override
      public void onSwitchUser(final 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) {
                  final List<String> targets = mImpl.updateOverlaysForUser(newUserId);
                  updateAssets(newUserId, targets);
              }
              schedulePersistSettings();
          } finally {
              traceEnd(TRACE_TAG_RRO);
          }
      }
  
      private static String[] getDefaultOverlayPackages() {
          final String str = SystemProperties.get(DEFAULT_OVERLAYS_PROP);
          if (TextUtils.isEmpty(str)) {
              return EmptyArray.STRING;
          }
  
          final ArraySet<String> defaultPackages = new ArraySet<>();
          for (String packageName : str.split(";")) {
              if (!TextUtils.isEmpty(packageName)) {
                  defaultPackages.add(packageName);
              }
          }
          return defaultPackages.toArray(new String[defaultPackages.size()]);
      }
  
      private final class PackageReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
              final String action = intent.getAction();
              if (action == null) {
                  Slog.e(TAG, "Cannot handle package broadcast with null action");
                  return;
              }
              final Uri data = intent.getData();
              if (data == null) {
                  Slog.e(TAG, "Cannot handle package broadcast with null data");
                  return;
              }
              final String packageName = data.getSchemeSpecificPart();
  
              final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
  
              final int[] userIds;
              final int extraUid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
              if (extraUid == UserHandle.USER_NULL) {
                  userIds = mUserManager.getUserIds();
              } else {
                  userIds = new int[] { UserHandle.getUserId(extraUid) };
              }
  
              switch (action) {
                  case ACTION_PACKAGE_ADDED:
                      if (replacing) {
                          onPackageReplaced(packageName, userIds);
                      } else {
                          onPackageAdded(packageName, userIds);
                      }
                      break;
                  case ACTION_PACKAGE_CHANGED:
                      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 PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
                                  false);
                          if (pi != null && !pi.applicationInfo.isInstantApp()) {
                              mPackageManager.cachePackageInfo(packageName, userId, pi);
                              if (pi.isOverlayPackage()) {
                                  mImpl.onOverlayPackageAdded(packageName, userId);
                              } else {
                                  mImpl.onTargetPackageAdded(packageName, userId);
                              }
                          }
                      }
                  }
              } 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 PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
                                  false);
                          if (pi != null && pi.applicationInfo.isInstantApp()) {
                              mPackageManager.cachePackageInfo(packageName, userId, pi);
                              if (pi.isOverlayPackage()) {
                                  mImpl.onOverlayPackageChanged(packageName, userId);
                              }  else {
                                  mImpl.onTargetPackageChanged(packageName, userId);
                              }
                          }
                      }
                  }
              } 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) {
                          mPackageManager.forgetPackageInfo(packageName, userId);
                          final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
                          if (oi != null) {
                              mImpl.onOverlayPackageReplacing(packageName, userId);
                          }
                      }
                  }
              } 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 PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
                                  false);
                          if (pi != null && !pi.applicationInfo.isInstantApp()) {
                              mPackageManager.cachePackageInfo(packageName, userId, pi);
                              if (pi.isOverlayPackage()) {
                                  mImpl.onOverlayPackageReplaced(packageName, userId);
                              } else {
                                  mImpl.onTargetPackageReplaced(packageName, userId);
                              }
                          }
                      }
                  }
              } 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.forgetPackageInfo(packageName, userId);
                          final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
                          if (oi != null) {
                              mImpl.onOverlayPackageRemoved(packageName, userId);
                          } else {
                              mImpl.onTargetPackageRemoved(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");
                              final ArrayList<String> targets;
                              synchronized (mLock) {
                                  targets = mImpl.updateOverlaysForUser(userId);
                              }
                              updateOverlayPaths(userId, targets);
                          } 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(int userId) throws RemoteException {
              try {
                  traceBegin(TRACE_TAG_RRO, "OMS#getAllOverlays " + userId);
                  userId = handleIncomingUser(userId, "getAllOverlays");
  
                  synchronized (mLock) {
                      return mImpl.getOverlaysForUser(userId);
                  }
              } finally {
                  traceEnd(TRACE_TAG_RRO);
              }
          }
  
          @Override
          public List<OverlayInfo> getOverlayInfosForTarget(@Nullable final String targetPackageName,
                  int userId) throws RemoteException {
              try {
                  traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfosForTarget " + targetPackageName);
                  userId = handleIncomingUser(userId, "getOverlayInfosForTarget");
                  if (targetPackageName == null) {
                      return Collections.emptyList();
                  }
  
                  synchronized (mLock) {
                      return mImpl.getOverlayInfosForTarget(targetPackageName, userId);
                  }
              } finally {
                  traceEnd(TRACE_TAG_RRO);
              }
          }
  
          @Override
          public OverlayInfo getOverlayInfo(@Nullable final String packageName,
                  int userId) throws RemoteException {
              try {
                  traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + packageName);
                  userId = handleIncomingUser(userId, "getOverlayInfo");
                  if (packageName == null) {
                      return null;
                  }
  
                  synchronized (mLock) {
                      return mImpl.getOverlayInfo(packageName, userId);
                  }
              } finally {
                  traceEnd(TRACE_TAG_RRO);
              }
          }
  
          @Override
          public boolean setEnabled(@Nullable final String packageName, final boolean enable,
                  int userId) throws RemoteException {
              try {
                  traceBegin(TRACE_TAG_RRO, "OMS#setEnabled " + packageName + " " + enable);
                  enforceChangeOverlayPackagesPermission("setEnabled");
                  userId = handleIncomingUser(userId, "setEnabled");
                  if (packageName == null) {
                      return false;
                  }
  
                  final long ident = Binder.clearCallingIdentity();
                  try {
                      synchronized (mLock) {
                          return mImpl.setEnabled(packageName, enable, userId);
                      }
                  } finally {
                      Binder.restoreCallingIdentity(ident);
                  }
              } finally {
                  traceEnd(TRACE_TAG_RRO);
              }
          }
  
          @Override
          public boolean setEnabledExclusive(@Nullable final String packageName, final boolean enable,
                  int userId) throws RemoteException {
              try {
                  traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusive " + packageName + " " + enable);
                  enforceChangeOverlayPackagesPermission("setEnabledExclusive");
                  userId = handleIncomingUser(userId, "setEnabledExclusive");
                  if (packageName == null || !enable) {
                      return false;
                  }
  
                  final long ident = Binder.clearCallingIdentity();
                  try {
                      synchronized (mLock) {
                          return mImpl.setEnabledExclusive(packageName, false /* withinCategory */,
                                  userId);
                      }
                  } finally {
                      Binder.restoreCallingIdentity(ident);
                  }
              } finally {
                  traceEnd(TRACE_TAG_RRO);
              }
          }
  
          @Override
          public boolean setEnabledExclusiveInCategory(@Nullable String packageName, int userId)
                  throws RemoteException {
              try {
                  traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusiveInCategory " + packageName);
                  enforceChangeOverlayPackagesPermission("setEnabledExclusiveInCategory");
                  userId = handleIncomingUser(userId, "setEnabledExclusiveInCategory");
                  if (packageName == null) {
                      return false;
                  }
  
                  final long ident = Binder.clearCallingIdentity();
                  try {
                      synchronized (mLock) {
                          return mImpl.setEnabledExclusive(packageName, true /* withinCategory */,
                                  userId);
                      }
                  } finally {
                      Binder.restoreCallingIdentity(ident);
                  }
              } finally {
                  traceEnd(TRACE_TAG_RRO);
              }
          }
  
          @Override
          public boolean setPriority(@Nullable final String packageName,
                  @Nullable final String parentPackageName, int userId) throws RemoteException {
              try {
                  traceBegin(TRACE_TAG_RRO, "OMS#setPriority " + packageName + " "
                          + parentPackageName);
                  enforceChangeOverlayPackagesPermission("setPriority");
                  userId = handleIncomingUser(userId, "setPriority");
                  if (packageName == null || parentPackageName == null) {
                      return false;
                  }
  
                  final long ident = Binder.clearCallingIdentity();
                  try {
                      synchronized (mLock) {
                          return mImpl.setPriority(packageName, parentPackageName, userId);
                      }
                  } finally {
                      Binder.restoreCallingIdentity(ident);
                  }
              } finally {
                  traceEnd(TRACE_TAG_RRO);
              }
          }
  
          @Override
          public boolean setHighestPriority(@Nullable final String packageName, int userId)
                  throws RemoteException {
              try {
                  traceBegin(TRACE_TAG_RRO, "OMS#setHighestPriority " + packageName);
                  enforceChangeOverlayPackagesPermission("setHighestPriority");
                  userId = handleIncomingUser(userId, "setHighestPriority");
                  if (packageName == null) {
                      return false;
                  }
  
                  final long ident = Binder.clearCallingIdentity();
                  try {
                      synchronized (mLock) {
                          return mImpl.setHighestPriority(packageName, userId);
                      }
                  } finally {
                      Binder.restoreCallingIdentity(ident);
                  }
              } finally {
                  traceEnd(TRACE_TAG_RRO);
              }
          }
  
          @Override
          public boolean setLowestPriority(@Nullable final String packageName, int userId)
                  throws RemoteException {
              try {
                  traceBegin(TRACE_TAG_RRO, "OMS#setLowestPriority " + packageName);
                  enforceChangeOverlayPackagesPermission("setLowestPriority");
                  userId = handleIncomingUser(userId, "setLowestPriority");
                  if (packageName == null) {
                      return false;
                  }
  
                  final long ident = Binder.clearCallingIdentity();
                  try {
                      synchronized (mLock) {
                          return mImpl.setLowestPriority(packageName, userId);
                      }
                  } finally {
                      Binder.restoreCallingIdentity(ident);
                  }
              } finally {
                  traceEnd(TRACE_TAG_RRO);
              }
          }
  
          @Override
          public String[] getDefaultOverlayPackages() throws RemoteException {
              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 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(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 "isstatic":
                      case "priority":
                      case "category":
                          dumpState.setField(arg);
                          break;
                      default:
                          dumpState.setPackageName(arg);
                          break;
                  }
              }
              if (dumpState.getPackageName() == null && opti < args.length) {
                  dumpState.setPackageName(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 CHANGE_OVERLAY_PACKAGES permission (or is
           * system or root).
           *
           * @param message used as message if SecurityException is thrown
           * @throws SecurityException if the permission check fails
           */
          private void enforceChangeOverlayPackagesPermission(@NonNull final String message) {
              getContext().enforceCallingOrSelfPermission(
                      android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, message);
          }
  
          /**
           * 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 final class OverlayChangeListener
              implements OverlayManagerServiceImpl.OverlayChangeListener {
          @Override
          public void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) {
              schedulePersistSettings();
              FgThread.getHandler().post(() -> {
                  updateAssets(userId, targetPackageName);
  
                  final Intent intent = new Intent(Intent.ACTION_OVERLAY_CHANGED,
                          Uri.fromParts("package", targetPackageName, null));
                  intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
  
                  if (DEBUG) {
                      Slog.d(TAG, "send broadcast " + intent);
                  }
  
                  try {
                      ActivityManager.getService().broadcastIntent(null, intent, null, null, 0,
                              null, null, null, android.app.AppOpsManager.OP_NONE, null, false, false,
                              userId);
                  } catch (RemoteException e) {
                      // Intentionally left empty.
                  }
              });
          }
      }
  
      /**
       * Updates the target packages' set of enabled overlays in PackageManager.
       */
      private void updateOverlayPaths(int userId, List<String> targetPackageNames) {
          try {
              traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
              if (DEBUG) {
                  Slog.d(TAG, "Updating overlay assets");
              }
              final PackageManagerInternal pm =
                      LocalServices.getService(PackageManagerInternal.class);
              final boolean updateFrameworkRes = targetPackageNames.contains("android");
              if (updateFrameworkRes) {
                  targetPackageNames = pm.getTargetPackageNames(userId);
              }
  
              final Map<String, List<String>> pendingChanges =
                      new ArrayMap<>(targetPackageNames.size());
              synchronized (mLock) {
                  final List<String> frameworkOverlays =
                          mImpl.getEnabledOverlayPackageNames("android", userId);
                  final int n = targetPackageNames.size();
                  for (int i = 0; i < n; i++) {
                      final String targetPackageName = targetPackageNames.get(i);
                      List<String> list = new ArrayList<>();
                      if (!"android".equals(targetPackageName)) {
                          list.addAll(frameworkOverlays);
                      }
                      list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
                      pendingChanges.put(targetPackageName, list);
                  }
              }
  
              final int n = targetPackageNames.size();
              for (int i = 0; i < n; i++) {
                  final String targetPackageName = targetPackageNames.get(i);
                  if (DEBUG) {
                      Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
                              + TextUtils.join(",", pendingChanges.get(targetPackageName))
                              + "] userId=" + userId);
                  }
  
                  if (!pm.setEnabledOverlayPackages(
                          userId, targetPackageName, pendingChanges.get(targetPackageName))) {
                      Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
                              targetPackageName, userId));
                  }
              }
          } finally {
              traceEnd(TRACE_TAG_RRO);
          }
      }
  
      private void updateAssets(final int userId, final String targetPackageName) {
          updateAssets(userId, Collections.singletonList(targetPackageName));
      }
  
      private void updateAssets(final int userId, List<String> targetPackageNames) {
          updateOverlayPaths(userId, targetPackageNames);
          final IActivityManager am = ActivityManager.getService();
          try {
              am.scheduleApplicationInfoChanged(targetPackageNames, userId);
          } catch (RemoteException e) {
              // Intentionally left empty.
          }
      }
  
      private void schedulePersistSettings() {
          if (mPersistSettingsScheduled.getAndSet(true)) {
              return;
          }
          IoThread.getHandler().post(() -> {
              mPersistSettingsScheduled.set(false);
              if (DEBUG) {
                  Slog.d(TAG, "Writing overlay settings");
              }
              synchronized (mLock) {
                  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);
          }
      }
  
      private static final class PackageManagerHelper implements
              OverlayManagerServiceImpl.PackageManagerHelper {
  
          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 SparseArray<HashMap<String, PackageInfo>> mCache = new SparseArray<>();
  
          PackageManagerHelper() {
              mPackageManager = getPackageManager();
              mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
          }
  
          public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
                  final boolean useCache) {
              if (useCache) {
                  final PackageInfo cachedPi = getCachedPackageInfo(packageName, userId);
                  if (cachedPi != null) {
                      return cachedPi;
                  }
              }
              try {
                  final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0, userId);
                  if (useCache && pi != null) {
                      cachePackageInfo(packageName, userId, pi);
                  }
                  return pi;
              } catch (RemoteException e) {
                  // Intentionally left empty.
              }
              return null;
          }
  
          @Override
          public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
              return getPackageInfo(packageName, userId, true);
          }
  
          @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 List<PackageInfo> getOverlayPackages(final int userId) {
              return mPackageManagerInternal.getOverlayPackages(userId);
          }
  
          public PackageInfo getCachedPackageInfo(@NonNull final String packageName,
                  final int userId) {
              final HashMap<String, PackageInfo> map = mCache.get(userId);
              return map == null ? null : map.get(packageName);
          }
  
          public void cachePackageInfo(@NonNull final String packageName, final int userId,
                  @NonNull final PackageInfo pi) {
              HashMap<String, PackageInfo> map = mCache.get(userId);
              if (map == null) {
                  map = new HashMap<>();
                  mCache.put(userId, map);
              }
              map.put(packageName, pi);
          }
  
          public void forgetPackageInfo(@NonNull final String packageName, final int userId) {
              final HashMap<String, PackageInfo> map = mCache.get(userId);
              if (map == null) {
                  return;
              }
              map.remove(packageName);
              if (map.isEmpty()) {
                  mCache.delete(userId);
              }
          }
  
          public void forgetAllPackageInfos(final int userId) {
              mCache.delete(userId);
          }
  
          private static final String TAB1 = "    ";
          private static final String TAB2 = TAB1 + TAB1;
  
          public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
              pw.println("PackageInfo cache");
  
              if (!dumpState.isVerbose()) {
                  int count = 0;
                  final int n = mCache.size();
                  for (int i = 0; i < n; i++) {
                      final int userId = mCache.keyAt(i);
                      count += mCache.get(userId).size();
                  }
                  pw.println(TAB1 + count + " package(s)");
                  return;
              }
  
              if (mCache.size() == 0) {
                  pw.println(TAB1 + "<empty>");
                  return;
              }
  
              final int n = mCache.size();
              for (int i = 0; i < n; i++) {
                  final int userId = mCache.keyAt(i);
                  pw.println(TAB1 + "User " + userId);
                  final HashMap<String, PackageInfo> map = mCache.get(userId);
                  for (Map.Entry<String, PackageInfo> entry : map.entrySet()) {
                      pw.println(TAB2 + entry.getKey() + ": " + entry.getValue());
                  }
              }
          }
      }
  }
  