Merge "Added a UserManager.DISALLOW_AUTOFILL restriction."
diff --git a/api/current.txt b/api/current.txt
index 42d45ff..e124d09 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31652,6 +31652,7 @@
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
     field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
     field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
+    field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill";
     field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
diff --git a/api/system-current.txt b/api/system-current.txt
index 4a7ee51..9daf8f0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -34466,6 +34466,7 @@
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
     field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
     field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
+    field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill";
     field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
diff --git a/api/test-current.txt b/api/test-current.txt
index 292b505..8b2ca05 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -31777,6 +31777,7 @@
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
     field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
     field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
+    field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill";
     field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 827a77f..deb5b31 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -1584,6 +1584,7 @@
         System.err.println("       pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
+        System.err.println("       pm set-user-restriction [--user USER_ID] RESTRICTION VALUE");
         System.err.println("       pm hide [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm grant [--user USER_ID] PACKAGE PERMISSION");
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f6712f8..42575b6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -706,6 +706,20 @@
             = "allow_parent_profile_app_linking";
 
     /**
+     * Specifies if a user is not allowed to use Autofill Services.
+     *
+     * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
+     * only the target user will be affected.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_AUTOFILL = "no_autofill";
+
+    /**
      * Application restriction key that is used to indicate the pending arrival
      * of real restrictions for the app.
      *
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 709e5f9..6f17d0e 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -228,8 +228,11 @@
      */
     //TODO(b/33197203): remove once clients are not using anymore
     @Deprecated
-    public abstract void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data,
-            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
+    public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data,
+            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) {
+        // Should never be called because it was abstract before.
+        throw new UnsupportedOperationException("deprecated");
+    }
 
     /**
      * Called when user requests service to save the fields of an {@link Activity}.
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 47e7803..484b875 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -208,7 +208,7 @@
         }
 
         /**
-         * Sets the value of a field, usin a custom presentation to visualize it.
+         * Sets the value of a field, using a custom presentation to visualize it.
          *
          * @param id id returned by {@link
          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
@@ -257,8 +257,7 @@
             throwIfDestroyed();
             mDestroyed = true;
             if (mFieldIds == null) {
-                throw new IllegalArgumentException(
-                        "at least one value must be set");
+                throw new IllegalArgumentException("at least one value must be set");
             }
             return new Dataset(this);
         }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index c7ba1ff..a77e533 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -18,7 +18,10 @@
 
 import static android.Manifest.permission.MANAGE_AUTO_FILL;
 import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
+
+import static com.android.server.autofill.Helper.DEBUG;
 import static com.android.server.autofill.Helper.VERBOSE;
+import static com.android.server.autofill.Helper.bundleToString;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -28,8 +31,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.graphics.Rect;
 import android.net.Uri;
@@ -41,16 +44,19 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.provider.Settings;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.autofill.AutofillId;
 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.os.BackgroundThread;
 import com.android.internal.os.IResultReceiver;
@@ -90,20 +96,20 @@
      * It has to be mapped by user id because the same current user could have simultaneous sessions
      * associated to different user profiles (for example, in a multi-window environment or when
      * device has work profiles).
-     * <p>
-     * Entries on this cache are added on demand and removed when:
-     * <ol>
-     *   <li>An autofill service app is removed.
-     *   <li>The {@link android.provider.Settings.Secure#AUTOFILL_SERVICE} for an user change.
-     * </ol>
      */
-    // TODO(b/33197203): Update the above comment
     @GuardedBy("mLock")
     private SparseArray<AutofillManagerServiceImpl> mServicesCache = new SparseArray<>();
 
+    /**
+     * Users disabled due to {@link UserManager} restrictions.
+     */
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mDisabledUsers = new SparseBooleanArray();
+
     // TODO(b/33197203): set a different max (or disable it) on low-memory devices.
     private final LocalLog mRequestsHistory = new LocalLog(20);
 
+    // TODO(b/33197203): is this still needed?
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -122,10 +128,38 @@
         mContext = context;
         mUi = new AutoFillUI(mContext);
 
