OSDN Git Service

AutoFill Framework refactoring.
authorFelipe Leme <felipeal@google.com>
Tue, 29 Nov 2016 01:21:21 +0000 (17:21 -0800)
committerFelipe Leme <felipeal@google.com>
Tue, 13 Dec 2016 02:22:45 +0000 (18:22 -0800)
The AutoFill Framework uses the same AssitStructure provided by the Assist API
and so far it was using the same methods as well, both internally and externally
(public API).

Sharing that internal code internally is fine, but the public APIs must distinguish between the 2 cases so they can fill the assist structures accordingly (although the initial implementation still shares the same logic).

This CL also splits the original 'auto-fill' request in 2 types of requests,
which are set by View flags:

- ASSIST_FLAG_SANITIZED_TEXT
- ASSIST_FLAG_NON_SANITIZED_TEXT

It also added new methods and callbacks to handle save requests.

Bug: 31001899
Test: manual verification

Change-Id:  I4eb09099dc19a43cb7e053e64d939aed3704b410

24 files changed:
api/current.txt
api/system-current.txt
api/test-current.txt
core/java/android/app/Activity.java
core/java/android/app/ActivityManager.java
core/java/android/app/ActivityThread.java
core/java/android/app/IActivityManager.aidl
core/java/android/app/IApplicationThread.aidl
core/java/android/app/assist/AssistStructure.java
core/java/android/service/autofill/AutoFillService.java
core/java/android/service/autofill/FillCallback.java
core/java/android/service/autofill/IAutoFillManagerService.aidl
core/java/android/service/autofill/SaveCallback.java [new file with mode: 0644]
core/java/android/service/voice/VoiceInteractionSession.java
core/java/android/view/View.java
core/java/android/view/ViewGroup.java
core/java/android/webkit/WebView.java
core/java/android/webkit/WebViewProvider.java
core/java/android/widget/Switch.java
core/java/android/widget/TextView.java
services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
services/core/java/com/android/server/am/ActivityManagerService.java

index 5a2df57..cd83170 100644 (file)
@@ -34869,7 +34869,8 @@ package android.service.autofill {
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
-    method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback);
+    method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
   }
 
@@ -34887,6 +34888,11 @@ package android.service.autofill {
     method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String);
   }
 
