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.autofill;
  
  import static android.Manifest.permission.MANAGE_AUTO_FILL;
  import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
  import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
  import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
  
  import static com.android.server.autofill.Helper.sDebug;
  import static com.android.server.autofill.Helper.sFullScreenMode;
  import static com.android.server.autofill.Helper.sVerbose;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.UserIdInt;
  import android.app.ActivityManagerInternal;
  import android.app.ActivityThread;
  import android.content.AutofillOptions;
  import android.content.BroadcastReceiver;
  import android.content.ComponentName;
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.PackageManager;
  import android.content.pm.UserInfo;
  import android.database.ContentObserver;
  import android.graphics.Rect;
  import android.os.Binder;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.IBinder;
  import android.os.Parcelable;
  import android.os.RemoteCallback;
  import android.os.RemoteException;
  import android.os.ResultReceiver;
  import android.os.ShellCallback;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.provider.DeviceConfig;
  import android.provider.Settings;
  import android.service.autofill.FillEventHistory;
  import android.service.autofill.UserData;
  import android.text.TextUtils;
  import android.text.TextUtils.SimpleStringSplitter;
  import android.util.ArrayMap;
  import android.util.LocalLog;
  import android.util.Slog;
  import android.util.SparseArray;
  import android.util.SparseBooleanArray;
  import android.view.autofill.AutofillId;
  import android.view.autofill.AutofillManager;
  import android.view.autofill.AutofillManager.SmartSuggestionMode;
  import android.view.autofill.AutofillManagerInternal;
  import android.view.autofill.AutofillValue;
  import android.view.autofill.IAutoFillManager;
  import android.view.autofill.IAutoFillManagerClient;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.infra.AbstractRemoteService;
  import com.android.internal.infra.GlobalWhitelistState;
  import com.android.internal.infra.WhitelistHelper;
  import com.android.internal.os.IResultReceiver;
  import com.android.internal.util.DumpUtils;
  import com.android.internal.util.Preconditions;
  import com.android.internal.util.SyncResultReceiver;
  import com.android.server.FgThread;
  import com.android.server.LocalServices;
  import com.android.server.autofill.ui.AutoFillUI;
  import com.android.server.infra.AbstractMasterSystemService;
  import com.android.server.infra.FrameworkResourcesServiceNameResolver;
  import com.android.server.infra.SecureSettingsServiceNameResolver;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Set;
  
  /**
   * Entry point service for autofill management.
   *
   * <p>This service provides the {@link IAutoFillManager} implementation and keeps a list of
   * {@link AutofillManagerServiceImpl} per user; the real work is done by
   * {@link AutofillManagerServiceImpl} itself.
   */
  public final class AutofillManagerService
          extends AbstractMasterSystemService<AutofillManagerService, AutofillManagerServiceImpl> {
  
      private static final String TAG = "AutofillManagerService";
  
      private static final Object sLock = AutofillManagerService.class;
  
      static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
  
      private static final char COMPAT_PACKAGE_DELIMITER = ':';
      private static final char COMPAT_PACKAGE_URL_IDS_DELIMITER = ',';
      private static final char COMPAT_PACKAGE_URL_IDS_BLOCK_BEGIN = '[';
      private static final char COMPAT_PACKAGE_URL_IDS_BLOCK_END = ']';
  
      private static final int DEFAULT_AUGMENTED_AUTOFILL_REQUEST_TIMEOUT_MILLIS = 5_000;
  
      /**
       * Maximum number of partitions that can be allowed in a session.
       *
       * <p>Can be modified using {@code cmd autofill set max_partitions} or through
       * {@link android.provider.Settings.Global#AUTOFILL_MAX_PARTITIONS_SIZE}.
       */
      @GuardedBy("sLock")
      private static int sPartitionMaxCount = AutofillManager.DEFAULT_MAX_PARTITIONS_SIZE;
  
      /**
       * Maximum number of visible datasets in the dataset picker UI, or {@code 0} to use default
       * value from resources.
       *
       * <p>Can be modified using {@code cmd autofill set max_visible_datasets} or through
       * {@link android.provider.Settings.Global#AUTOFILL_MAX_VISIBLE_DATASETS}.
       */
      @GuardedBy("sLock")
      private static int sVisibleDatasetsMaxCount = 0;
  
      /**
       * Object used to set the name of the augmented autofill service.
       */
      @NonNull
      final FrameworkResourcesServiceNameResolver mAugmentedAutofillResolver;
  
      private final AutoFillUI mUi;
  
      private final LocalLog mRequestsHistory = new LocalLog(20);
      private final LocalLog mUiLatencyHistory = new LocalLog(20);
      private final LocalLog mWtfHistory = new LocalLog(50);
  
      private final AutofillCompatState mAutofillCompatState = new AutofillCompatState();
  
      private final LocalService mLocalService = new LocalService();
      private final ActivityManagerInternal mAm;
  
      private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
                  if (sDebug) Slog.d(TAG, "Close system dialogs");
  
                  // TODO(b/64940307): we need to destroy all sessions that are finished but showing
                  // Save UI because there is no way to show the Save UI back when the activity
                  // beneath it is brought back to top. Ideally, we should just hide the UI and
                  // bring it back when the activity resumes.
                  synchronized (mLock) {
                      visitServicesLocked((s) -> s.destroyFinishedSessionsLocked());
                  }
                  mUi.hideAll(null);
              }
          }
      };
  
      /**
       * Supported modes for Augmented Autofill Smart Suggestions.
       */
      @GuardedBy("mLock")
      private int mSupportedSmartSuggestionModes;
  
      @GuardedBy("mLock")
      int mAugmentedServiceIdleUnbindTimeoutMs;
      @GuardedBy("mLock")
      int mAugmentedServiceRequestTimeoutMs;
  
      final AugmentedAutofillState mAugmentedAutofillState = new AugmentedAutofillState();
  
      public AutofillManagerService(Context context) {
          super(context,
                  new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
                  UserManager.DISALLOW_AUTOFILL);
          mUi = new AutoFillUI(ActivityThread.currentActivityThread().getSystemUiContext());
          mAm = LocalServices.getService(ActivityManagerInternal.class);
  
          DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_AUTOFILL,
                  ActivityThread.currentApplication().getMainExecutor(),
                  (properties) -> onDeviceConfigChange(properties.getKeyset()));
  
          setLogLevelFromSettings();
          setMaxPartitionsFromSettings();
          setMaxVisibleDatasetsFromSettings();
          setDeviceConfigProperties();
  
          final IntentFilter filter = new IntentFilter();
          filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
          context.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler());
  
          mAugmentedAutofillResolver = new FrameworkResourcesServiceNameResolver(getContext(),
                  com.android.internal.R.string.config_defaultAugmentedAutofillService);
          mAugmentedAutofillResolver.setOnTemporaryServiceNameChangedCallback(
                  (u, s, t) -> onAugmentedServiceNameChanged(u, s, t));
  
          if (mSupportedSmartSuggestionModes != AutofillManager.FLAG_SMART_SUGGESTION_OFF) {
              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;
                  // Must eager load the services so they bind to the augmented autofill service
                  getServiceForUserLocked(userId);
  
                  // And also set the global state
                  mAugmentedAutofillState.setServiceInfo(userId,
                          mAugmentedAutofillResolver.getServiceName(userId),
                          mAugmentedAutofillResolver.isTemporary(userId));
              }
          }
      }
  
      @Override // from AbstractMasterSystemService
      protected String getServiceSettingsProperty() {
          return Settings.Secure.AUTOFILL_SERVICE;
      }
  
      @Override // from AbstractMasterSystemService
      protected void registerForExtraSettingsChanges(@NonNull ContentResolver resolver,
              @NonNull ContentObserver observer) {
          resolver.registerContentObserver(Settings.Global.getUriFor(
                  Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES), false, observer,
                  UserHandle.USER_ALL);
          resolver.registerContentObserver(Settings.Global.getUriFor(
                  Settings.Global.AUTOFILL_LOGGING_LEVEL), false, observer,
                  UserHandle.USER_ALL);
          resolver.registerContentObserver(Settings.Global.getUriFor(
                  Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE), false, observer,
                  UserHandle.USER_ALL);
          resolver.registerContentObserver(Settings.Global.getUriFor(
                  Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS), false, observer,
                  UserHandle.USER_ALL);
      }
  
      @Override // from AbstractMasterSystemService
      protected void onSettingsChanged(int userId, @NonNull String property) {
          switch (property) {
              case Settings.Global.AUTOFILL_LOGGING_LEVEL:
                  setLogLevelFromSettings();
                  break;
              case Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE:
                  setMaxPartitionsFromSettings();
                  break;
              case Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS:
                  setMaxVisibleDatasetsFromSettings();
                  break;
              default:
                  Slog.w(TAG, "Unexpected property (" + property + "); updating cache instead");
                  // fall through
              case Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES:
                  synchronized (mLock) {
                      updateCachedServiceLocked(userId);
                  }
          }
      }
  
      private void onDeviceConfigChange(@NonNull Set<String> keys) {
          for (String key : keys) {
              switch (key) {
                  case AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES:
                  case AutofillManager.DEVICE_CONFIG_AUGMENTED_SERVICE_IDLE_UNBIND_TIMEOUT:
                  case AutofillManager.DEVICE_CONFIG_AUGMENTED_SERVICE_REQUEST_TIMEOUT:
                      setDeviceConfigProperties();
                      break;
                  default:
                      Slog.i(mTag, "Ignoring change on " + key);
              }
          }
      }
  
      private void onAugmentedServiceNameChanged(@UserIdInt int userId, @Nullable String serviceName,
              boolean isTemporary) {
          mAugmentedAutofillState.setServiceInfo(userId, serviceName, isTemporary);
          synchronized (mLock) {
              getServiceForUserLocked(userId).updateRemoteAugmentedAutofillService();
          }
      }
  
      @Override // from AbstractMasterSystemService
      protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
              boolean disabled) {
          return new AutofillManagerServiceImpl(this, mLock, mUiLatencyHistory,
                  mWtfHistory, resolvedUserId, mUi, mAutofillCompatState, disabled);
      }
  
      @Override // AbstractMasterSystemService
      protected void onServiceRemoved(@NonNull AutofillManagerServiceImpl service,
              @UserIdInt int userId) {
          service.destroyLocked();
          mAutofillCompatState.removeCompatibilityModeRequests(userId);
      }
  
      @Override // from AbstractMasterSystemService
      protected void onServiceEnabledLocked(@NonNull AutofillManagerServiceImpl service,
              @UserIdInt int userId) {
          addCompatibilityModeRequestsLocked(service, userId);
      }
  
      @Override // from AbstractMasterSystemService
      protected void enforceCallingPermissionForManagement() {
          getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
      }
  
      @Override // from SystemService
      public void onStart() {
          publishBinderService(AUTOFILL_MANAGER_SERVICE, new AutoFillManagerServiceStub());
          publishLocalService(AutofillManagerInternal.class, mLocalService);
      }
  
      @Override // from SystemService
      public void onSwitchUser(int userHandle) {
          if (sDebug) Slog.d(TAG, "Hiding UI when user switched");
          mUi.hideAll(null);
      }
  
      @SmartSuggestionMode int getSupportedSmartSuggestionModesLocked() {
          return mSupportedSmartSuggestionModes;
      }
  
      /**
       * Logs a request so it's dumped later...
       */
      void logRequestLocked(@NonNull String historyItem) {
          mRequestsHistory.log(historyItem);
      }
  
      // Called by AutofillManagerServiceImpl, doesn't need to check permission
      boolean isInstantServiceAllowed() {
          return mAllowInstantService;
      }
  
      // Called by Shell command.
      void destroySessions(@UserIdInt int userId, IResultReceiver receiver) {
          Slog.i(TAG, "destroySessions() for userId " + userId);
          enforceCallingPermissionForManagement();
  
          synchronized (mLock) {
              if (userId != UserHandle.USER_ALL) {
                  AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      service.destroySessionsLocked();
                  }
              } else {
                  visitServicesLocked((s) -> s.destroySessionsLocked());
              }
          }
  
          try {
              receiver.send(0, new Bundle());
          } catch (RemoteException e) {
              // Just ignore it...
          }
      }
  
      // Called by Shell command.
      void listSessions(int userId, IResultReceiver receiver) {
          Slog.i(TAG, "listSessions() for userId " + userId);
          enforceCallingPermissionForManagement();
  
          final Bundle resultData = new Bundle();
          final ArrayList<String> sessions = new ArrayList<>();
  
          synchronized (mLock) {
              if (userId != UserHandle.USER_ALL) {
                  AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      service.listSessionsLocked(sessions);
                  }
              } else {
                  visitServicesLocked((s) -> s.listSessionsLocked(sessions));
              }
          }
  
          resultData.putStringArrayList(RECEIVER_BUNDLE_EXTRA_SESSIONS, sessions);
          try {
              receiver.send(0, resultData);
          } catch (RemoteException e) {
              // Just ignore it...
          }
      }
  
      // Called by Shell command.
      void reset() {
          Slog.i(TAG, "reset()");
          enforceCallingPermissionForManagement();
  
          synchronized (mLock) {
              visitServicesLocked((s) -> s.destroyLocked());
              clearCacheLocked();
          }
      }
  
      // Called by Shell command.
      void setLogLevel(int level) {
          Slog.i(TAG, "setLogLevel(): " + level);
          enforceCallingPermissionForManagement();
  
          final long token = Binder.clearCallingIdentity();
          try {
              Settings.Global.putInt(getContext().getContentResolver(),
                      Settings.Global.AUTOFILL_LOGGING_LEVEL, level);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      private void setLogLevelFromSettings() {
          final int level = Settings.Global.getInt(
                  getContext().getContentResolver(),
                  Settings.Global.AUTOFILL_LOGGING_LEVEL, AutofillManager.DEFAULT_LOGGING_LEVEL);
          boolean debug = false;
          boolean verbose = false;
          if (level != AutofillManager.NO_LOGGING) {
              if (level == AutofillManager.FLAG_ADD_CLIENT_VERBOSE) {
                  debug = verbose = true;
              } else if (level == AutofillManager.FLAG_ADD_CLIENT_DEBUG) {
                  debug = true;
              } else {
                  Slog.w(TAG,  "setLogLevelFromSettings(): invalid level: " + level);
              }
          }
          if (debug || sDebug) {
              Slog.d(TAG, "setLogLevelFromSettings(): level=" + level + ", debug=" + debug
                      + ", verbose=" + verbose);
          }
          synchronized (mLock) {
              setLoggingLevelsLocked(debug, verbose);
          }
      }
  
      // Called by Shell command.
      int getLogLevel() {
          enforceCallingPermissionForManagement();
  
          synchronized (mLock) {
              if (sVerbose) return AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
              if (sDebug) return AutofillManager.FLAG_ADD_CLIENT_DEBUG;
              return 0;
          }
      }
  
      // Called by Shell command.
      int getMaxPartitions() {
          enforceCallingPermissionForManagement();
  
          synchronized (mLock) {
              return sPartitionMaxCount;
          }
      }
  
      // Called by Shell command.
      void setMaxPartitions(int max) {
          Slog.i(TAG, "setMaxPartitions(): " + max);
          enforceCallingPermissionForManagement();
  
          final long token = Binder.clearCallingIdentity();
          try {
              Settings.Global.putInt(getContext().getContentResolver(),
                      Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE, max);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      private void setMaxPartitionsFromSettings() {
          final int max = Settings.Global.getInt(getContext().getContentResolver(),
                  Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE,
                  AutofillManager.DEFAULT_MAX_PARTITIONS_SIZE);
          if (sDebug) Slog.d(TAG, "setMaxPartitionsFromSettings(): " + max);
  
          synchronized (sLock) {
              sPartitionMaxCount = max;
          }
      }
  
      // Called by Shell command.
      int getMaxVisibleDatasets() {
          enforceCallingPermissionForManagement();
  
          synchronized (sLock) {
              return sVisibleDatasetsMaxCount;
          }
      }
  
      // Called by Shell command.
      void setMaxVisibleDatasets(int max) {
          Slog.i(TAG, "setMaxVisibleDatasets(): " + max);
          enforceCallingPermissionForManagement();
  
          final long token = Binder.clearCallingIdentity();
          try {
              Settings.Global.putInt(getContext().getContentResolver(),
                      Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, max);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      private void setMaxVisibleDatasetsFromSettings() {
          final int max = Settings.Global.getInt(getContext().getContentResolver(),
                  Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, 0);
  
          if (sDebug) Slog.d(TAG, "setMaxVisibleDatasetsFromSettings(): " + max);
          synchronized (sLock) {
              sVisibleDatasetsMaxCount = max;
          }
      }
  
      private void setDeviceConfigProperties() {
          synchronized (mLock) {
              mAugmentedServiceIdleUnbindTimeoutMs = DeviceConfig.getInt(
                      DeviceConfig.NAMESPACE_AUTOFILL,
                      AutofillManager.DEVICE_CONFIG_AUGMENTED_SERVICE_IDLE_UNBIND_TIMEOUT,
                      (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
              mAugmentedServiceRequestTimeoutMs = DeviceConfig.getInt(
                      DeviceConfig.NAMESPACE_AUTOFILL,
                      AutofillManager.DEVICE_CONFIG_AUGMENTED_SERVICE_REQUEST_TIMEOUT,
                      DEFAULT_AUGMENTED_AUTOFILL_REQUEST_TIMEOUT_MILLIS);
              mSupportedSmartSuggestionModes = DeviceConfig.getInt(
                      DeviceConfig.NAMESPACE_AUTOFILL,
                      AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES,
                      AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM);
              if (verbose) {
                  Slog.v(mTag, "setDeviceConfigProperties(): "
                          + "augmentedIdleTimeout=" + mAugmentedServiceIdleUnbindTimeoutMs
                          + ", augmentedRequestTimeout=" + mAugmentedServiceRequestTimeoutMs
                          + ", smartSuggestionMode="
                          + getSmartSuggestionModeToString(mSupportedSmartSuggestionModes));
              }
          }
      }
  
      // Called by Shell command.
      void calculateScore(@Nullable String algorithmName, @NonNull String value1,
              @NonNull String value2, @NonNull RemoteCallback callback) {
          enforceCallingPermissionForManagement();
  
          final FieldClassificationStrategy strategy =
                  new FieldClassificationStrategy(getContext(), UserHandle.USER_CURRENT);
  
          strategy.calculateScores(callback, Arrays.asList(AutofillValue.forText(value1)),
                  new String[] { value2 }, new String[] { null }, algorithmName, null, null, null);
      }
  
      // Called by Shell command.
      Boolean getFullScreenMode() {
          enforceCallingPermissionForManagement();
          return sFullScreenMode;
      }
  
      // Called by Shell command.
      void setFullScreenMode(@Nullable Boolean mode) {
          enforceCallingPermissionForManagement();
          sFullScreenMode = mode;
      }
  
      // Called by Shell command.
      void setTemporaryAugmentedAutofillService(@UserIdInt int userId, @NonNull String serviceName,
              int durationMs) {
          Slog.i(mTag, "setTemporaryAugmentedAutofillService(" + userId + ") to " + serviceName
                  + " for " + durationMs + "ms");
          enforceCallingPermissionForManagement();
  
          Preconditions.checkNotNull(serviceName);
          if (durationMs > MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS) {
              throw new IllegalArgumentException("Max duration is "
                      + MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS + " (called with " + durationMs + ")");
          }
  
          mAugmentedAutofillResolver.setTemporaryService(userId, serviceName, durationMs);
      }
  
      // Called by Shell command
      void resetTemporaryAugmentedAutofillService(@UserIdInt int userId) {
          enforceCallingPermissionForManagement();
          mAugmentedAutofillResolver.resetTemporaryService(userId);
      }
  
      // Called by Shell command
      boolean isDefaultAugmentedServiceEnabled(@UserIdInt int userId) {
          enforceCallingPermissionForManagement();
          return mAugmentedAutofillResolver.isDefaultServiceEnabled(userId);
      }
  
      // Called by Shell command
      boolean setDefaultAugmentedServiceEnabled(@UserIdInt int userId, boolean enabled) {
          Slog.i(mTag, "setDefaultAugmentedServiceEnabled() for userId " + userId + ": " + enabled);
          enforceCallingPermissionForManagement();
  
          synchronized (mLock) {
              final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
              if (service != null) {
                  final boolean changed = mAugmentedAutofillResolver
                          .setDefaultServiceEnabled(userId, enabled);
                  if (changed) {
                      service.updateRemoteAugmentedAutofillService();
                      return true;
                  } else {
                      if (debug) {
                          Slog.d(TAG, "setDefaultAugmentedServiceEnabled(): already " + enabled);
                      }
                  }
              }
          }
          return false;
      }
  
      private void setLoggingLevelsLocked(boolean debug, boolean verbose) {
          com.android.server.autofill.Helper.sDebug = debug;
          android.view.autofill.Helper.sDebug = debug;
          this.debug = debug;
  
          com.android.server.autofill.Helper.sVerbose = verbose;
          android.view.autofill.Helper.sVerbose = verbose;
          this.verbose = verbose;
      }
  
      private void addCompatibilityModeRequestsLocked(@NonNull AutofillManagerServiceImpl service
              , int userId) {
          mAutofillCompatState.reset(userId);
          final ArrayMap<String, Long> compatPackages =
                  service.getCompatibilityPackagesLocked();
          if (compatPackages == null || compatPackages.isEmpty()) {
              return;
          }
  
          final Map<String, String[]> whiteListedPackages = getWhitelistedCompatModePackages();
          final int compatPackageCount = compatPackages.size();
          for (int i = 0; i < compatPackageCount; i++) {
              final String packageName = compatPackages.keyAt(i);
              if (whiteListedPackages == null || !whiteListedPackages.containsKey(packageName)) {
                  Slog.w(TAG, "Ignoring not whitelisted compat package " + packageName);
                  continue;
              }
              final Long maxVersionCode = compatPackages.valueAt(i);
              if (maxVersionCode != null) {
                  mAutofillCompatState.addCompatibilityModeRequest(packageName,
                          maxVersionCode, whiteListedPackages.get(packageName), userId);
              }
          }
      }
  
      private String getWhitelistedCompatModePackagesFromSettings() {
          return Settings.Global.getString(
                  getContext().getContentResolver(),
                  Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES);
      }
  
      @Nullable
      private Map<String, String[]> getWhitelistedCompatModePackages() {
          return getWhitelistedCompatModePackages(getWhitelistedCompatModePackagesFromSettings());
      }
  
      private void send(@NonNull IResultReceiver receiver, int value) {
          try {
              receiver.send(value, null);
          } catch (RemoteException e) {
              Slog.w(TAG, "Error async reporting result to client: " + e);
          }
      }
  
      private void send(@NonNull IResultReceiver receiver, @NonNull Bundle value) {
          try {
              receiver.send(0, value);
          } catch (RemoteException e) {
              Slog.w(TAG, "Error async reporting result to client: " + e);
          }
      }
  
      private void send(@NonNull IResultReceiver receiver, @Nullable String value) {
          send(receiver, SyncResultReceiver.bundleFor(value));
      }
  
      private void send(@NonNull IResultReceiver receiver, @Nullable String[] value) {
          send(receiver, SyncResultReceiver.bundleFor(value));
      }
  
      private void send(@NonNull IResultReceiver receiver, @Nullable Parcelable value) {
          send(receiver, SyncResultReceiver.bundleFor(value));
      }
  
      private void send(@NonNull IResultReceiver receiver, boolean value) {
          send(receiver, value ? 1 : 0);
      }
  
      private void send(@NonNull IResultReceiver receiver, int value1, int value2) {
          try {
              receiver.send(value1, SyncResultReceiver.bundleFor(value2));
          } catch (RemoteException e) {
              Slog.w(TAG, "Error async reporting result to client: " + e);
          }
      }
  
      @Nullable
      @VisibleForTesting
      static Map<String, String[]> getWhitelistedCompatModePackages(String setting) {
          if (TextUtils.isEmpty(setting)) {
              return null;
          }
  
          final ArrayMap<String, String[]> compatPackages = new ArrayMap<>();
          final SimpleStringSplitter splitter = new SimpleStringSplitter(COMPAT_PACKAGE_DELIMITER);
          splitter.setString(setting);
          while (splitter.hasNext()) {
              final String packageBlock = splitter.next();
              final int urlBlockIndex = packageBlock.indexOf(COMPAT_PACKAGE_URL_IDS_BLOCK_BEGIN);
              final String packageName;
              final List<String> urlBarIds;
              if (urlBlockIndex == -1) {
                  packageName = packageBlock;
                  urlBarIds = null;
              } else {
                  if (packageBlock.charAt(packageBlock.length() - 1)
                          != COMPAT_PACKAGE_URL_IDS_BLOCK_END) {
                      Slog.w(TAG, "Ignoring entry '" + packageBlock + "' on '" + setting
                              + "'because it does not end on '" + COMPAT_PACKAGE_URL_IDS_BLOCK_END +
                              "'");
                      continue;
                  }
                  packageName = packageBlock.substring(0, urlBlockIndex);
                  urlBarIds = new ArrayList<>();
                  final String urlBarIdsBlock =
                          packageBlock.substring(urlBlockIndex + 1, packageBlock.length() - 1);
                  if (sVerbose) {
                      Slog.v(TAG, "pkg:" + packageName + ": block:" + packageBlock + ": urls:"
                              + urlBarIds + ": block:" + urlBarIdsBlock + ":");
                  }
                  final SimpleStringSplitter splitter2 =
                          new SimpleStringSplitter(COMPAT_PACKAGE_URL_IDS_DELIMITER);
                  splitter2.setString(urlBarIdsBlock);
                  while (splitter2.hasNext()) {
                      final String urlBarId = splitter2.next();
                      urlBarIds.add(urlBarId);
                  }
              }
              if (urlBarIds == null) {
                  compatPackages.put(packageName, null);
              } else {
                  final String[] urlBarIdsArray = new String[urlBarIds.size()];
                  urlBarIds.toArray(urlBarIdsArray);
                  compatPackages.put(packageName, urlBarIdsArray);
              }
          }
          return compatPackages;
      }
  
      /**
       * Gets the maximum number of partitions / fill requests.
       */
      public static int getPartitionMaxCount() {
          synchronized (sLock) {
              return sPartitionMaxCount;
          }
      }
  
      /**
       * Gets the maxium number of datasets visible in the UI.
       */
      public static int getVisibleDatasetsMaxCount() {
          synchronized (sLock) {
              return sVisibleDatasetsMaxCount;
          }
      }
  
      private final class LocalService extends AutofillManagerInternal {
          @Override
          public void onBackKeyPressed() {
              if (sDebug) Slog.d(TAG, "onBackKeyPressed()");
              mUi.hideAll(null);
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service =
                          getServiceForUserLocked(UserHandle.getCallingUserId());
                  service.onBackKeyPressed();
              }
          }
  
          @Override
          public AutofillOptions getAutofillOptions(@NonNull String packageName,
                  long versionCode, @UserIdInt int userId) {
              final int loggingLevel;
              if (verbose) {
                  loggingLevel = AutofillManager.FLAG_ADD_CLIENT_VERBOSE
                          | AutofillManager.FLAG_ADD_CLIENT_DEBUG;
              } else if (debug) {
                  loggingLevel = AutofillManager.FLAG_ADD_CLIENT_DEBUG;
              } else {
                  loggingLevel = AutofillManager.NO_LOGGING;
              }
              final boolean compatModeEnabled = mAutofillCompatState.isCompatibilityModeRequested(
                      packageName, versionCode, userId);
              final AutofillOptions options = new AutofillOptions(loggingLevel, compatModeEnabled);
              mAugmentedAutofillState.injectAugmentedAutofillInfo(options, userId, packageName);
              return options;
          }
  
          @Override
          public boolean isAugmentedAutofillServiceForUser(int callingUid, int userId) {
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      return service.isAugmentedAutofillServiceForUserLocked(callingUid);
                  }
              }
              return false;
          }
      }
  
      /**
       * Compatibility mode metadata per package.
       */
      static final class PackageCompatState {
          private final long maxVersionCode;
          private final String[] urlBarResourceIds;
  
          PackageCompatState(long maxVersionCode, String[] urlBarResourceIds) {
              this.maxVersionCode = maxVersionCode;
              this.urlBarResourceIds = urlBarResourceIds;
          }
  
          @Override
          public String toString() {
              return "maxVersionCode=" + maxVersionCode
                      + ", urlBarResourceIds=" + Arrays.toString(urlBarResourceIds);
          }
      }
  
      /**
       * Compatibility mode metadata associated with all services.
       *
       * <p>This object is defined here instead of on each {@link AutofillManagerServiceImpl} because
       * it cannot hold a lock on the main lock when
       * {@link AutofillCompatState#isCompatibilityModeRequested(String, long, int)} is called by
       * external services.
       */
      static final class AutofillCompatState {
          private final Object mLock = new Object();
  
          /**
           * Map of app->compat_state per user.
           */
          @GuardedBy("mLock")
          private SparseArray<ArrayMap<String, PackageCompatState>> mUserSpecs;
  
          boolean isCompatibilityModeRequested(@NonNull String packageName,
                  long versionCode, @UserIdInt int userId) {
              synchronized (mLock) {
                  if (mUserSpecs == null) {
                      return false;
                  }
                  final ArrayMap<String, PackageCompatState> userSpec = mUserSpecs.get(userId);
                  if (userSpec == null) {
                      return false;
                  }
                  final PackageCompatState metadata = userSpec.get(packageName);
                  if (metadata == null) {
                      return false;
                  }
                  return versionCode <= metadata.maxVersionCode;
              }
          }
  
          @Nullable
          String[] getUrlBarResourceIds(@NonNull String packageName, @UserIdInt int userId) {
              synchronized (mLock) {
                  if (mUserSpecs == null) {
                      return null;
                  }
                  final ArrayMap<String, PackageCompatState> userSpec = mUserSpecs.get(userId);
                  if (userSpec == null) {
                      return null;
                  }
                  final PackageCompatState metadata = userSpec.get(packageName);
                  if (metadata == null) {
                      return null;
                  }
                  return metadata.urlBarResourceIds;
              }
          }
  
          void addCompatibilityModeRequest(@NonNull String packageName,
                  long versionCode, @Nullable String[] urlBarResourceIds, @UserIdInt int userId) {
              synchronized (mLock) {
                  if (mUserSpecs == null) {
                      mUserSpecs = new SparseArray<>();
                  }
                  ArrayMap<String, PackageCompatState> userSpec = mUserSpecs.get(userId);
                  if (userSpec == null) {
                      userSpec = new ArrayMap<>();
                      mUserSpecs.put(userId, userSpec);
                  }
                  userSpec.put(packageName,
                          new PackageCompatState(versionCode, urlBarResourceIds));
              }
          }
  
          void removeCompatibilityModeRequests(@UserIdInt int userId) {
              synchronized (mLock) {
                  if (mUserSpecs != null) {
                      mUserSpecs.remove(userId);
                      if (mUserSpecs.size() <= 0) {
                          mUserSpecs = null;
                      }
                  }
              }
          }
  
          void reset(int userId) {
              synchronized (mLock) {
                  if (mUserSpecs != null) {
                      mUserSpecs.delete(userId);
                      final int newSize = mUserSpecs.size();
                      if (newSize == 0) {
                          if (sVerbose) Slog.v(TAG, "reseting mUserSpecs");
                          mUserSpecs = null;
                      } else {
                          if (sVerbose) Slog.v(TAG, "mUserSpecs down to " + newSize);
                      }
                  }
              }
          }
  
          private void dump(String prefix, PrintWriter pw) {
              synchronized (mLock) {
                  if (mUserSpecs == null) {
                      pw.println("N/A");
                      return;
                  }
                  pw.println();
                  final String prefix2 = prefix + "  ";
                  for (int i = 0; i < mUserSpecs.size(); i++) {
                      final int user = mUserSpecs.keyAt(i);
                      pw.print(prefix);
                      pw.print("User: ");
                      pw.println(user);
                      final ArrayMap<String, PackageCompatState> perUser = mUserSpecs.valueAt(i);
                      for (int j = 0; j < perUser.size(); j++) {
                          final String packageName = perUser.keyAt(j);
                          final PackageCompatState state = perUser.valueAt(j);
                          pw.print(prefix2); pw.print(packageName); pw.print(": "); pw.println(state);
                      }
                  }
              }
          }
      }
  
      /**
       * Augmented autofill metadata associated with all services.
       *
       * <p>This object is defined here instead of on each {@link AutofillManagerServiceImpl} because
       * it cannot hold a lock on the main lock when
       * {@link AugmentedAutofillState#injectAugmentedAutofillInfo(AutofillOptions, int, String)}
       * is called by external services.
       */
      static final class AugmentedAutofillState extends GlobalWhitelistState {
  
          @GuardedBy("mGlobalWhitelistStateLock")
          private final SparseArray<String> mServicePackages = new SparseArray<>();
          @GuardedBy("mGlobalWhitelistStateLock")
          private final SparseBooleanArray mTemporaryServices = new SparseBooleanArray();
  
          private void setServiceInfo(@UserIdInt int userId, @Nullable String serviceName,
                  boolean isTemporary) {
              synchronized (mGlobalWhitelistStateLock) {
                  if (isTemporary) {
                      mTemporaryServices.put(userId, true);
                  } else {
                      mTemporaryServices.delete(userId);
                  }
                  if (serviceName != null) {
                      final ComponentName componentName =
                              ComponentName.unflattenFromString(serviceName);
                      if (componentName == null) {
                          Slog.w(TAG, "setServiceInfo(): invalid name: " + serviceName);
                          mServicePackages.remove(userId);
                      } else {
                          mServicePackages.put(userId, componentName.getPackageName());
                      }
                  } else {
                      mServicePackages.remove(userId);
                  }
              }
          }
  
          public void injectAugmentedAutofillInfo(@NonNull AutofillOptions options,
                  @UserIdInt int userId, @NonNull String packageName) {
              synchronized (mGlobalWhitelistStateLock) {
                  if (mWhitelisterHelpers == null) return;
                  final WhitelistHelper helper = mWhitelisterHelpers.get(userId);
                  if (helper != null) {
                      options.augmentedAutofillEnabled = helper.isWhitelisted(packageName);
                      options.whitelistedActivitiesForAugmentedAutofill = helper
                              .getWhitelistedComponents(packageName);
                  }
              }
          }
  
          @Override
          public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
              synchronized (mGlobalWhitelistStateLock) {
                  if (!super.isWhitelisted(userId, componentName)) return false;
  
                  if (Build.IS_USER && mTemporaryServices.get(userId)) {
                      final String packageName = componentName.getPackageName();
                      if (!packageName.equals(mServicePackages.get(userId))) {
                          Slog.w(TAG, "Ignoring package " + packageName + " for augmented autofill "
                                  + "while using temporary service " + mServicePackages.get(userId));
                          return false;
                      }
                  }
              }
              return true;
          }
  
          @Override
          public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
              super.dump(prefix, pw);
  
              synchronized (mGlobalWhitelistStateLock) {
                  if (mServicePackages.size() > 0) {
                      pw.print(prefix); pw.print("Service packages: "); pw.println(mServicePackages);
                  }
                  if (mTemporaryServices.size() > 0) {
                      pw.print(prefix); pw.print("Temp services: "); pw.println(mTemporaryServices);
                  }
              }
          }
      }
  
      final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
          @Override
          public void addClient(IAutoFillManagerClient client, ComponentName componentName,
                  int userId, IResultReceiver receiver) {
              int flags = 0;
              synchronized (mLock) {
                  final int enabledFlags = getServiceForUserLocked(userId).addClientLocked(client,
                          componentName);
                  if (enabledFlags != 0) {
                      flags |= enabledFlags;
                  }
                  if (sDebug) {
                      flags |= AutofillManager.FLAG_ADD_CLIENT_DEBUG;
                  }
                  if (sVerbose) {
                      flags |= AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
                  }
              }
              send(receiver, flags);
          }
  
          @Override
          public void removeClient(IAutoFillManagerClient client, int userId) {
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      service.removeClientLocked(client);
                  } else if (sVerbose) {
                      Slog.v(TAG, "removeClient(): no service for " + userId);
                  }
              }
          }
  
          @Override
          public void setAuthenticationResult(Bundle data, int sessionId, int authenticationId,
                  int userId) {
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
                  service.setAuthenticationResultLocked(data, sessionId, authenticationId,
                          getCallingUid());
              }
          }
  
          @Override
          public void setHasCallback(int sessionId, int userId, boolean hasIt) {
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
                  service.setHasCallback(sessionId, getCallingUid(), hasIt);
              }
          }
  
          @Override
          public void startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
                  Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
                  ComponentName componentName, boolean compatMode, IResultReceiver receiver) {
  
              activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
              appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
              autofillId = Preconditions.checkNotNull(autofillId, "autoFillId");
              componentName = Preconditions.checkNotNull(componentName, "componentName");
              final String packageName = Preconditions.checkNotNull(componentName.getPackageName());
  
              Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId");
  
              try {
                  getContext().getPackageManager().getPackageInfoAsUser(packageName, 0, userId);
              } catch (PackageManager.NameNotFoundException e) {
                  throw new IllegalArgumentException(packageName + " is not a valid package", e);
              }
  
              // TODO(b/113281366): add a callback method on AM to be notified when a task is finished
              // so we can clean up sessions kept alive
              final int taskId = mAm.getTaskIdForActivity(activityToken, false);
              final long result;
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
                  result = service.startSessionLocked(activityToken, taskId, getCallingUid(),
                          appCallback, autofillId, bounds, value, hasCallback, componentName,
                          compatMode, mAllowInstantService, flags);
              }
              final int sessionId = (int) result;
              final int resultFlags = (int) (result >> 32);
              if (resultFlags != 0) {
                  send(receiver, sessionId, resultFlags);
              } else {
                  send(receiver, sessionId);
              }
          }
  
          @Override
          public void getFillEventHistory(@NonNull IResultReceiver receiver) throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
  
              FillEventHistory fillEventHistory = null;
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      fillEventHistory = service.getFillEventHistory(getCallingUid());
                  } else if (sVerbose) {
                      Slog.v(TAG, "getFillEventHistory(): no service for " + userId);
                  }
              }
              send(receiver, fillEventHistory);
          }
  
          @Override
          public void getUserData(@NonNull IResultReceiver receiver) throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
  
              UserData userData = null;
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      userData = service.getUserData(getCallingUid());
                  } else if (sVerbose) {
                      Slog.v(TAG, "getUserData(): no service for " + userId);
                  }
              }
              send(receiver, userData);
          }
  
          @Override
          public void getUserDataId(@NonNull IResultReceiver receiver) throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
              UserData userData = null;
  
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      userData = service.getUserData(getCallingUid());
                  } else if (sVerbose) {
                      Slog.v(TAG, "getUserDataId(): no service for " + userId);
                  }
              }
              final String userDataId = userData == null ? null : userData.getId();
              send(receiver, userDataId);
          }
  
          @Override
          public void setUserData(UserData userData) throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
  
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      service.setUserData(getCallingUid(), userData);
                  } else if (sVerbose) {
                      Slog.v(TAG, "setUserData(): no service for " + userId);
                  }
              }
          }
  
          @Override
          public void isFieldClassificationEnabled(@NonNull IResultReceiver receiver)
                  throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
              boolean enabled = false;
  
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      enabled = service.isFieldClassificationEnabled(getCallingUid());
                  } else if (sVerbose) {
                      Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId);
                  }
              }
              send(receiver, enabled);
          }
  
          @Override
          public void getDefaultFieldClassificationAlgorithm(@NonNull IResultReceiver receiver)
                  throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
              String algorithm = null;
  
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      algorithm = service.getDefaultFieldClassificationAlgorithm(getCallingUid());
                  } else {
                      if (sVerbose) {
                          Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
                      }
                 }
              }
              send(receiver, algorithm);
          }
  
          @Override
          public void setAugmentedAutofillWhitelist(@Nullable List<String> packages,
                  @Nullable List<ComponentName> activities, @NonNull IResultReceiver receiver)
                  throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
  
              boolean ok;
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      ok = service.setAugmentedAutofillWhitelistLocked(packages, activities,
                              getCallingUid());
                  } else {
                      if (sVerbose) {
                          Slog.v(TAG, "setAugmentedAutofillWhitelist(): no service for " + userId);
                      }
                      ok = false;
                  }
              }
              send(receiver,
                      ok ? AutofillManager.RESULT_OK : AutofillManager.RESULT_CODE_NOT_SERVICE);
          }
  
          @Override
          public void getAvailableFieldClassificationAlgorithms(@NonNull IResultReceiver receiver)
                  throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
              String[] algorithms = null;
  
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      algorithms = service.getAvailableFieldClassificationAlgorithms(getCallingUid());
                  } else {
                      if (sVerbose) {
                          Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
                      }
                  }
              }
              send(receiver, algorithms);
          }
  
          @Override
          public void getAutofillServiceComponentName(@NonNull IResultReceiver receiver)
                  throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
  
              ComponentName componentName = null;
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      componentName = service.getServiceComponentName();
                  } else if (sVerbose) {
                      Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId);
                  }
              }
              send(receiver, componentName);
          }
  
          @Override
          public void restoreSession(int sessionId, @NonNull IBinder activityToken,
                  @NonNull IBinder appCallback, @NonNull IResultReceiver receiver)
                  throws RemoteException {
              final int userId = UserHandle.getCallingUserId();
              activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
              appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
  
              boolean restored = false;
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      restored = service.restoreSession(sessionId, getCallingUid(), activityToken,
                              appCallback);
                  } else if (sVerbose) {
                      Slog.v(TAG, "restoreSession(): no service for " + userId);
                  }
              }
              send(receiver, restored);
          }
  
          @Override
          public void updateSession(int sessionId, AutofillId autoFillId, Rect bounds,
                  AutofillValue value, int action, int flags, int userId) {
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      service.updateSessionLocked(sessionId, getCallingUid(), autoFillId, bounds,
                              value, action, flags);
                  } else if (sVerbose) {
                      Slog.v(TAG, "updateSession(): no service for " + userId);
                  }
              }
          }
  
          @Override
          public void setAutofillFailure(int sessionId, @NonNull List<AutofillId> ids, int userId) {
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      service.setAutofillFailureLocked(sessionId, getCallingUid(), ids);
                  } else if (sVerbose) {
                      Slog.v(TAG, "setAutofillFailure(): no service for " + userId);
                  }
              }
          }
  
          @Override
          public void finishSession(int sessionId, int userId) {
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      service.finishSessionLocked(sessionId, getCallingUid());
                  } else if (sVerbose) {
                      Slog.v(TAG, "finishSession(): no service for " + userId);
                  }
              }
          }
  
          @Override
          public void cancelSession(int sessionId, int userId) {
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      service.cancelSessionLocked(sessionId, getCallingUid());
                  } else if (sVerbose) {
                      Slog.v(TAG, "cancelSession(): no service for " + userId);
                  }
              }
          }
  
          @Override
          public void disableOwnedAutofillServices(int userId) {
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      service.disableOwnedAutofillServicesLocked(Binder.getCallingUid());
                  } else if (sVerbose) {
                      Slog.v(TAG, "cancelSession(): no service for " + userId);
                  }
              }
          }
  
          @Override
          public void isServiceSupported(int userId, @NonNull IResultReceiver receiver) {
              boolean supported = false;
              synchronized (mLock) {
                  supported = !isDisabledLocked(userId);
              }
              send(receiver, supported);
          }
  
          @Override
          public void isServiceEnabled(int userId, @NonNull String packageName,
                  @NonNull IResultReceiver receiver) {
              boolean enabled = false;
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                  if (service != null) {
                      enabled = Objects.equals(packageName, service.getServicePackageName());
                  } else if (sVerbose) {
                      Slog.v(TAG, "isServiceEnabled(): no service for " + userId);
                  }
              }
              send(receiver, enabled);
          }
  
          @Override
          public void onPendingSaveUi(int operation, IBinder token) {
              Preconditions.checkNotNull(token, "token");
              Preconditions.checkArgument(operation == AutofillManager.PENDING_UI_OPERATION_CANCEL
                      || operation == AutofillManager.PENDING_UI_OPERATION_RESTORE,
                      "invalid operation: %d", operation);
              synchronized (mLock) {
                  final AutofillManagerServiceImpl service = peekServiceForUserLocked(
                          UserHandle.getCallingUserId());
                  if (service != null) {
                      service.onPendingSaveUi(operation, token);
                  }
              }
          }
  
          @Override
          public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
              if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
  
              boolean showHistory = true;
              boolean uiOnly = false;
              if (args != null) {
                  for (String arg : args) {
                      switch(arg) {
                          case "--no-history":
                              showHistory = false;
                              break;
                          case "--ui-only":
                              uiOnly = true;
                              break;
                          case "--help":
                              pw.println("Usage: dumpsys autofill [--ui-only|--no-history]");
                              return;
                          default:
                              Slog.w(TAG, "Ignoring invalid dump arg: " + arg);
                      }
                  }
              }
  
              if (uiOnly) {
                  mUi.dump(pw);
                  return;
              }
  
              final String prefix = "  ";
              boolean realDebug = sDebug;
              boolean realVerbose = sVerbose;
              try {
                  sDebug = sVerbose = true;
                  synchronized (mLock) {
                      pw.print("sDebug: "); pw.print(realDebug);
                      pw.print(" sVerbose: "); pw.println(realVerbose);
                      // Dump per-user services
                      dumpLocked("", pw);
                      mAugmentedAutofillResolver.dumpShort(pw); pw.println();
                      pw.print("Max partitions per session: "); pw.println(sPartitionMaxCount);
                      pw.print("Max visible datasets: "); pw.println(sVisibleDatasetsMaxCount);
                      if (sFullScreenMode != null) {
                          pw.print("Overridden full-screen mode: "); pw.println(sFullScreenMode);
                      }
                      pw.println("User data constraints: "); UserData.dumpConstraints(prefix, pw);
                      mUi.dump(pw);
                      pw.print("Autofill Compat State: ");
                      mAutofillCompatState.dump(prefix, pw);
                      pw.print("from settings: ");
                      pw.println(getWhitelistedCompatModePackagesFromSettings());
                      if (mSupportedSmartSuggestionModes != 0) {
                          pw.print("Smart Suggestion modes: ");
                          pw.println(getSmartSuggestionModeToString(mSupportedSmartSuggestionModes));
                      }
                      pw.print("Augmented Service Idle Unbind Timeout: ");
                      pw.println(mAugmentedServiceIdleUnbindTimeoutMs);
                      pw.print("Augmented Service Request Timeout: ");
                      pw.println(mAugmentedServiceRequestTimeoutMs);
                      if (showHistory) {
                          pw.println(); pw.println("Requests history:"); pw.println();
                          mRequestsHistory.reverseDump(fd, pw, args);
                          pw.println(); pw.println("UI latency history:"); pw.println();
                          mUiLatencyHistory.reverseDump(fd, pw, args);
                          pw.println(); pw.println("WTF history:"); pw.println();
                          mWtfHistory.reverseDump(fd, pw, args);
                      }
                      pw.println("Augmented Autofill State: ");
                      mAugmentedAutofillState.dump(prefix, pw);
                  }
              } finally {
                  sDebug = realDebug;
                  sVerbose = realVerbose;
              }
          }
  
          @Override
          public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                  String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
              new AutofillManagerServiceShellCommand(AutofillManagerService.this).exec(
                      this, in, out, err, args, callback, resultReceiver);
          }
      }
  }
  