-        IntentFilter filter = new IntentFilter();
+        final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        mContext.registerReceiver(mBroadcastReceiver, filter, null,
-                FgThread.getHandler());
+        mContext.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler());
+
+        // 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, UserManager.DISALLOW_AUTOFILL);
+            if (disabled) {
+                mDisabledUsers.put(userId, disabled);
+            }
+        }
+        umi.addUserRestrictionsListener((userId, newRestrictions, prevRestrictions) -> {
+            final boolean disabledNow =
+                    newRestrictions.getBoolean(UserManager.DISALLOW_AUTOFILL, false);
+            synchronized (mLock) {
+                final boolean disabledBefore = mDisabledUsers.get(userId);
+                if (disabledBefore == disabledNow) {
+                    // Nothing changed, do nothing.
+                    if (DEBUG) {
+                        Slog.d(TAG, "Restriction not changed for user " + userId + ": "
+                                + bundleToString(newRestrictions));
+                        return;
+                    }
+                }
+                mDisabledUsers.put(userId, disabledNow);
+                updateCachedServiceLocked(userId, disabledNow);
+            }
+        });
     }
 
     @Override
@@ -164,7 +198,7 @@
         AutofillManagerServiceImpl service = mServicesCache.get(userId);
         if (service == null) {
             service = new AutofillManagerServiceImpl(mContext, mLock,
-                    mRequestsHistory, userId, mUi);
+                    mRequestsHistory, userId, mUi, mDisabledUsers.get(userId));
             mServicesCache.put(userId, service);
         }
         return service;
@@ -272,9 +306,16 @@
      * Updates a cached service for a given user.
      */
     private void updateCachedServiceLocked(int userId) {
+        updateCachedServiceLocked(userId, mDisabledUsers.get(userId));
+    }
+
+    /**
+     * Updates a cached service for a given user.
+     */
+    private void updateCachedServiceLocked(int userId, boolean disabled) {
         AutofillManagerServiceImpl service = mServicesCache.get(userId);
         if (service != null) {
-            service.updateLocked();
+            service.updateLocked(disabled);
         }
     }
 
@@ -386,6 +427,7 @@
                 return;
             }
             synchronized (mLock) {
+                pw.print("Disabled users: "); pw.println(mDisabledUsers);
                 final int size = mServicesCache.size();
                 pw.print("Cached services: ");
                 if (size == 0) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index d13cf77..6fb9f7c 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -53,6 +53,7 @@
 import android.os.Parcelable;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.autofill.AutofillService;
 import android.service.autofill.AutofillServiceInfo;
@@ -105,6 +106,10 @@
     private AutofillServiceInfo mInfo;
 
     private final LocalLog mRequestsHistory;
+    /**
+     * Whether service was disabled for user due to {@link UserManager} restrictions.
+     */
+    private boolean mDisabled;
 
     private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
         switch (msg.what) {
@@ -183,13 +188,13 @@
     };
 
     AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
-            int userId, AutoFillUI ui) {
+            int userId, AutoFillUI ui, boolean disabled) {
         mContext = context;
         mLock = lock;
         mRequestsHistory = requestsHistory;
         mUserId = userId;
         mUi = ui;
-        updateLocked();
+        updateLocked(disabled);
     }
 
     CharSequence getServiceName() {
@@ -209,7 +214,9 @@
         }
     }
 
