content: /*
   * Copyright (C) 2018 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.infra;
  
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.UserIdInt;
  import android.app.ActivityManager;
  import android.content.ComponentName;
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.UserInfo;
  import android.database.ContentObserver;
  import android.net.Uri;
  import android.os.Binder;
  import android.os.Handler;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.os.UserManagerInternal;
  import android.provider.Settings;
  import android.util.Slog;
  import android.util.SparseArray;
  import android.util.SparseBooleanArray;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.content.PackageMonitor;
  import com.android.internal.infra.AbstractRemoteService;
  import com.android.internal.os.BackgroundThread;
  import com.android.internal.util.Preconditions;
  import com.android.server.LocalServices;
  import com.android.server.SystemService;
  
  import java.io.PrintWriter;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.List;
  
  /**
   * Base class for {@link SystemService SystemServices} that support multi user.
   *
   * <p>Subclasses of this service are just a facade for the service binder calls - the "real" work
   * is done by the {@link AbstractPerUserSystemService} subclasses, which are automatically managed
   * through an user -> service cache.
   *
   * <p>It also takes care of other plumbing tasks such as:
   *
   * <ul>
   *   <li>Disabling the service when {@link UserManager} restrictions change.
   *   <li>Refreshing the service when its underlying
   *   {@link #getServiceSettingsProperty() Settings property} changed.
   *   <li>Calling the service when other Settings properties changed.
   * </ul>
   *
   * <p>See {@code com.android.server.autofill.AutofillManagerService} for a concrete
   * (no pun intended) example of how to use it.
   *
   * @param <M> "master" service class.
   * @param <S> "real" service class.
   *
   * @hide
   */
  // TODO(b/117779333): improve javadoc above instead of using Autofill as an example
  public abstract class AbstractMasterSystemService<M extends AbstractMasterSystemService<M, S>,
          S extends AbstractPerUserSystemService<S, M>> extends SystemService {
  
      /** On a package update, does not refresh the per-user service in the cache. */
      public static final int PACKAGE_UPDATE_POLICY_NO_REFRESH = 0x00000001;
  
      /**
       * On a package update, removes any existing per-user services in the cache.
       *
       * <p>This does not immediately recreate these services. It is assumed they will be recreated
       * for the next user request.
       */
      public static final int PACKAGE_UPDATE_POLICY_REFRESH_LAZY = 0x00000002;
  
      /**
       * On a package update, removes and recreates any existing per-user services in the cache.
       */
      public static final int PACKAGE_UPDATE_POLICY_REFRESH_EAGER = 0x00000004;
  
      /** On a package restart, does not refresh the per-user service in the cache. */
      public static final int PACKAGE_RESTART_POLICY_NO_REFRESH = 0x00000010;
  
      /**
       * On a package restart, removes any existing per-user services in the cache.
       *
       * <p>This does not immediately recreate these services. It is assumed they will be recreated
       * for the next user request.
       */
      public static final int PACKAGE_RESTART_POLICY_REFRESH_LAZY = 0x00000020;
  
      /**
       * On a package restart, removes and recreates any existing per-user services in the cache.
       */
      public static final int PACKAGE_RESTART_POLICY_REFRESH_EAGER = 0x00000040;
  
      @IntDef(flag = true, prefix = { "PACKAGE_" }, value = {
              PACKAGE_UPDATE_POLICY_NO_REFRESH,
              PACKAGE_UPDATE_POLICY_REFRESH_LAZY,
              PACKAGE_UPDATE_POLICY_REFRESH_EAGER,
              PACKAGE_RESTART_POLICY_NO_REFRESH,
              PACKAGE_RESTART_POLICY_REFRESH_LAZY,
              PACKAGE_RESTART_POLICY_REFRESH_EAGER
      })
  
      @Retention(RetentionPolicy.SOURCE)
      public @interface ServicePackagePolicyFlags {}
  
      /**
       * Log tag
       */
      protected final String mTag = getClass().getSimpleName();
  
      /**
       * Lock used to synchronize access to internal state; should be acquired before calling a
       * method whose name ends with {@code locked}.
       */
      protected final Object mLock = new Object();
  
      /**
       * Object used to define the name of the service component used to create
       * {@link com.android.internal.infra.AbstractRemoteService} instances.
       */
      @Nullable
      protected final ServiceNameResolver mServiceNameResolver;
  
      /**
       * Whether the service should log debug statements.
       */
      //TODO(b/117779333): consider using constants for these guards
      public boolean verbose = false;
  
      /**
       * Whether the service should log verbose statements.
       */
      //TODO(b/117779333): consider using constants for these guards
      public boolean debug = false;
  
      /**
       * Whether the service is allowed to bind to an instant-app.
       */
      @GuardedBy("mLock")
      protected boolean mAllowInstantService;
  
      /**
       * Users disabled due to {@link UserManager} restrictions, or {@code null} if the service cannot
       * be disabled through {@link UserManager}.
       */
      @GuardedBy("mLock")
      @Nullable
      private final SparseBooleanArray mDisabledByUserRestriction;
  
      /**
       * Cache of services per user id.
       */
      @GuardedBy("mLock")
      private final SparseArray<S> mServicesCache = new SparseArray<>();
  
      /**
       * Value that determines whether the per-user service should be removed from the cache when its
       * apk is updated or restarted.
       */
      private final @ServicePackagePolicyFlags int mServicePackagePolicyFlags;
  
      /**
       * Name of the service packages whose APK are being updated, keyed by user id.
       */
      @GuardedBy("mLock")
      private SparseArray<String> mUpdatingPackageNames;
  
      /**
       * Default constructor.
       *
       * <p>When using this constructor, the {@link AbstractPerUserSystemService} is removed from
       * the cache (and re-added) when the service package is updated.
       *
       * @param context system context.
       * @param serviceNameResolver resolver for
       * {@link com.android.internal.infra.AbstractRemoteService} instances, or
       * {@code null} when the service doesn't bind to remote services.
       * @param disallowProperty when not {@code null}, defines a {@link UserManager} restriction that
       *        disables the service. <b>NOTE: </b> you'll also need to add it to
       *        {@code UserRestrictionsUtils.USER_RESTRICTIONS}.
       */
      protected AbstractMasterSystemService(@NonNull Context context,
              @Nullable ServiceNameResolver serviceNameResolver,
              @Nullable String disallowProperty) {
          this(context, serviceNameResolver, disallowProperty,
                  PACKAGE_UPDATE_POLICY_REFRESH_LAZY | PACKAGE_RESTART_POLICY_REFRESH_LAZY);
      }
  
      /**
       * Full Constructor.
       *
       * @param context system context.
       * @param serviceNameResolver resolver for
       * {@link com.android.internal.infra.AbstractRemoteService} instances, or
       * {@code null} when the service doesn't bind to remote services.
       * @param disallowProperty when not {@code null}, defines a {@link UserManager} restriction that
       *        disables the service. <b>NOTE: </b> you'll also need to add it to
       *        {@code UserRestrictionsUtils.USER_RESTRICTIONS}.
       * @param servicePackagePolicyFlags a combination of
       *        {@link #PACKAGE_UPDATE_POLICY_NO_REFRESH},
       *        {@link #PACKAGE_UPDATE_POLICY_REFRESH_LAZY},
       *        {@link #PACKAGE_UPDATE_POLICY_REFRESH_EAGER},
       *        {@link #PACKAGE_RESTART_POLICY_NO_REFRESH},
       *        {@link #PACKAGE_RESTART_POLICY_REFRESH_LAZY} or
       *        {@link #PACKAGE_RESTART_POLICY_REFRESH_EAGER}
       */
      protected AbstractMasterSystemService(@NonNull Context context,
              @Nullable ServiceNameResolver serviceNameResolver, @Nullable String disallowProperty,
              @ServicePackagePolicyFlags int servicePackagePolicyFlags) {
          super(context);
  
          final int updatePolicyMask = PACKAGE_UPDATE_POLICY_NO_REFRESH
                  | PACKAGE_UPDATE_POLICY_REFRESH_LAZY | PACKAGE_UPDATE_POLICY_REFRESH_EAGER;
          if ((servicePackagePolicyFlags & updatePolicyMask) == 0) {
              // If the package update policy is not set, add the default flag
              servicePackagePolicyFlags |= PACKAGE_UPDATE_POLICY_REFRESH_LAZY;
          }
          final int restartPolicyMask = PACKAGE_RESTART_POLICY_NO_REFRESH
                  | PACKAGE_RESTART_POLICY_REFRESH_LAZY | PACKAGE_RESTART_POLICY_REFRESH_EAGER;
          if ((servicePackagePolicyFlags & restartPolicyMask) == 0) {
              // If the package restart policy is not set, add the default flag
              servicePackagePolicyFlags |= PACKAGE_RESTART_POLICY_REFRESH_LAZY;
          }
          mServicePackagePolicyFlags = servicePackagePolicyFlags;
  
          mServiceNameResolver = serviceNameResolver;
          if (mServiceNameResolver != null) {
              mServiceNameResolver.setOnTemporaryServiceNameChangedCallback(
                      (u, s, t) -> onServiceNameChanged(u, s, t));
  
          }
          if (disallowProperty == null) {
              mDisabledByUserRestriction = null;
          } else {
              mDisabledByUserRestriction = new SparseBooleanArray();
              // Hookup with UserManager to disable service when necessary.
              final UserManager um = context.getSystemService(UserManager.class);
              final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
              final List<UserInfo> users = um.getUsers();
              for (int i = 0; i < users.size(); i++) {
                  final int userId = users.get(i).id;
                  final boolean disabled = umi.getUserRestriction(userId, disallowProperty);
                  if (disabled) {
                      Slog.i(mTag, "Disabling by restrictions user " + userId);
                      mDisabledByUserRestriction.put(userId, disabled);
                  }
              }
              umi.addUserRestrictionsListener((userId, newRestrictions, prevRestrictions) -> {
                  final boolean disabledNow =
                          newRestrictions.getBoolean(disallowProperty, false);
                  synchronized (mLock) {
                      final boolean disabledBefore = mDisabledByUserRestriction.get(userId);
                      if (disabledBefore == disabledNow) {
                          // Nothing changed, do nothing.
                          if (debug) {
                              Slog.d(mTag, "Restriction did not change for user " + userId);
                              return;
                          }
                      }
                      Slog.i(mTag, "Updating for user " + userId + ": disabled=" + disabledNow);
                      mDisabledByUserRestriction.put(userId, disabledNow);
                      updateCachedServiceLocked(userId, disabledNow);
                  }
              });
          }
          startTrackingPackageChanges();
      }
  
      @Override // from SystemService
      public void onBootPhase(int phase) {
          if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
              new SettingsObserver(BackgroundThread.getHandler());
          }
      }
  
      @Override // from SystemService
      public void onUnlockUser(int userId) {
          synchronized (mLock) {
              updateCachedServiceLocked(userId);
          }
      }
  
      @Override // from SystemService
      public void onCleanupUser(int userId) {
          synchronized (mLock) {
              removeCachedServiceLocked(userId);
          }
      }
  
      /**
       * Gets whether the service is allowed to bind to an instant-app.
       *
       * <p>Typically called by {@code ShellCommand} during CTS tests.
       *
       * @throws SecurityException if caller is not allowed to manage this service's settings.
       */
      public final boolean getAllowInstantService() {
          enforceCallingPermissionForManagement();
          synchronized (mLock) {
              return mAllowInstantService;
          }
      }
  
      /**
       * Checks whether the service is allowed to bind to an instant-app.
       *
       * <p>Typically called by subclasses when creating {@link AbstractRemoteService} instances.
       *
       * <p><b>NOTE: </b>must not be called by {@code ShellCommand} as it does not check for
       * permission.
       */
      public final boolean isBindInstantServiceAllowed() {
          synchronized (mLock) {
              return mAllowInstantService;
          }
      }
  
      /**
       * Sets whether the service is allowed to bind to an instant-app.
       *
       * <p>Typically called by {@code ShellCommand} during CTS tests.
       *
       * @throws SecurityException if caller is not allowed to manage this service's settings.
       */
      public final void setAllowInstantService(boolean mode) {
          Slog.i(mTag, "setAllowInstantService(): " + mode);
          enforceCallingPermissionForManagement();
          synchronized (mLock) {
              mAllowInstantService = mode;
          }
      }
  
      /**
       * Temporarily sets the service implementation.
       *
       * <p>Typically used by Shell command and/or CTS tests.
       *
       * @param componentName name of the new component
       * @param durationMs how long the change will be valid (the service will be automatically reset
       *            to the default component after this timeout expires).
       * @throws SecurityException if caller is not allowed to manage this service's settings.
       * @throws IllegalArgumentException if value of {@code durationMs} is higher than
       *             {@link #getMaximumTemporaryServiceDurationMs()}.
       */
      public final void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
              int durationMs) {
          Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for "
                  + durationMs + "ms");
          enforceCallingPermissionForManagement();
  
          Preconditions.checkNotNull(componentName);
          final int maxDurationMs = getMaximumTemporaryServiceDurationMs();
          if (durationMs > maxDurationMs) {
              throw new IllegalArgumentException(
                      "Max duration is " + maxDurationMs + " (called with " + durationMs + ")");
          }
  
          synchronized (mLock) {
              final S oldService = peekServiceForUserLocked(userId);
              if (oldService != null) {
                  oldService.removeSelfFromCacheLocked();
              }
              mServiceNameResolver.setTemporaryService(userId, componentName, durationMs);
          }
      }
  
      /**
       * Sets whether the default service should be used.
       *
       * <p>Typically used during CTS tests to make sure only the default service doesn't interfere
       * with the test results.
       *
       * @throws SecurityException if caller is not allowed to manage this service's settings.
       *
       * @return whether the enabled state changed.
       */
      public final boolean setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) {
          Slog.i(mTag, "setDefaultServiceEnabled() for userId " + userId + ": " + enabled);
          enforceCallingPermissionForManagement();
  
          synchronized (mLock) {
              final boolean changed = mServiceNameResolver.setDefaultServiceEnabled(userId, enabled);
              if (!changed) {
                  if (verbose) {
                      Slog.v(mTag, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
                  }
                  return false;
              }
  
              final S oldService = peekServiceForUserLocked(userId);
              if (oldService != null) {
                  oldService.removeSelfFromCacheLocked();
              }
  
              // Must update the service on cache so its initialization code is triggered
              updateCachedServiceLocked(userId);
          }
          return true;
      }
  
      /**
       * Checks whether the default service should be used.
       *
       * <p>Typically used during CTS tests to make sure only the default service doesn't interfere
       * with the test results.
       *
       * @throws SecurityException if caller is not allowed to manage this service's settings.
       */
      public final boolean isDefaultServiceEnabled(@UserIdInt int userId) {
          enforceCallingPermissionForManagement();
  
          synchronized (mLock) {
              return mServiceNameResolver.isDefaultServiceEnabled(userId);
          }
      }
  
      /**
       * Gets the maximum time the service implementation can be changed.
       *
       * @throws UnsupportedOperationException if subclass doesn't override it.
       */
      protected int getMaximumTemporaryServiceDurationMs() {
          throw new UnsupportedOperationException("Not implemented by " + getClass());
      }
  
      /**
       * Resets the temporary service implementation to the default component.
       *
       * <p>Typically used by Shell command and/or CTS tests.
       *
       * @throws SecurityException if caller is not allowed to manage this service's settings.
       */
      public final void resetTemporaryService(@UserIdInt int userId) {
          Slog.i(mTag, "resetTemporaryService(): " + userId);
          enforceCallingPermissionForManagement();
          synchronized (mLock) {
              final S service = getServiceForUserLocked(userId);
              if (service != null) {
                  service.resetTemporaryServiceLocked();
              }
          }
      }
  
      /**
       * Asserts that the caller has permissions to manage this service.
       *
       * <p>Typically called by {@code ShellCommand} implementations.
       *
       * @throws UnsupportedOperationException if subclass doesn't override it.
       * @throws SecurityException if caller is not allowed to manage this service's settings.
       */
      protected void enforceCallingPermissionForManagement() {
          throw new UnsupportedOperationException("Not implemented by " + getClass());
      }
  
      /**
       * Creates a new service that will be added to the cache.
       *
       * @param resolvedUserId the resolved user id for the service.
       * @param disabled whether the service is currently disabled (due to {@link UserManager}
       * restrictions).
       *
       * @return a new instance.
       */
      @Nullable
      protected abstract S newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled);
  
      /**
       * Register the service for extra Settings changes (i.e., other than
       * {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE} or
       * {@link #getServiceSettingsProperty()}, which are automatically handled).
       *
       * <p> Example:
       *
       * <pre><code>
       * resolver.registerContentObserver(Settings.Global.getUriFor(
       *     Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES), false, observer,
       *     UserHandle.USER_ALL);
       * </code></pre>
       *
       * <p><b>NOTE: </p>it doesn't need to register for
       * {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE} or
       * {@link #getServiceSettingsProperty()}.
       *
       */
      @SuppressWarnings("unused")
      protected void registerForExtraSettingsChanges(@NonNull ContentResolver resolver,
              @NonNull ContentObserver observer) {
      }
  
      /**
       * Callback for Settings changes that were registered though
       * {@link #registerForExtraSettingsChanges(ContentResolver, ContentObserver)}.
       *
       * @param userId user associated with the change
       * @param property Settings property changed.
       */
      protected void onSettingsChanged(@UserIdInt int userId, @NonNull String property) {
      }
  
      /**
       * Gets the service instance for an user, creating an instance if not present in the cache.
       */
      @GuardedBy("mLock")
      @NonNull
      protected S getServiceForUserLocked(@UserIdInt int userId) {
          final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                  Binder.getCallingUid(), userId, false, false, null, null);
          S service = mServicesCache.get(resolvedUserId);
          if (service == null) {
              final boolean disabled = isDisabledLocked(userId);
              service = newServiceLocked(resolvedUserId, disabled);
              if (!disabled) {
                  onServiceEnabledLocked(service, resolvedUserId);
              }
              mServicesCache.put(userId, service);
          }
          return service;
      }
  
      /**
       * Gets the <b>existing</b> service instance for a user, returning {@code null} if not already
       * present in the cache.
       */
      @GuardedBy("mLock")
      @Nullable
      protected S peekServiceForUserLocked(@UserIdInt int userId) {
          final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                  Binder.getCallingUid(), userId, false, false, null, null);
          return mServicesCache.get(resolvedUserId);
      }
  
      /**
       * Updates a cached service for a given user.
       */
      @GuardedBy("mLock")
      protected void updateCachedServiceLocked(@UserIdInt int userId) {
          updateCachedServiceLocked(userId, isDisabledLocked(userId));
      }
  
      /**
       * Checks whether the service is disabled (through {@link UserManager} restrictions) for the
       * given user.
       */
      protected boolean isDisabledLocked(@UserIdInt int userId) {
          return mDisabledByUserRestriction == null ? false : mDisabledByUserRestriction.get(userId);
      }
  
      /**
       * Updates a cached service for a given user.
       *
       * @param userId user handle.
       * @param disabled whether the user is disabled.
       * @return service for the user.
       */
      @GuardedBy("mLock")
      protected S updateCachedServiceLocked(@UserIdInt int userId, boolean disabled) {
          final S service = getServiceForUserLocked(userId);
          if (service != null) {
              service.updateLocked(disabled);
              if (!service.isEnabledLocked()) {
                  removeCachedServiceLocked(userId);
              } else {
                  onServiceEnabledLocked(service, userId);
              }
          }
          return service;
      }
  
      /**
       * Gets the Settings property that defines the name of the component name used to bind this
       * service to an external service, or {@code null} when the service is not defined by such
       * property (for example, if it's a system service defined by framework resources).
       */
      @Nullable
      protected String getServiceSettingsProperty() {
          return null;
      }
  
      /**
       * Callback called after a new service was added to the cache, or an existing service that was
       * previously disabled gets enabled.
       *
       * <p>By default doesn't do anything, but can be overridden by subclasses.
       */
      @SuppressWarnings("unused")
      protected void onServiceEnabledLocked(@NonNull S service, @UserIdInt int userId) {
      }
  
      /**
       * Removes a cached service for a given user.
       *
       * @return the removed service.
       */
      @GuardedBy("mLock")
      @NonNull
      protected final S removeCachedServiceLocked(@UserIdInt int userId) {
          final S service = peekServiceForUserLocked(userId);
          if (service != null) {
              mServicesCache.delete(userId);
              onServiceRemoved(service, userId);
          }
          return service;
      }
  
      /**
       * Called before the package that provides the service for the given user is being updated.
       */
      protected void onServicePackageUpdatingLocked(@UserIdInt int userId) {
          if (verbose) Slog.v(mTag, "onServicePackageUpdatingLocked(" + userId + ")");
      }
  
      /**
       * Called after the package that provides the service for the given user is being updated.
       */
      protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
          if (verbose) Slog.v(mTag, "onServicePackageUpdated(" + userId + ")");
      }
  
      /**
       * Called after the package data that provides the service for the given user is cleared.
       */
      protected void onServicePackageDataClearedLocked(@UserIdInt int userId) {
          if (verbose) Slog.v(mTag, "onServicePackageDataCleared(" + userId + ")");
      }
  
      /**
       * Called after the package that provides the service for the given user is restarted.
       */
      protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
          if (verbose) Slog.v(mTag, "onServicePackageRestarted(" + userId + ")");
      }
  
      /**
       * Called after the service is removed from the cache.
       */
      @SuppressWarnings("unused")
      protected void onServiceRemoved(@NonNull S service, @UserIdInt int userId) {
      }
  
      /**
       * Called when the service name changed (typically when using temporary services).
       *
       * <p>By default, it calls {@link #updateCachedServiceLocked(int)}; subclasses must either call
       * that same method, or {@code super.onServiceNameChanged()}.
       *
       * @param userId user handle.
       * @param serviceName the new service name.
       * @param isTemporary whether the new service is temporary.
       */
      protected void onServiceNameChanged(@UserIdInt int userId, @Nullable String serviceName,
              boolean isTemporary) {
          synchronized (mLock) {
              updateCachedServiceLocked(userId);
          }
      }
  
      /**
       * Visits all services in the cache.
       */
      @GuardedBy("mLock")
      protected void visitServicesLocked(@NonNull Visitor<S> visitor) {
          final int size = mServicesCache.size();
          for (int i = 0; i < size; i++) {
              visitor.visit(mServicesCache.valueAt(i));
          }
      }
  
      /**
       * Clear the cache by removing all services.
       */
      @GuardedBy("mLock")
      protected void clearCacheLocked() {
          mServicesCache.clear();
      }
  
      /**
       * Asserts that the given package name is owned by the UID making this call.
       *
       * @throws SecurityException when it's not...
       */
      protected final void assertCalledByPackageOwner(@NonNull String packageName) {
          Preconditions.checkNotNull(packageName);
          final int uid = Binder.getCallingUid();
          final String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
          if (packages != null) {
              for (String candidate : packages) {
                  if (packageName.equals(candidate)) return; // Found it
              }
          }
          throw new SecurityException("UID " + uid + " does not own " + packageName);
      }
  
      // TODO(b/117779333): support proto
      protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
          boolean realDebug = debug;
          boolean realVerbose = verbose;
          final String prefix2 = "    ";
  
          try {
              // Temporarily turn on full logging;
              debug = verbose = true;
              final int size = mServicesCache.size();
              pw.print(prefix); pw.print("Debug: "); pw.print(realDebug);
              pw.print(" Verbose: "); pw.println(realVerbose);
              pw.print("Package policy flags: "); pw.println(mServicePackagePolicyFlags);
              if (mUpdatingPackageNames != null) {
                  pw.print("Packages being updated: "); pw.println(mUpdatingPackageNames);
              }
              if (mServiceNameResolver != null) {
                  pw.print(prefix); pw.print("Name resolver: ");
                  mServiceNameResolver.dumpShort(pw); pw.println();
                  final UserManager um = getContext().getSystemService(UserManager.class);
                  final List<UserInfo> users = um.getUsers();
                  for (int i = 0; i < users.size(); i++) {
                      final int userId = users.get(i).id;
                      pw.print(prefix2); pw.print(userId); pw.print(": ");
                      mServiceNameResolver.dumpShort(pw, userId); pw.println();
                  }
              }
              pw.print(prefix); pw.print("Users disabled by restriction: ");
              pw.println(mDisabledByUserRestriction);
              pw.print(prefix); pw.print("Allow instant service: "); pw.println(mAllowInstantService);
              final String settingsProperty = getServiceSettingsProperty();
              if (settingsProperty != null) {
                  pw.print(prefix); pw.print("Settings property: "); pw.println(settingsProperty);
              }
              pw.print(prefix); pw.print("Cached services: ");
              if (size == 0) {
                  pw.println("none");
              } else {
                  pw.println(size);
                  for (int i = 0; i < size; i++) {
                      pw.print(prefix); pw.print("Service at "); pw.print(i); pw.println(": ");
                      final S service = mServicesCache.valueAt(i);
                      service.dumpLocked(prefix2, pw);
                      pw.println();
                  }
              }
          } finally {
              debug = realDebug;
              verbose = realVerbose;
          }
      }
  
      private void startTrackingPackageChanges() {
          final PackageMonitor monitor = new PackageMonitor() {
  
              @Override
              public void onPackageUpdateStarted(@NonNull String packageName, int uid) {
                  if (verbose) Slog.v(mTag, "onPackageUpdateStarted(): " + packageName);
                  final String activePackageName = getActiveServicePackageNameLocked();
                  if (!packageName.equals(activePackageName)) return;
  
                  final int userId = getChangingUserId();
                  synchronized (mLock) {
                      if (mUpdatingPackageNames == null) {
                          mUpdatingPackageNames = new SparseArray<String>(mServicesCache.size());
                      }
                      mUpdatingPackageNames.put(userId, packageName);
                      onServicePackageUpdatingLocked(userId);
                      if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_NO_REFRESH) != 0) {
                          if (debug) {
                              Slog.d(mTag, "Holding service for user " + userId + " while package "
                                      + activePackageName + " is being updated");
                          }
                      } else {
                          if (debug) {
                              Slog.d(mTag, "Removing service for user " + userId
                                      + " because package " + activePackageName
                                      + " is being updated");
                          }
                          removeCachedServiceLocked(userId);
  
                          if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_REFRESH_EAGER)
                                  != 0) {
                              if (debug) {
                                  Slog.d(mTag, "Eagerly recreating service for user "
                                          + userId);
                              }
                              getServiceForUserLocked(userId);
                          }
                      }
                  }
              }
  
              @Override
              public void onPackageUpdateFinished(@NonNull String packageName, int uid) {
                  if (verbose) Slog.v(mTag, "onPackageUpdateFinished(): " + packageName);
                  final int userId = getChangingUserId();
                  synchronized (mLock) {
                      final String activePackageName = mUpdatingPackageNames == null ? null
                              : mUpdatingPackageNames.get(userId);
                      if (packageName.equals(activePackageName)) {
                          if (mUpdatingPackageNames != null) {
                              mUpdatingPackageNames.remove(userId);
                              if (mUpdatingPackageNames.size() == 0) {
                                  mUpdatingPackageNames = null;
                              }
                          }
                          onServicePackageUpdatedLocked(userId);
                      } else {
                          handlePackageUpdateLocked(packageName);
                      }
                  }
              }
  
              @Override
              public void onPackageRemoved(String packageName, int uid) {
                  synchronized (mLock) {
                      final int userId = getChangingUserId();
                      final S service = peekServiceForUserLocked(userId);
                      if (service != null) {
                          final ComponentName componentName = service.getServiceComponentName();
                          if (componentName != null) {
                              if (packageName.equals(componentName.getPackageName())) {
                                  handleActiveServiceRemoved(userId);
                              }
                          }
                      }
                  }
              }
  
              @Override
              public boolean onHandleForceStop(Intent intent, String[] packages,
                      int uid, boolean doit) {
                  synchronized (mLock) {
                      final String activePackageName = getActiveServicePackageNameLocked();
                      for (String pkg : packages) {
                          if (pkg.equals(activePackageName)) {
                              if (!doit) {
                                  return true;
                              }
                              final String action = intent.getAction();
                              final int userId = getChangingUserId();
                              if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
                                  handleActiveServiceRestartedLocked(activePackageName, userId);
                              } else {
                                  removeCachedServiceLocked(userId);
                              }
                          } else {
                              handlePackageUpdateLocked(pkg);
                          }
                      }
                  }
                  return false;
              }
  
              @Override
              public void onPackageDataCleared(String packageName, int uid) {
                  if (verbose) Slog.v(mTag, "onPackageDataCleared(): " + packageName);
                  final int userId = getChangingUserId();
                  synchronized (mLock) {
                      final S service = peekServiceForUserLocked(userId);
                      if (service != null) {
                          final ComponentName componentName = service.getServiceComponentName();
                          if (componentName != null) {
                              if (packageName.equals(componentName.getPackageName())) {
                                  onServicePackageDataClearedLocked(userId);
                              }
                          }
                      }
                  }
              }
  
              private void handleActiveServiceRemoved(@UserIdInt int userId) {
                  synchronized (mLock) {
                      removeCachedServiceLocked(userId);
                  }
                  final String serviceSettingsProperty = getServiceSettingsProperty();
                  if (serviceSettingsProperty != null) {
                      Settings.Secure.putStringForUser(getContext().getContentResolver(),
                              serviceSettingsProperty, null, userId);
                  }
              }
  
              private void handleActiveServiceRestartedLocked(String activePackageName,
                      @UserIdInt int userId) {
                  if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_NO_REFRESH) != 0) {
                      if (debug) {
                          Slog.d(mTag, "Holding service for user " + userId + " while package "
                                  + activePackageName + " is being restarted");
                      }
                  } else {
                      if (debug) {
                          Slog.d(mTag, "Removing service for user " + userId
                                  + " because package " + activePackageName
                                  + " is being restarted");
                      }
                      removeCachedServiceLocked(userId);
  
                      if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_REFRESH_EAGER) != 0) {
                          if (debug) {
                              Slog.d(mTag, "Eagerly recreating service for user " + userId);
                          }
                          getServiceForUserLocked(userId);
                      }
                  }
                  onServicePackageRestartedLocked(userId);
              }
  
              private String getActiveServicePackageNameLocked() {
                  final int userId = getChangingUserId();
                  final S service = peekServiceForUserLocked(userId);
                  if (service == null) {
                      return null;
                  }
                  final ComponentName serviceComponent = service.getServiceComponentName();
                  if (serviceComponent == null) {
                      return null;
                  }
                  return serviceComponent.getPackageName();
              }
  
              @GuardedBy("mLock")
              private void handlePackageUpdateLocked(String packageName) {
                  visitServicesLocked((s) -> s.handlePackageUpdateLocked(packageName));
              }
          };
  
          // package changes
          monitor.register(getContext(), null,  UserHandle.ALL, true);
      }
  
      /**
       * Visitor pattern.
       *
       * @param <S> visited class.
       */
      public interface Visitor<S> {
          /**
           * Visits a service.
           *
           * @param service the service to be visited.
           */
          void visit(@NonNull S service);
      }
  
      private final class SettingsObserver extends ContentObserver {
          SettingsObserver(Handler handler) {
              super(handler);
              ContentResolver resolver = getContext().getContentResolver();
              final String serviceProperty = getServiceSettingsProperty();
              if (serviceProperty != null) {
                  resolver.registerContentObserver(Settings.Secure.getUriFor(
                          serviceProperty), false, this, UserHandle.USER_ALL);
              }
              resolver.registerContentObserver(Settings.Secure.getUriFor(
                      Settings.Secure.USER_SETUP_COMPLETE), false, this, UserHandle.USER_ALL);
              registerForExtraSettingsChanges(resolver, this);
          }
  
          @Override
          public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
              if (verbose) Slog.v(mTag, "onChange(): uri=" + uri + ", userId=" + userId);
              final String property = uri.getLastPathSegment();
              if (property.equals(getServiceSettingsProperty())
                      || property.equals(Settings.Secure.USER_SETUP_COMPLETE)) {
                  synchronized (mLock) {
                      updateCachedServiceLocked(userId);
                  }
              } else {
                  onSettingsChanged(userId, property);
              }
          }
      }
  }
  