+  public final class SaveCallback {
+    method public void onFailure(java.lang.CharSequence);
+    method public void onSuccess(int[]);
+  }
+
 }
 
 package android.service.carrier {
@@ -42932,7 +42938,8 @@ package android.view {
     method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
     method public boolean dispatchNestedScroll(int, int, int, int, int[]);
     method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
-    method public void dispatchProvideStructure(android.view.ViewStructure);
+    method public deprecated void dispatchProvideStructure(android.view.ViewStructure);
+    method public void dispatchProvideStructure(android.view.ViewStructure, int);
     method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
     method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
     method protected void dispatchSetActivated(boolean);
@@ -43198,8 +43205,10 @@ package android.view {
     method protected void onMeasure(int, int);
     method protected void onOverScrolled(int, int, boolean, boolean);
     method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
-    method public void onProvideStructure(android.view.ViewStructure);
-    method public void onProvideVirtualStructure(android.view.ViewStructure);
+    method public deprecated void onProvideStructure(android.view.ViewStructure);
+    method public void onProvideStructure(android.view.ViewStructure, int);
+    method public deprecated void onProvideVirtualStructure(android.view.ViewStructure);
+    method public void onProvideVirtualStructure(android.view.ViewStructure, int);
     method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
     method protected void onRestoreInstanceState(android.os.Parcelable);
     method public void onRtlPropertiesChanged(int);
@@ -43401,6 +43410,8 @@ package android.view {
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+    field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2
+    field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
index 7a287df..e8c9b40 100644 (file)
@@ -37700,7 +37700,8 @@ package android.service.autofill {
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
-    method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback);
+    method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
   }
 
@@ -37718,6 +37719,11 @@ package android.service.autofill {
     method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String);
   }
 
+  public final class SaveCallback {
+    method public void onFailure(java.lang.CharSequence);
+    method public void onSuccess(int[]);
+  }
+
 }
 
 package android.service.carrier {
@@ -46118,7 +46124,8 @@ package android.view {
     method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
     method public boolean dispatchNestedScroll(int, int, int, int, int[]);
     method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
-    method public void dispatchProvideStructure(android.view.ViewStructure);
+    method public deprecated void dispatchProvideStructure(android.view.ViewStructure);
+    method public void dispatchProvideStructure(android.view.ViewStructure, int);
     method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
     method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
     method protected void dispatchSetActivated(boolean);
@@ -46384,8 +46391,10 @@ package android.view {
     method protected void onMeasure(int, int);
     method protected void onOverScrolled(int, int, boolean, boolean);
     method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
-    method public void onProvideStructure(android.view.ViewStructure);
-    method public void onProvideVirtualStructure(android.view.ViewStructure);
+    method public deprecated void onProvideStructure(android.view.ViewStructure);
+    method public void onProvideStructure(android.view.ViewStructure, int);
+    method public deprecated void onProvideVirtualStructure(android.view.ViewStructure);
+    method public void onProvideVirtualStructure(android.view.ViewStructure, int);
     method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
     method protected void onRestoreInstanceState(android.os.Parcelable);
     method public void onRtlPropertiesChanged(int);
@@ -46587,6 +46596,8 @@ package android.view {
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+    field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2
+    field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -49956,6 +49967,7 @@ package android.webkit {
     method public abstract void onMeasure(int, int);
     method public abstract void onOverScrolled(int, int, boolean, boolean);
     method public abstract void onProvideVirtualStructure(android.view.ViewStructure);
+    method public default void onProvideVirtualStructure(android.view.ViewStructure, int);
     method public abstract void onScrollChanged(int, int, int, int);
     method public abstract void onSizeChanged(int, int, int, int);
     method public abstract void onStartTemporaryDetach();
index 20b723d..258ef4b 100644 (file)
@@ -34966,7 +34966,8 @@ package android.service.autofill {
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
-    method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback);
+    method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
   }
 
@@ -34984,6 +34985,11 @@ package android.service.autofill {
     method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String);
   }
 
+  public final class SaveCallback {
+    method public void onFailure(java.lang.CharSequence);
+    method public void onSuccess(int[]);
+  }
+
 }
 
 package android.service.carrier {
@@ -43200,7 +43206,8 @@ package android.view {
     method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
     method public boolean dispatchNestedScroll(int, int, int, int, int[]);
     method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
-    method public void dispatchProvideStructure(android.view.ViewStructure);
+    method public deprecated void dispatchProvideStructure(android.view.ViewStructure);
+    method public void dispatchProvideStructure(android.view.ViewStructure, int);
     method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
     method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
     method protected void dispatchSetActivated(boolean);
@@ -43467,8 +43474,10 @@ package android.view {
     method protected void onMeasure(int, int);
     method protected void onOverScrolled(int, int, boolean, boolean);
     method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
-    method public void onProvideStructure(android.view.ViewStructure);
-    method public void onProvideVirtualStructure(android.view.ViewStructure);
+    method public deprecated void onProvideStructure(android.view.ViewStructure);
+    method public void onProvideStructure(android.view.ViewStructure, int);
+    method public deprecated void onProvideVirtualStructure(android.view.ViewStructure);
+    method public void onProvideVirtualStructure(android.view.ViewStructure, int);
     method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
     method protected void onRestoreInstanceState(android.os.Parcelable);
     method public void onRtlPropertiesChanged(int);
@@ -43670,6 +43679,8 @@ package android.view {
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+    field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2
+    field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
index 6dd488f..bbbedb3 100644 (file)
@@ -1737,8 +1737,8 @@ public class Activity extends ContextThemeWrapper
                                 FillableInputField autoFillField = (FillableInputField) field;
                                 final int viewId = autoFillField.getId();
                                 final View view = root.findViewByAccessibilityIdTraversal(viewId);
-                                // TODO: should handle other types of view as well, but that will
-                                // require:
+                                // TODO(b/33197203): should handle other types of view as well, but
+                                // that will require:
                                 // - a new interface like AutoFillable
                                 // - a way for the views to define the type of the autofield value
                                 if ((view instanceof EditText)) {
@@ -1751,7 +1751,7 @@ public class Activity extends ContextThemeWrapper
                     @Override
                     public void showError(String message) {
                         runOnUiThread(() -> {
-                            // TODO: temporary show a toast until it uses the Snack bar.
+                            // TODO(b/33197203): temporary show a toast until it uses the Snack bar.
                             Toast.makeText(Activity.this, "Auto-fill request failed: " + message,
                                     Toast.LENGTH_LONG).show();
                         });
index 312d3a5..f6f5472 100644 (file)
@@ -450,9 +450,6 @@ public class ActivityManager {
     /** @hide requestType for assist context: generate full AssistStructure. */
     public static final int ASSIST_CONTEXT_FULL = 1;
 
-    /** @hide requestType for assist context: generate full AssistStructure for auto-fill. */
-    public static final int ASSIST_CONTEXT_AUTOFILL = 2;
-
     /** @hide Flag for registerUidObserver: report changes in process state. */
     public static final int UID_OBSERVER_PROCSTATE = 1<<0;
 
index f052bf7..9a92764 100644 (file)
@@ -88,6 +88,7 @@ import android.provider.Downloads;
 import android.provider.Settings;
 import android.security.NetworkSecurityPolicy;
 import android.security.net.config.NetworkSecurityConfigProvider;
+import android.service.autofill.AutoFillService;
 import android.service.autofill.IAutoFillCallback;
 import android.service.voice.VoiceInteractionSession;
 import android.util.AndroidRuntimeException;
@@ -640,6 +641,7 @@ public final class ActivityThread {
         IBinder requestToken;
         int requestType;
         int sessionId;
+        int flags;
     }
 
     static final class ActivityConfigChangeData {
@@ -1245,12 +1247,13 @@ public final class ActivityThread {
 
         @Override
         public void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
-                int requestType, int sessionId) {
+                int requestType, int sessionId, int flags) {
             RequestAssistContextExtras cmd = new RequestAssistContextExtras();
             cmd.activityToken = activityToken;
             cmd.requestToken = requestToken;
             cmd.requestType = requestType;
             cmd.sessionId = sessionId;
+            cmd.flags = flags;
             sendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd);
         }
 
@@ -2883,6 +2886,16 @@ public final class ActivityThread {
     }
 
     public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
+        // Filling for auto-fill has a few differences:
+        // - it does not need an AssistContent
+        // - it does not call onProvideAssistData()
+        // - it needs an IAutoFillCallback
+        // - it sets the flags so views can provide autofill-specific data (such as passwords)
+        boolean forAutoFill = (cmd.flags
+                & (View.ASSIST_FLAG_SANITIZED_TEXT
+                        | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+
+        // TODO(b/33197203): decide if lastSessionId logic applies to auto-fill sessions
         if (mLastSessionId != cmd.sessionId) {
             // Clear the existing structures
             mLastSessionId = cmd.sessionId;
@@ -2894,46 +2907,45 @@ public final class ActivityThread {
                 mLastAssistStructures.remove(i);
             }
         }
-        // Filling for auto-fill has a few differences:
-        // - it does not need an AssistContent
-        // - it does not call onProvideAssistData()
-        // - it needs an IAutoFillCallback
-        boolean forAutofill = cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTOFILL;
 
         Bundle data = new Bundle();
         AssistStructure structure = null;
-        AssistContent content = forAutofill ? null : new AssistContent();
+        AssistContent content = forAutoFill ? null : new AssistContent();
         ActivityClientRecord r = mActivities.get(cmd.activityToken);
         Uri referrer = null;
         if (r != null) {
-            r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
-            if (!forAutofill) {
+            if (!forAutoFill) {
+                r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
                 r.activity.onProvideAssistData(data);
             }
             referrer = r.activity.onProvideReferrer();
-            if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutofill) {
-                structure = new AssistStructure(r.activity);
+            if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutoFill) {
+                structure = new AssistStructure(r.activity, cmd.flags);
                 Intent activityIntent = r.activity.getIntent();
+                if (cmd.flags > 0) {
+                    data.putInt(VoiceInteractionSession.KEY_FLAGS, cmd.flags);
+                }
+                // TODO(b/33197203): re-evaluate conditions below for auto-fill. In particular,
+                // FLAG_SECURE might be allowed on AUTO_FILL but not on AUTO_FILL_SAVE)
                 if (activityIntent != null && (r.window == null ||
                         (r.window.getAttributes().flags
                                 & WindowManager.LayoutParams.FLAG_SECURE) == 0)) {
-                    Intent intent = new Intent(activityIntent);
-                    intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                            | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
-                    intent.removeUnsafeExtras();
-                    if (forAutofill) {
+                    if (forAutoFill) {
                         IAutoFillCallback autoFillCallback = r.activity.getAutoFillCallback();
-                        data.putBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK,
-                                autoFillCallback.asBinder());
+                        data.putBinder(AutoFillService.KEY_CALLBACK, autoFillCallback.asBinder());
                     } else {
+                        Intent intent = new Intent(activityIntent);
+                        intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+                        intent.removeUnsafeExtras();
                         content.setDefaultIntent(intent);
                     }
                 } else {
-                    if (!forAutofill) {
+                    if (!forAutoFill) {
                         content.setDefaultIntent(new Intent());
                     }
                 }
-                if (!forAutofill) {
+                if (!forAutoFill) {
                     r.activity.onProvideAssistContent(content);
                 }
             }
@@ -2941,6 +2953,7 @@ public final class ActivityThread {
         if (structure == null) {
             structure = new AssistStructure();
         }
+        // TODO(b/33197203): decide if lastSessionId logic applies to auto-fill sessions
         mLastAssistStructures.add(new WeakReference<>(structure));
         IActivityManager mgr = ActivityManager.getService();
         try {
index 62b3977..1e6f6c6 100644 (file)
@@ -573,7 +573,7 @@ interface IActivityManager {
     void unregisterTaskStackListener(ITaskStackListener listener);
     void moveStackToDisplay(int stackId, int displayId);
     boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras,
-            in IBinder activityToken);
+            in IBinder activityToken, int flags);
     void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
 
     // WARNING: when these transactions are updated, check if they are any callers on the native
index 6b962b9..7f168c9 100644 (file)
@@ -134,7 +134,7 @@ oneway interface IApplicationThread {
     void dumpDbInfo(in ParcelFileDescriptor fd, in String[] args);
     void unstableProviderDied(IBinder provider);
     void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
-            int requestType, int sessionId);
+            int requestType, int sessionId, int flags);
     void scheduleTranslucentConversionComplete(IBinder token, boolean timeout);
     void setProcessState(int state);
     void scheduleInstallProvider(in ProviderInfo provider);
index a6f2366..1988e42 100644 (file)
@@ -400,7 +400,7 @@ public class AssistStructure implements Parcelable {
         final int mDisplayId;
         final ViewNode mRoot;
 
-        WindowNode(AssistStructure assist, ViewRootImpl root) {
+        WindowNode(AssistStructure assist, ViewRootImpl root, int flags) {
             View view = root.getView();
             Rect rect = new Rect();
             view.getBoundsOnScreen(rect);
@@ -415,11 +415,22 @@ public class AssistStructure implements Parcelable {
             if ((root.getWindowFlags()& WindowManager.LayoutParams.FLAG_SECURE) != 0) {
                 // This is a secure window, so it doesn't want a screenshot, and that
                 // means we should also not copy out its view hierarchy.
-                view.onProvideStructure(builder);
+
+                // Must explicitly set which method to calls since View subclasses might
+                // have implemented the deprecated method.
+                if (flags == 0) {
+                    view.onProvideStructure(builder);
+                } else {
+                    view.onProvideStructure(builder, flags);
+                }
                 builder.setAssistBlocked(true);
                 return;
             }
-            view.dispatchProvideStructure(builder);
+            if (flags == 0) {
+                view.dispatchProvideStructure(builder);
+            } else {
+                view.dispatchProvideStructure(builder, flags);
+            }
         }
 
         WindowNode(ParcelTransferReader reader) {
@@ -1351,14 +1362,14 @@ public class AssistStructure implements Parcelable {
     }
 
     /** @hide */
-    public AssistStructure(Activity activity) {
+    public AssistStructure(Activity activity, int flags) {
         mHaveData = true;
         mActivityComponent = activity.getComponentName();
         ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
                 activity.getActivityToken());
         for (int i=0; i<views.size(); i++) {
             ViewRootImpl root = views.get(i);
-            mWindowNodes.add(new WindowNode(this, root));
+            mWindowNodes.add(new WindowNode(this, root, flags));
         }
     }
 
index 5f27e34..a7941c7 100644 (file)
  */
 package android.service.autofill;
 
+import static android.service.voice.VoiceInteractionSession.KEY_FLAGS;
+import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
+import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT;
+import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT;
+
 import android.annotation.SdkConstant;
 import android.app.Activity;
 import android.app.Service;
@@ -26,13 +31,14 @@ import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.service.voice.VoiceInteractionSession;
 import android.util.Log;
 
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.SomeArgs;
 
+// TODO(b/33197203): improve javadoc (class and methods)
+
 /**
  * Top-level service of the current auto-fill service for a given user.
  *
@@ -52,6 +58,11 @@ public abstract class AutoFillService extends Service {
     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     public static final String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
 
+    // Bundle keys.
+    /** @hide */
+    public static final String KEY_CALLBACK = "callback";
+
+    // Handler messages.
     private static final int MSG_CONNECT = 1;
     private static final int MSG_AUTO_FILL_ACTIVITY = 2;
     private static final int MSG_DISCONNECT = 3;
@@ -59,14 +70,12 @@ public abstract class AutoFillService extends Service {
     private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
         @Override
         public void send(int resultCode, Bundle resultData) throws RemoteException {
-            final AssistStructure structure = resultData
-                    .getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
-
-            final IBinder binder = resultData
-                    .getBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK);
+            final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
+            final IBinder binder = resultData.getBinder(KEY_CALLBACK);
+            final int flags = resultData.getInt(KEY_FLAGS, 0);
 
             mHandlerCaller
-                .obtainMessageOO(MSG_AUTO_FILL_ACTIVITY, structure, binder).sendToTarget();
+                .obtainMessageIOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, binder).sendToTarget();
         }
 
     };
@@ -100,7 +109,8 @@ public abstract class AutoFillService extends Service {
                     final SomeArgs args = (SomeArgs) msg.obj;
                     final AssistStructure structure = (AssistStructure) args.arg1;
                     final IBinder binder = (IBinder) args.arg2;
-                    requestAutoFill(structure, binder);
+                    final int flags = msg.arg1;
+                    requestAutoFill(structure, flags, binder);
                     break;
                 } case MSG_DISCONNECT: {
                     onDisconnected();
@@ -145,19 +155,46 @@ public abstract class AutoFillService extends Service {
     }
 
     /**
-     * Handles an auto-fill request.
+     * Called when user requests service to auto-fill an {@link Activity}.
      *
      * @param structure {@link Activity}'s view structure .
+     * @param data bundle with optional parameters (currently none) which is passed along on
+     * subsequent calls (so it can be used by the service to share data).
      * @param cancellationSignal signal for observing cancel requests.
-     * @param callback object used to fulllfill the request.
      */
     public abstract void onFillRequest(AssistStructure structure,
-            CancellationSignal cancellationSignal, FillCallback callback);
+            Bundle data, CancellationSignal cancellationSignal, FillCallback callback);
 
-    private void requestAutoFill(AssistStructure structure, IBinder binder) {
-        final FillCallback callback = new FillCallback(binder);
-        // TODO: hook up the cancelationSignal
-        onFillRequest(structure, new CancellationSignal(), callback);
+    /**
+     * Called when user requests service to save the fields of an {@link Activity}.
+     *
+     * @param structure {@link Activity}'s view structure.
+     * @param data same bundle passed to
+     * {@link #onFillRequest(AssistStructure, Bundle, CancellationSignal, FillCallback)};
+     * might also contain with optional parameters (currently none).
+     * @param cancellationSignal signal for observing cancel requests.
+     * @param callback object used to notify the result of the request.
+     */
+    public abstract void onSaveRequest(AssistStructure structure,
+            Bundle data, CancellationSignal cancellationSignal, SaveCallback callback);
+
+    private void requestAutoFill(AssistStructure structure, int flags, IBinder binder) {
+        // TODO(b/33197203): pass the Bundle received from mAssistReceiver instead?
+        final Bundle data = new Bundle();
+        switch (flags) {
+            case ASSIST_FLAG_SANITIZED_TEXT:
+                final FillCallback fillCallback = new FillCallback(binder);
+                // TODO(b/33197203): hook up the cancelationSignal
+                onFillRequest(structure, data, new CancellationSignal(), fillCallback);
+                break;
+            case ASSIST_FLAG_NON_SANITIZED_TEXT:
+                final SaveCallback saveCallback = new SaveCallback(binder);
+                // TODO(b/33197203): hook up the cancelationSignal
+                onSaveRequest(structure, null, new CancellationSignal(), saveCallback);
+                break;
+            default:
+                Log.w(TAG, "invalid flag on requestAutoFill(): " + flags);
+        }
     }
 
     /**
index 2308440..3284b90 100644 (file)
@@ -17,7 +17,6 @@
 package android.service.autofill;
 
 import static android.service.autofill.AutoFillService.DEBUG;
-import static android.service.autofill.AutoFillService.TAG;
 
 import android.app.Activity;
 import android.app.assist.AssistStructure.ViewNode;
@@ -38,6 +37,8 @@ import java.util.List;
  */
 public final class FillCallback {
 
+    private static final String TAG = "FillCallback";
+
     private final IAutoFillCallback mCallback;
 
     /** @hide */
@@ -62,6 +63,13 @@ public final class FillCallback {
         }
     }
 
+    /**
+     * Notifies the {@link Activity} that the auto-fill request failed.
+     *
+     * @param message error message to be displayed.
+     *
+     * @throws RuntimeException if an error occurred while notifying the activity.
+     */
     public void onFailure(CharSequence message) {
         if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
 
index 76a2561..f1251c0 100644 (file)
@@ -25,11 +25,5 @@ import android.os.Bundle;
  */
 oneway interface IAutoFillManagerService {
 
-    /**
-     * Request auto-fill on the top activity of a given user.
-     *
-     * @param userId user handle.
-     * @param activityToken optional token of activity that needs to be on top.
-     */
-    void requestAutoFill(int userId, IBinder activityToken);
+    void requestAutoFill(IBinder activityToken, int userId, int flags);
 }
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
new file mode 100644 (file)
index 0000000..4dc7392
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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 android.service.autofill;
+
+import static android.service.autofill.AutoFillService.DEBUG;
+
+import android.app.Activity;
+import android.app.assist.AssistStructure.ViewNode;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Handles save requests from the {@link AutoFillService} into the {@link Activity} being
+ * auto-filled.
+ */
+public final class SaveCallback {
+
+    private static final String TAG = "SaveCallback";
+
+    private final IAutoFillCallback mCallback;
+
+    /** @hide */
+    SaveCallback(IBinder binder) {
+        mCallback = IAutoFillCallback.Stub.asInterface(binder);
+    }
+
+    /**
+     * Notifies the {@link Activity} that the save request succeeded.
+     *
+     * @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved.
+     *
+     * @throws RuntimeException if an error occurred while saving the data.
+     */
+    public void onSuccess(int[] ids) {
+        Preconditions.checkArgument(ids != null, "ids cannot be null");
+
+        Preconditions.checkArgument(ids.length > 0, "ids cannot be empty");
+
+        if (DEBUG) Log.d(TAG, "onSuccess(): ids=" + ids.length);
+
+        // TODO(b/33197203): display which ids were saved
+    }
+
+    /**
+     * Notifies the {@link Activity} that the save request failed.
+     *
+     * @param message error message to be displayed.
+     *
+     * @throws RuntimeException if an error occurred while notifying the activity.
+     */
+    public void onFailure(CharSequence message) {
+        if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
+
+        Preconditions.checkArgument(message != null, "message cannot be null");
+
+        try {
+            mCallback.showError(message.toString());
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+}
index 12aed25..48f3ac3 100644 (file)
@@ -120,7 +120,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
     /** @hide */
     public static final String KEY_RECEIVER_EXTRAS = "receiverExtras";
     /** @hide */
-    public static final String KEY_AUTO_FILL_CALLBACK = "autoFillCallback";
+    public static final String KEY_FLAGS = "flags";
 
     final Context mContext;
     final HandlerCaller mHandlerCaller;
index f9a03c0..06f10c7 100644 (file)
@@ -3940,6 +3940,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     int mLayerType = LAYER_TYPE_NONE;
     Paint mLayerPaint;
 
+
+    /**
+     * <p>When setting a {@link android.app.assist.AssistStructure}, its nodes should not contain
+     * PII (Personally Identifiable Information).
+     */
+    // TODO(b/33197203) (b/33269702): improve documentation: mention all cases, show examples, etc.
+    public static final int ASSIST_FLAG_SANITIZED_TEXT = 0x1;
+
+    /**
+     * <p>When setting a {@link android.app.assist.AssistStructure}, its nodes should contain all
+     * type of data, even sensitive PII (Personally Identifiable Information) like passwords or
+     * credit card numbers.
+     */
+    public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 0x2;
+
     /**
      * Set to true when drawing cache is enabled and cannot be created.
      *
@@ -6757,8 +6772,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
      * @param structure Fill in with structured view data.  The default implementation
      * fills in all data that can be inferred from the view itself.
+     *
+     * @deprecated As of API O sub-classes should override
+     * {@link #onProvideStructure(ViewStructure, int)} instead.
      */
+    // TODO(b/33197203): set proper API above
+    @Deprecated
     public void onProvideStructure(ViewStructure structure) {
+        onProvideStructure(structure, 0);
+    }
+
+    /**
+     * Called when assist structure is being retrieved from a view as part of
+     * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} or as part
+     * of an auto-fill request.
+     *
+     * <p>The default implementation fills in all data that can be inferred from the view itself.
+     *
+     * <p>The structure must be filled according to the request type, which is set in the
+     * {@code flags} parameter - see the documentation on each flag for more details.
+     *
+     * @param structure Fill in with structured view data. The default implementation
+     * fills in all data that can be inferred from the view itself.
+     * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and
+     * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info).
+     */
+    public void onProvideStructure(ViewStructure structure, int flags) {
+        boolean forAutoFill = (flags
+                & (View.ASSIST_FLAG_SANITIZED_TEXT
+                        | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
         final int id = mID;
         if (id > 0 && (id&0xff000000) != 0 && (id&0x00ff0000) != 0
                 && (id&0x0000ffff) != 0) {
@@ -6776,9 +6818,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             structure.setId(id, null, null, null);
         }
 
-        // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
-        // reuse the accessibility id to save space.
-        structure.setAutoFillId(getAccessibilityViewId());
+        if (forAutoFill) {
+            // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
+            // reuse the accessibility id to save space.
+            structure.setAutoFillId(getAccessibilityViewId());
+        }
 
         structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight - mLeft, mBottom - mTop);
         if (!hasIdentityMatrix()) {
@@ -6828,20 +6872,52 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * uses {@link #getAccessibilityNodeProvider()} to try to generate this from the
      * view's virtual accessibility nodes, if any.  You can override this for a more
      * optimal implementation providing this data.
+     *
+     * @deprecated As of API O, sub-classes should override
+     * {@link #onProvideVirtualStructure(ViewStructure, int)} instead.
      */
+    // TODO(b/33197203): set proper API above
+    @Deprecated
     public void onProvideVirtualStructure(ViewStructure structure) {
+        onProvideVirtualStructure(structure, 0);
+    }
+
+    /**
+     * Called when assist structure is being retrieved from a view as part of
+     * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} or as part
+     * of an auto-fill request to generate additional virtual structure under this view.
+     *
+     * <p>The defaullt implementation uses {@link #getAccessibilityNodeProvider()} to try to
+     * generate this from the view's virtual accessibility nodes, if any.  You can override this
+     * for a more optimal implementation providing this data.
+     *
+     * <p>The structure must be filled according to the request type, which is set in the
+     * {@code flags} parameter - see the documentation on each flag for more details.
+     *
+     * @param structure Fill in with structured view data.
+     * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and
+     * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info).
+     */
+    public void onProvideVirtualStructure(ViewStructure structure, int flags) {
+        boolean sanitize = (flags & View.ASSIST_FLAG_SANITIZED_TEXT) != 0;
+
+        if (sanitize) {
+            // TODO(b/33197203): change populateVirtualStructure so it sanitizes data in this case.
+            return;
+        }
+
         AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
         if (provider != null) {
             AccessibilityNodeInfo info = createAccessibilityNodeInfo();
             structure.setChildCount(1);
             ViewStructure root = structure.newChild(0);
-            populateVirtualStructure(root, provider, info);
+            populateVirtualStructure(root, provider, info, flags);
             info.recycle();
         }
     }
 
     private void populateVirtualStructure(ViewStructure structure,
-            AccessibilityNodeProvider provider, AccessibilityNodeInfo info) {
+            AccessibilityNodeProvider provider, AccessibilityNodeInfo info, int flags) {
         structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
                 null, null, null);
         Rect rect = structure.getTempRect();
@@ -6890,7 +6966,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                 AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo(
                         AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
                 ViewStructure child = structure.newChild(i);
-                populateVirtualStructure(child, provider, cinfo);
+                populateVirtualStructure(child, provider, cinfo, flags);
                 cinfo.recycle();
             }
         }
@@ -6900,11 +6976,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * Dispatch creation of {@link ViewStructure} down the hierarchy.  The default
      * implementation calls {@link #onProvideStructure} and
      * {@link #onProvideVirtualStructure}.
+     *
+     * @deprecated As of API O,  sub-classes should override
+     * {@link #dispatchProvideStructure(ViewStructure, int)} instead.
      */
+    // TODO(b/33197203): set proper API above
+    @Deprecated
     public void dispatchProvideStructure(ViewStructure structure) {
-        if (!isAssistBlocked()) {
-            onProvideStructure(structure);
-            onProvideVirtualStructure(structure);
+        dispatchProvideStructure(structure, 0);
+    }
+
+    /**
+     * Dispatch creation of {@link ViewStructure} down the hierarchy.
+     *
+     * <p>The structure must be filled according to the request type, which is set in the
+     * {@code flags} parameter - see the documentation on each flag for more details.
+     *
+     * <p>The default implementation calls {@link #onProvideStructure(ViewStructure, int)} and
+     * {@link #onProvideVirtualStructure(ViewStructure, int)}.
+     *
+     * @param structure Fill in with structured view data.
+     * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and
+     * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info).
+     */
+    public void dispatchProvideStructure(ViewStructure structure, int flags) {
+        boolean forAutoFill = (flags
+                & (View.ASSIST_FLAG_SANITIZED_TEXT
+                        | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+
+        boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked();
+        if (!blocked) {
+            onProvideStructure(structure, flags);
+            onProvideVirtualStructure(structure, flags);
         } else {
             structure.setClassName(getAccessibilityClassName().toString());
             structure.setAssistBlocked(true);
@@ -8643,6 +8746,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
 
     /**
      * @hide
+     * Indicates whether this view will participate in data collection through
+     * {@link ViewStructure} for auto-fill purposes.
+     *
+     * <p>If {@code true}, it will not provide any data for itself or its children.
+     * <p>If {@code false}, the normal data collection will be allowed.
+     *
+     * @return Returns {@code false} if assist data collection for auto-fill is not blocked,
+     * else {@code true}.
+     *
+     * TODO(b/33197203): update / remove javadoc tags below
+     * @see #setAssistBlocked(boolean)
+     * @attr ref android.R.styleable#View_assistBlocked
+     */
+    public boolean isAutoFillBlocked() {
+        return false; // TODO(b/33197203): properly implement it
+    }
+
+    /**
+     * @hide
      * Controls whether assist data collection from this view and its children is enabled
      * (that is, whether {@link #onProvideStructure} and
      * {@link #onProvideVirtualStructure} will be called).  The default value is false,
index 0f8200d..ff797d1 100644 (file)
@@ -3083,14 +3083,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     }
 
     /**
-     * Dispatch creation of {@link ViewStructure} down the hierarchy.  This implementation
-     * adds in all child views of the view group, in addition to calling the default View
-     * implementation.
+     * {@inheritDoc}
+     *
+     * <p>This implementation adds in all child views of the view group, in addition to calling the
+     * default {@link View} implementation.
      */
     @Override
-    public void dispatchProvideStructure(ViewStructure structure) {
-        super.dispatchProvideStructure(structure);
-        if (!isAssistBlocked()) {
+    public void dispatchProvideStructure(ViewStructure structure, int flags) {
+        super.dispatchProvideStructure(structure, flags);
+
+        boolean forAutoFill = (flags
+                & (View.ASSIST_FLAG_SANITIZED_TEXT
+                        | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+
+        boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked();
+
+        if (!blocked) {
             if (structure.getChildCount() == 0) {
                 final int childrenCount = getChildCount();
                 if (childrenCount > 0) {
@@ -3151,7 +3159,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                         final View child = getAndVerifyPreorderedView(
                                 preorderedList, children, childIndex);
                         final ViewStructure cstructure = structure.newChild(i);
-                        child.dispatchProvideStructure(cstructure);
+
+                        // Must explicitly check which recursive method to call because child might
+                        // not be overriding the new, flags-based version
+                        if (flags == 0) {
+                            child.dispatchProvideStructure(cstructure);
+                        } else {
+                            child.dispatchProvideStructure(cstructure, flags);
+                        }
                     }
                     if (preorderedList != null) preorderedList.clear();
                 }
index b840f4a..8ecc42d 100644 (file)
@@ -2504,6 +2504,7 @@ public class WebView extends AbsoluteLayout
         return mProvider.getViewDelegate().shouldDelayChildPressedState();
     }
 
+    @Override
     public CharSequence getAccessibilityClassName() {
         return WebView.class.getName();
     }
@@ -2513,6 +2514,11 @@ public class WebView extends AbsoluteLayout
         mProvider.getViewDelegate().onProvideVirtualStructure(structure);
     }
 
+    @Override
+    public void onProvideVirtualStructure(ViewStructure structure, int flags) {
+        mProvider.getViewDelegate().onProvideVirtualStructure(structure, flags);
+    }
+
     /** @hide */
     @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
index 95ec179..7b95180 100644 (file)
@@ -311,6 +311,11 @@ public interface WebViewProvider {
 
         public void onProvideVirtualStructure(android.view.ViewStructure structure);
 
+        @SuppressWarnings("unused")
+        public default void onProvideVirtualStructure(android.view.ViewStructure structure,
+                int flags) {
+        }
+
         public AccessibilityNodeProvider getAccessibilityNodeProvider();
 
         public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
index fcc1667..e629df9 100644 (file)
@@ -1404,8 +1404,10 @@ public class Switch extends CompoundButton {
     }
 
     @Override
-    public void onProvideStructure(ViewStructure structure) {
-        super.onProvideStructure(structure);
+    public void onProvideStructure(ViewStructure structure, int flags) {
+        super.onProvideStructure(structure, flags);
+
+        // NOTE: current there is no difference for Assist (flags=0) or AutoFill (flags>0);
         CharSequence switchText = isChecked() ? mTextOn : mTextOff;
         if (!TextUtils.isEmpty(switchText)) {
             CharSequence oldText = structure.getText();
index 44655f1..3d35819 100644 (file)
@@ -9385,11 +9385,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     }
 
     @Override
-    public void onProvideStructure(ViewStructure structure) {
-        super.onProvideStructure(structure);
+    public void onProvideStructure(ViewStructure structure, int flags) {
+        super.onProvideStructure(structure, flags);
+
+        final boolean forAutoFillSave =
+                (flags & ASSIST_FLAG_NON_SANITIZED_TEXT) != 0;
         final boolean isPassword = hasPasswordTransformationMethod()
                 || isPasswordInputType(getInputType());
-        if (!isPassword) {
+        if (!isPassword || forAutoFillSave) {
             if (mLayout == null) {
                 assumeLayout();
             }
index d70c439..6a16131 100644 (file)
@@ -18,10 +18,13 @@ package com.android.server.autofill;
 
 import static android.Manifest.permission.MANAGE_AUTO_FILL;
 import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
+import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT;
+import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT;
 
 import android.Manifest;
 import android.app.AppGlobals;
 import android.app.Notification;
+import android.app.Notification.Action;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -248,13 +251,14 @@ public final class AutoFillManagerService extends SystemService {
     final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
 
         @Override
-        public void requestAutoFill(int userId, IBinder activityToken) {
+        public void requestAutoFill(IBinder activityToken, int userId, int flags) {
+            if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
             mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
 
             synchronized (mLock) {
                 final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
                 if (service != null) {
-                    service.requestAutoFill(activityToken);
+                    service.requestAutoFill(activityToken, flags);
                 }
             }
         }
@@ -307,7 +311,7 @@ public final class AutoFillManagerService extends SystemService {
             synchronized (mLock) {
                 removeCachedServiceForUserLocked(userId);
                 final ComponentName serviceComponent = getProviderForUser(userId);
-                if (serviceComponent== null) {
+                if (serviceComponent == null) {
                     cancelNotificationLocked(userId);
                 } else {
                     showNotification(serviceComponent, userId);
@@ -322,9 +326,10 @@ public final class AutoFillManagerService extends SystemService {
     ////////////////////////////////////////////////////////////////////////////
 
     // TODO: remove from frameworks/base/core/res/AndroidManifest.xml once it's not used anymore
-    private static final String NOTIFICATION_INTENT =
+    private static final String NOTIFICATION_AUTO_FILL_INTENT =
             "com.android.internal.autofill.action.REQUEST_AUTOFILL";
     private static final String EXTRA_USER_ID = "user_id";
+    private static final String EXTRA_FLAGS = "flags";
 
     private static final int MSG_SHOW_ALL_NOTIFICATIONS = 42;
     private static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000;
@@ -335,13 +340,14 @@ public final class AutoFillManagerService extends SystemService {
         @Override
         public void onReceive(Context context, Intent intent) {
             final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
+            final int flags = intent.getIntExtra(EXTRA_FLAGS, 0);
             if (DEBUG) Slog.d(TAG, "Requesting autofill by notification for user " + userId);
             synchronized (mLock) {
                 final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
                 if (service == null) {
                     Slog.w(TAG, "no auto-fill service for user " + userId);
                 } else {
-                    service.requestAutoFill(null);
+                    service.requestAutoFill(null, flags);
                 }
             }
         }
@@ -393,14 +399,23 @@ public final class AutoFillManagerService extends SystemService {
             if (mNotificationReceiver == null) {
                 mNotificationReceiver = new NotificationReceiver();
                 mContext.registerReceiver(mNotificationReceiver,
-                        new IntentFilter(NOTIFICATION_INTENT));
+                        new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
             }
         }
 
-        final Intent intent = new Intent(NOTIFICATION_INTENT);
-        intent.putExtra(EXTRA_USER_ID, userId);
-        final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+        final Intent fillIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
+        fillIntent.putExtra(EXTRA_USER_ID, userId);
+        fillIntent.putExtra(EXTRA_FLAGS, ASSIST_FLAG_SANITIZED_TEXT);
+        final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext,
+                ASSIST_FLAG_SANITIZED_TEXT, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+        final Action fillAction = new Action.Builder(null, "FILL", fillPendingIntent).build();
+
+        final Intent saveIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
+        saveIntent.putExtra(EXTRA_USER_ID, userId);
+        saveIntent.putExtra(EXTRA_FLAGS, ASSIST_FLAG_NON_SANITIZED_TEXT);
+        final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext,
+                ASSIST_FLAG_NON_SANITIZED_TEXT, saveIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+        final Action saveAction = new Action.Builder(null, "SAVE", savePendingIntent).build();
 
         final String packageName = serviceComponent.getPackageName();
         String providerName = null;
@@ -413,8 +428,8 @@ public final class AutoFillManagerService extends SystemService {
         } catch (Exception e) {
             providerName = packageName;
         }
-        final String title = "AutoFill by '" + providerName + "'";
-        final String subTitle = "Tap notification to auto-fill top activity for user " + userId;
+        final String title = "AutoFill actions";
+        final String subTitle = "Provider: " + providerName + "\n" + "User: " + userId;
 
         final Notification notification = new Notification.Builder(mContext)
                 .setCategory(Notification.CATEGORY_SYSTEM)
@@ -425,7 +440,7 @@ public final class AutoFillManagerService extends SystemService {
                         com.android.internal.R.color.system_notification_accent_color))
                 .setContentTitle(title)
                 .setStyle(new Notification.BigTextStyle().bigText(subTitle))
-                .setContentIntent(pi)
+                .setActions(fillAction, saveAction)
                 .build();
         NotificationManager.from(mContext).notify(userId, notification);
     }
index e409cb0..82356c8 100644 (file)
@@ -47,7 +47,6 @@ import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
@@ -70,13 +69,13 @@ final class AutoFillManagerServiceImpl {
     private final AutoFillServiceInfo mInfo;
     private final AutoFillManagerService mManagerService;
 
-    // TODO: improve its usage
+    // TODO(b/33197203): improve its usage
     // - set maximum number of entries
     // - disable on low-memory devices.
     private final List<String> mRequestHistory = new LinkedList<>();
 
     @GuardedBy("mLock")
-    private final List<IBinder> mQueuedRequests = new LinkedList<>();
+    private final List<QueuedRequest> mQueuedRequests = new LinkedList<>();
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -84,7 +83,8 @@ final class AutoFillManagerServiceImpl {
             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
                 final String reason = intent.getStringExtra("reason");
                 if (DEBUG) Slog.d(TAG, "close system dialogs: " + reason);
-                // TODO: close any pending UI like account selection (or remove this receiver)
+                // TODO(b/33197203): close any pending UI like account selection (or remove this
+                // receiver)
             }
         }
     };
@@ -104,8 +104,8 @@ final class AutoFillManagerServiceImpl {
                 if (!mQueuedRequests.isEmpty()) {
                     if (DEBUG) Log.d(TAG, "queued requests:" + mQueuedRequests.size());
                 }
-                for (IBinder activityToken : mQueuedRequests) {
-                    requestAutoFillLocked(activityToken, false);
+                for (final QueuedRequest request: mQueuedRequests) {
+                    requestAutoFillLocked(request.activityToken, request.flags, false);
                 }
             }
         }
@@ -180,7 +180,7 @@ final class AutoFillManagerServiceImpl {
         if (DEBUG) Slog.d(TAG, "Bound to " + mComponent);
     }
 
-    void requestAutoFill(IBinder activityToken) {
+    void requestAutoFill(IBinder activityToken, int flags) {
         synchronized (mLock) {
             if (!mBound) {
                 Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
@@ -188,14 +188,14 @@ final class AutoFillManagerServiceImpl {
             }
         }
 
-        // TODO: activityToken should probably not be null, but we need to wait until the UI is
-        // triggering the call (for now it's trough 'adb shell cmd autofill request'
+        // TODO(b/33197203): activityToken should probably not be null, but we need to wait until
+        // the UI is triggering the call (for now it's trough 'adb shell cmd autofill request'
         if (activityToken == null) {
             // Let's get top activities from all visible stacks.
 
-            // TODO: overload getTopVisibleActivities() to take userId, otherwise it could return
-            // activities for different users when a work profile app is displayed in another
-            // window (in a multi-window environment).
+            // TODO(b/33197203): overload getTopVisibleActivities() to take userId, otherwise it
+            // could return activities for different users when a work profile app is displayed in
+            // another window (in a multi-window environment).
             final List<IBinder> topActivities = LocalServices
                     .getService(ActivityManagerInternal.class).getTopVisibleActivities();
             if (DEBUG)
@@ -211,32 +211,34 @@ final class AutoFillManagerServiceImpl {
                 DateFormat.getDateTimeInstance().format(new Date()) + " - " + activityToken;
         synchronized (mLock) {
             mRequestHistory.add(historyItem);
-            requestAutoFillLocked(activityToken, true);
+            requestAutoFillLocked(activityToken, flags, true);
         }
     }
 
-    private void requestAutoFillLocked(IBinder activityToken, boolean queueIfNecessary) {
+    private void requestAutoFillLocked(IBinder activityToken, int flags, boolean queueIfNecessary) {
         if (mService == null) {
             if (!queueIfNecessary) {
                 Slog.w(TAG, "requestAutoFillLocked(): service is null");
                 return;
             }
             if (DEBUG) Slog.d(TAG, "requestAutoFill(): service not set yet, queuing it");
-            mQueuedRequests.add(activityToken);
+            mQueuedRequests.add(new QueuedRequest(activityToken, flags));
             return;
         }
 
         /*
-         * TODO: apply security checks below:
+         * TODO(b/33197203): apply security checks below:
          * - checks if disabled by secure settings / device policy
          * - log operation using noteOp()
          * - check flags
          * - display disclosure if needed
          */
         try {
-            // TODO: add MetricsLogger call
-            if (!mAm.requestAutoFillData(mService.getAssistReceiver(), null, activityToken)) {
-                // TODO: might need a way to warn user (perhaps a new method on AutoFillService).
+            // TODO(b/33197203): add MetricsLogger call
+            if (!mAm.requestAutoFillData(mService.getAssistReceiver(), null, activityToken,
+                    flags)) {
+                // TODO(b/33197203): might need a way to warn user (perhaps a new method on
+                // AutoFillService).
                 Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
             }
         } catch (RemoteException e) {
@@ -322,4 +324,19 @@ final class AutoFillManagerServiceImpl {
         return "[AutoFillManagerServiceImpl: userId=" + mUserId + ", uid=" + mUid
                 + ", component=" + mComponent.flattenToShortString() + "]";
     }
+
+    private static final class QueuedRequest {
+        final IBinder activityToken;
+        final int flags;
+
+        QueuedRequest(IBinder activityToken, int flags) {
+            this.activityToken = activityToken;
+            this.flags = flags;
+        }
+
+        @Override
+        public String toString() {
+            return "flags: " + flags + " token: " + activityToken;
+        }
+    }
 }
index 6406b8a..aa3503b 100644 (file)
@@ -16,6 +16,9 @@
 
 package com.android.server.autofill;
 
+import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT;
+import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT;
+
 import android.app.ActivityManager;
 import android.os.RemoteException;
 import android.os.ShellCommand;
@@ -40,8 +43,10 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand {
         final PrintWriter pw = getOutPrintWriter();
         try {
             switch (cmd) {
-                case "request":
-                    return requestAutoFill();
+                case "fill":
+                    return requestAutoFill(ASSIST_FLAG_SANITIZED_TEXT);
+                case "save":
+                    return requestAutoFill(ASSIST_FLAG_NON_SANITIZED_TEXT);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -58,15 +63,17 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand {
             pw.println("  help");
             pw.println("    Prints this help text.");
             pw.println("");
-            pw.println("  request [--user USER_ID]");
-            pw.println("    Request auto-fill on the top activity. ");
+            pw.println("  fill [--user USER_ID]");
+            pw.println("    Request provider to auto-fill the top activity. ");
+            pw.println("  save [--user USER_ID]");
+            pw.println("    Request provider to save contents of the top activity. ");
             pw.println("");
         }
     }
 
-    private int requestAutoFill() throws RemoteException {
+    private int requestAutoFill(int flags) throws RemoteException {
         final int userId = getUserIdFromArgs();
-        mService.requestAutoFill(userId, null);
+        mService.requestAutoFill(null, userId, flags);
         return 0;
     }
 
index e6e4b2d..2890964 100644 (file)
@@ -202,6 +202,7 @@ import android.os.storage.StorageManagerInternal;
 import android.os.storage.StorageManager;
 import android.provider.Downloads;
 import android.provider.Settings;
+import android.service.autofill.AutoFillService;
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.service.voice.VoiceInteractionSession;
@@ -507,6 +508,9 @@ public class ActivityManagerService extends IActivityManager.Stub
     // on getting this result before starting to launch its UI).
     static final int PENDING_ASSIST_EXTRAS_LONG_TIMEOUT = 2000;
 
+    // How long to wait in getAutoFillAssistStructure() for the activity to respond with the result.
+    static final int PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT = 2000;
+
     // Maximum number of persisted Uri grants a package is allowed
     static final int MAX_PERSISTED_URI_GRANTS = 128;
 
@@ -683,15 +687,18 @@ public class ActivityManagerService extends IActivityManager.Stub
         public AssistStructure structure = null;
         public AssistContent content = null;
         public Bundle receiverExtras;
+        public int flags;
 
         public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
-                String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
+                String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _flags,
+                int _userHandle) {
             activity = _activity;
             extras = _extras;
             intent = _intent;
             hint = _hint;
             receiver = _receiver;
             receiverExtras = _receiverExtras;
+            flags = _flags;
             userHandle = _userHandle;
         }
         @Override
@@ -12167,7 +12174,7 @@ public class ActivityManagerService extends IActivityManager.Stub
     public Bundle getAssistContextExtras(int requestType) {
         PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
                 null, null, true /* focused */, true /* newSessionId */,
-                UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT);
+                UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT, 0);
         if (pae == null) {
             return null;
         }
@@ -12235,22 +12242,23 @@ public class ActivityManagerService extends IActivityManager.Stub
             IBinder activityToken, boolean focused, boolean newSessionId) {
         return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
                 activityToken, focused, newSessionId,
-                UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT)
+                UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0)
                 != null;
     }
 
     @Override
     public boolean requestAutoFillData(IResultReceiver receiver, Bundle receiverExtras,
-            IBinder activityToken) {
-        return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null, receiver,
+            IBinder activityToken, int flags) {
+        return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_FULL, null, null, receiver,
                 receiverExtras, activityToken, true, true,
-                UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT)
-                != null;
+                UserHandle.getCallingUserId(), null, PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT,
+                flags) != null;
     }
 
     private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
             IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
-            boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout) {
+            boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
+            int flags) {
         enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
                 "enqueueAssistContext()");
         synchronized (this) {
@@ -12289,14 +12297,14 @@ public class ActivityManagerService extends IActivityManager.Stub
             extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName);
             extras.putInt(Intent.EXTRA_ASSIST_UID, activity.app.uid);
             pae = new PendingAssistExtras(activity, extras, intent, hint, receiver, receiverExtras,
-                    userHandle);
+                    flags, userHandle);
             // Increment the sessionId if necessary
             if (newSessionId) {
                 mViSessionId++;
             }
             try {
                 activity.app.thread.requestAssistContextExtras(activity.appToken, pae,
-                        requestType, mViSessionId);
+                        requestType, mViSessionId, flags);
                 mPendingAssistExtras.add(pae);
                 mUiHandler.postDelayed(pae, timeout);
             } catch (RemoteException e) {
@@ -12372,10 +12380,13 @@ public class ActivityManagerService extends IActivityManager.Stub
                 sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content);
                 sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
                         pae.receiverExtras);
+                if (pae.flags > 0) {
+                    sendBundle.putInt(VoiceInteractionSession.KEY_FLAGS, pae.flags);
+                }
                 IBinder autoFillCallback =
-                        extras.getBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK);
+                        extras.getBinder(AutoFillService.KEY_CALLBACK);
                 if (autoFillCallback != null) {
-                    sendBundle.putBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK,
+                    sendBundle.putBinder(AutoFillService.KEY_CALLBACK,
                             autoFillCallback);
                 }
             }
@@ -12409,7 +12420,7 @@ public class ActivityManagerService extends IActivityManager.Stub
             Bundle args) {
         return enqueueAssistContext(requestType, intent, hint, null, null, null,
                 true /* focused */, true /* newSessionId */,
-                userHandle, args, PENDING_ASSIST_EXTRAS_TIMEOUT) != null;
+                userHandle, args, PENDING_ASSIST_EXTRAS_TIMEOUT, 0) != null;
     }
 
     public void registerProcessObserver(IProcessObserver observer) {