-    void updateLocked() {
+    void updateLocked(boolean disabled) {
+        final boolean wasEnabled = isEnabled();
+        mDisabled = disabled;
         ComponentName serviceComponent = null;
         ServiceInfo serviceInfo = null;
         final String componentName = Settings.Secure.getStringForUser(
@@ -225,15 +232,14 @@
             }
         }
         try {
-            final boolean hadService = hasService();
             if (serviceInfo != null) {
                 mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
                         serviceComponent, mUserId);
             } else {
                 mInfo = null;
             }
-            if (hadService != hasService()) {
-                if (!hasService()) {
+            if (wasEnabled != isEnabled()) {
+                if (!isEnabled()) {
                     final int sessionCount = mSessions.size();
                     for (int i = sessionCount - 1; i >= 0; i--) {
                         final Session session = mSessions.valueAt(i);
@@ -251,7 +257,7 @@
      * Used by {@link AutofillManagerServiceShellCommand} to request save for the current top app.
      */
     void requestSaveForUserLocked(IBinder activityToken) {
-        if (!hasService()) {
+        if (!isEnabled()) {
             return;
         }
         final Session session = mSessions.get(activityToken);
@@ -268,11 +274,11 @@
             mClients = new RemoteCallbackList<>();
         }
         mClients.register(client);
-        return hasService();
+        return isEnabled();
     }
 
     void setAuthenticationResultLocked(Bundle data, IBinder activityToken) {
-        if (!hasService()) {
+        if (!isEnabled()) {
             return;
         }
         final Session session = mSessions.get(activityToken);
@@ -282,7 +288,7 @@
     }
 
     void setHasCallback(IBinder activityToken, boolean hasIt) {
-        if (!hasService()) {
+        if (!isEnabled()) {
             return;
         }
         final Session session = mSessions.get(activityToken);
@@ -295,7 +301,7 @@
             @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect bounds,
             @Nullable AutofillValue value, boolean hasCallback, int flags,
             @NonNull String packageName) {
-        if (!hasService()) {
+        if (!isEnabled()) {
             return;
         }
 
@@ -318,7 +324,7 @@
     }
 
     void finishSessionLocked(IBinder activityToken) {
-        if (!hasService()) {
+        if (!isEnabled()) {
             return;
         }
 
@@ -338,7 +344,7 @@
     }
 
     void cancelSessionLocked(IBinder activityToken) {
-        if (!hasService()) {
+        if (!isEnabled()) {
             return;
         }
 
@@ -423,8 +429,9 @@
 
         pw.print(prefix); pw.print("Component:"); pw.println(mInfo != null
                 ? mInfo.getServiceInfo().getComponentName() : null);
+        pw.print(prefix); pw.print("Disabled:"); pw.println(mDisabled);
 
-        if (VERBOSE) {
+        if (VERBOSE && mInfo != null) {
             // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
             pw.print(prefix); pw.println("ServiceInfo:");
             mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
@@ -467,9 +474,9 @@
         }
         try {
             for (int i = 0; i < userClientCount; i++) {
-                IAutoFillManagerClient client = clients.getBroadcastItem(i);
+                final IAutoFillManagerClient client = clients.getBroadcastItem(i);
                 try {
-                    client.setState(hasService());
+                    client.setState(isEnabled());
                 } catch (RemoteException re) {
                     /* ignore */
                 }
@@ -479,8 +486,8 @@
         }
     }
 
-    private boolean hasService() {
-        return mInfo != null;
+    private boolean isEnabled() {
+        return mInfo != null && !mDisabled;
     }
 
     @Override
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 090cf91..353b4ac 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -31,24 +31,21 @@
 
     static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
     static final boolean VERBOSE = false;
-    static final String REDACTED = "[REDACTED]";
 
     static void append(StringBuilder builder, Bundle bundle) {
-        if (bundle == null) {
-            builder.append("N/A");
-        } else if (!VERBOSE) {
-            builder.append(REDACTED);
-        } else {
-            final Set<String> keySet = bundle.keySet();
-            builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
-            for (String key : keySet) {
-                final Object value = bundle.get(key);
-                builder.append(' ').append(key).append('=');
-                builder.append((value instanceof Object[])
-                        ? Arrays.toString((Objects[]) value) : value);
-            }
-            builder.append(']');
+        if (bundle == null || !DEBUG) {
+            builder.append("null");
+            return;
         }
+        final Set<String> keySet = bundle.keySet();
+        builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
+        for (String key : keySet) {
+            final Object value = bundle.get(key);
+            builder.append(' ').append(key).append('=');
+            builder.append((value instanceof Object[])
+                    ? Arrays.toString((Objects[]) value) : value);
+        }
+        builder.append(']');
     }
 
     static String bundleToString(Bundle bundle) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index cb2ed6e..84381fe 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -109,6 +109,7 @@
             UserManager.DISALLOW_SET_WALLPAPER,
             UserManager.DISALLOW_OEM_UNLOCK,
             UserManager.DISALLOW_UNMUTE_DEVICE,
+            UserManager.DISALLOW_AUTOFILL,
     });
 
     /**