Moar AutoFill Framework refactoring...
* Encapsulated application-level auto-fill logic on AutoFillSession.
Currently, Activity.java directly manages the IAutoFillCallback binder
object used to auto-fill its views, but this logic need to be
extended so Views can use it to draw the auto-fill bar.
* Pass auto-fill id and boundaries to requests
So AutoFillUI can display its affordance in the right places.
* Uses a new auto-fill id on View (instead of reusing accessibility's).
That allows moving the logic on whether a new request should be made or
the existing UI moved to the service side.
* Split service methods in 2, for shell cmd and app
And applied the right permission check on both.
* Removed CancelationSignal from onSaveRequest()
Since it's not really needed.
* Etc...
¯\_(ツ)_/¯
BUG: 34637800
BUG: 31001899
Test: CtsAutoFillServiceTestCases passes
Test: manual verification
Change-Id: Ibd0cb2cfff6d0f6bb6b697a423ed5e89df687b82
diff --git a/api/current.txt b/api/current.txt
index 56fedf6..3ee9155 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -35735,7 +35735,7 @@
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
- method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+ method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
@@ -44449,8 +44449,8 @@
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 AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
- field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+ field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+ field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
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
diff --git a/api/system-current.txt b/api/system-current.txt
index b4a374f..ccd2307f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -38759,7 +38759,7 @@
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
- method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+ method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
@@ -47848,8 +47848,8 @@
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 AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
- field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+ field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+ field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
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
diff --git a/api/test-current.txt b/api/test-current.txt
index 9b2bf2b..4e1e31f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -35869,7 +35869,7 @@
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
- method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+ method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
@@ -44754,8 +44754,8 @@
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 AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
- field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+ field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+ field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
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
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 580bb50..1d84ff5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -117,9 +117,8 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.autofill.AutoFillId;
-import android.view.autofill.Dataset;
-import android.view.autofill.DatasetField;
-import android.view.autofill.VirtualViewDelegate;
+import android.view.autofill.AutoFillManager;
+import android.view.autofill.AutoFillSession;
import android.widget.AdapterView;
import android.widget.Toast;
import android.widget.Toolbar;
@@ -848,10 +847,7 @@
private boolean mHasCurrentPermissionsRequest;
@GuardedBy("this")
- private WeakReference<IAutoFillAppCallback> mAutoFillCallback;
-
- @GuardedBy("this")
- private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+ private AutoFillSession mAutoFillSession;
private static native String getDlWarning();
@@ -1704,76 +1700,17 @@
}
/**
- * Lazily sets the {@link #mAutoFillDelegateCallback}.
- */
- private void setAutoFillDelegateCallback() {
- synchronized (this) {
- if (mAutoFillDelegateCallback == null) {
- mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
- // TODO(b/33197203): implement
- };
- }
- }
- }
-
- /**
* Lazily gets the {@link IAutoFillAppCallback} for this activitity.
*
* <p>This callback is used by the {@link AutoFillService} app to auto-fill the activity fields.
*/
- WeakReference<IAutoFillAppCallback> getAutoFillCallback() {
+ IAutoFillAppCallback getAutoFillCallback() {
synchronized (this) {
- if (mAutoFillCallback == null) {
- final IAutoFillAppCallback cb = new IAutoFillAppCallback.Stub() {
- @Override
- public void autoFill(Dataset dataset) throws RemoteException {
- // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
- // dataset.extras to service
- runOnUiThread(() -> {
- final View root = getWindow().getDecorView().getRootView();
- for (DatasetField field : dataset.getFields()) {
- final AutoFillId id = field.getId();
- if (id == null) {
- Log.w(TAG, "autoFill(): null id on " + field);
- continue;
- }
- final int viewId = id.getViewId();
- final View view = root.findViewByAccessibilityIdTraversal(viewId);
- if (view == null) {
- Log.w(TAG, "autoFill(): no View with id " + viewId);
- continue;
- }
-
- // TODO(b/33197203): handle protected value (like credit card)
- if (id.isVirtual()) {
- // Delegate virtual fields to provider.
- setAutoFillDelegateCallback();
- final VirtualViewDelegate mgr = view
- .getAutoFillVirtualViewDelegate(
- mAutoFillDelegateCallback);
- if (mgr == null) {
- Log.w(TAG, "autoFill(): cannot fill virtual " + id
- + "; no auto-fill provider for view "
- + view.getClass());
- continue;
- }
- if (DEBUG_AUTO_FILL) {
- Log.d(TAG, "autoFill(): delegating " + id
- + " to virtual manager " + mgr);
- }
- mgr.autoFill(id.getVirtualChildId(), field.getValue());
- } else {
- // Handle non-virtual fields itself.
- view.autoFill(field.getValue());
- }
- }
- });
- }
- };
- mAutoFillCallback = new WeakReference<IAutoFillAppCallback>(cb);
+ if (mAutoFillSession == null) {
+ mAutoFillSession = new AutoFillSession(this);
}
+ return mAutoFillSession.getCallback();
}
- return mAutoFillCallback;
}
/**
@@ -6067,9 +6004,9 @@
getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
}
- if (mAutoFillCallback != null) {
- writer.print(prefix); writer.print("mAutoFillCallback: " );
- writer.println(mAutoFillCallback.get());
+ if (mAutoFillSession!= null) {
+ writer.print(prefix); writer.print("mAutoFillSession: " );
+ writer.println(mAutoFillSession);
}
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 89510d9..e848080 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -154,6 +154,12 @@
public abstract List<IBinder> getTopVisibleActivities();
/**
+ * Returns the top, focused activity of the currently visible stack, but only if it belongs to
+ * the given UID.
+ */
+ public abstract IBinder getTopVisibleActivity(int uid);
+
+ /**
* Callback for window manager to let activity manager know that docked stack changes its
* minimized state.
*/
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 90fab41..9a22125 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2967,7 +2967,7 @@
if (!forAutoFill) {
r.activity.onProvideAssistContent(content);
} else if (addAutoFillCallback) {
- IAutoFillAppCallback cb = r.activity.getAutoFillCallback().get();
+ IAutoFillAppCallback cb = r.activity.getAutoFillCallback();
if (cb != null) {
data.putBinder(AutoFillService.KEY_CALLBACK, cb.asBinder());
} else {
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index b5cb8f8..1e4f90d 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -89,16 +89,14 @@
/**
* Key of the {@link Bundle} passed to methods such as
- * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
- * containing the extras set by
+ * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by
* {@link android.view.autofill.FillResponse.Builder#setExtras(Bundle)}.
*/
public static final String EXTRA_RESPONSE_EXTRAS = KEY_PREFIX + "RESPONSE_EXTRAS";
/**
* Key of the {@link Bundle} passed to methods such as
- * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
- * containing the extras set by
+ * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by
* {@link android.view.autofill.Dataset.Builder#setExtras(Bundle)}.
*/
public static final String EXTRA_DATASET_EXTRAS = KEY_PREFIX + "DATASET_EXTRAS";
@@ -134,9 +132,9 @@
@Override
public void autoFill(AssistStructure structure, IAutoFillServerCallback callback,
- Bundle extras, int flags) {
+ int flags) {
mHandlerCaller
- .obtainMessageIOOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, extras, callback)
+ .obtainMessageIOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, callback)
.sendToTarget();
}
@@ -179,9 +177,8 @@
final SomeArgs args = (SomeArgs) msg.obj;
final int flags = msg.arg1;
final AssistStructure structure = (AssistStructure) args.arg1;
- final Bundle extras = (Bundle) args.arg2;
- final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg3;
- requestAutoFill(callback, structure, extras, flags);
+ final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg2;
+ requestAutoFill(callback, structure, flags);
break;
} case MSG_AUTHENTICATE_FILL_RESPONSE: {
final int flags = msg.arg1;
@@ -254,8 +251,8 @@
* @param cancellationSignal signal for observing cancel requests.
* @param callback object used to notify the result of the request.
*/
- public abstract void onFillRequest(AssistStructure structure,
- Bundle data, CancellationSignal cancellationSignal, FillCallback callback);
+ public abstract void onFillRequest(AssistStructure structure, Bundle data,
+ CancellationSignal cancellationSignal, FillCallback callback);
/**
* Called when user requests service to save the fields of an {@link Activity}.
@@ -267,20 +264,19 @@
* @param structure {@link Activity}'s view structure.
* @param data bundle containing additional arguments set by the Android system (currently none)
* or data passed by the service in the {@link FillResponse} that originated this call.
- * @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);
+ public abstract void onSaveRequest(AssistStructure structure, Bundle data,
+ SaveCallback callback);
/**
* Called as result of the user action for a {@link FillResponse} that required authentication.
*
* <p>When the {@link FillResponse} required authentication through
- * {@link android.view.autofill.FillResponse.Builder#requiresCustomAuthentication(Bundle, int)}, this
- * call indicates the user is requesting the service to authenticate him/her (and {@code flags}
- * contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains the
- * {@link Bundle} passed to that method.
+ * {@link android.view.autofill.FillResponse.Builder#requiresCustomAuthentication(Bundle, int)},
+ * this call indicates the user is requesting the service to authenticate him/her (and
+ * {@code flags} contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains
+ * the {@link Bundle} passed to that method.
*
* <p>When the {@link FillResponse} required authentication through
* {@link android.view.autofill.FillResponse.Builder#requiresFingerprintAuthentication(
@@ -336,27 +332,28 @@
}
private void requestAutoFill(IAutoFillServerCallback callback, AssistStructure structure,
- Bundle data, int flags) {
- switch (flags) {
- case AUTO_FILL_FLAG_TYPE_FILL:
- final FillCallback fillCallback = new FillCallback(callback);
- if (DEBUG_PENDING_CALLBACKS) {
- addPendingCallback(fillCallback);
- }
- // TODO(b/33197203): hook up the cancelationSignal
- onFillRequest(structure, data, new CancellationSignal(), fillCallback);
- break;
- case AUTO_FILL_FLAG_TYPE_SAVE:
- final SaveCallback saveCallback = new SaveCallback(callback);
- if (DEBUG_PENDING_CALLBACKS) {
- addPendingCallback(saveCallback);
- }
- // TODO(b/33197203): hook up the cancelationSignal
- onSaveRequest(structure, data, new CancellationSignal(), saveCallback);
- break;
- default:
- Log.w(TAG, "invalid flag on requestAutoFill(): " + flags);
+ int flags) {
+ if (DEBUG) Log.d(TAG, "requestAutoFill(): flags=" + flags);
+
+ if ((flags & AUTO_FILL_FLAG_TYPE_FILL) != 0) {
+ final FillCallback fillCallback = new FillCallback(callback);
+ if (DEBUG_PENDING_CALLBACKS) {
+ addPendingCallback(fillCallback);
+ }
+ // TODO(b/33197203): hook up the cancelationSignal
+ onFillRequest(structure, null, new CancellationSignal(), fillCallback);
+ return;
}
+ if ((flags & AUTO_FILL_FLAG_TYPE_SAVE) != 0) {
+ final SaveCallback saveCallback = new SaveCallback(callback);
+ if (DEBUG_PENDING_CALLBACKS) {
+ addPendingCallback(saveCallback);
+ }
+ onSaveRequest(structure, null, saveCallback);
+ return;
+ }
+
+ Log.w(TAG, "invalid flags on requestAutoFill(): " + flags);
}
private void addPendingCallback(CallbackHelper.Dumpable callback) {
diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
index cc83776..8c3898a 100644
--- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
@@ -25,8 +25,7 @@
*
* @hide
*/
-// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
-// itself
+// TODO(b/33197203): rename IAutoFillAppSession
oneway interface IAutoFillAppCallback {
/**
* Auto-fills the activity with the contents of a dataset.
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index ce42107..ace5411 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -27,8 +27,9 @@
*/
oneway interface IAutoFillManagerService {
- void showAutoFillInput(in AutoFillId id, in Rect boundaries);
+ // Called by AutoFillManager (app).
+ void requestAutoFill(in AutoFillId id, in Rect bounds, int flags);
- // TODO(b/33197203): remove it and refactor onShellCommand
- void requestAutoFill(IBinder activityToken, int userId, in Bundle extras, int flags);
+ // Called by ShellCommand only.
+ void requestAutoFillForUser(int userId, int flags);
}
diff --git a/core/java/android/service/autofill/IAutoFillServerCallback.aidl b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
index 185c8f3..f7d5064 100644
--- a/core/java/android/service/autofill/IAutoFillServerCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
@@ -28,8 +28,7 @@
*
* @hide
*/
-// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
-// itself
+// TODO(b/33197203): rename to IAutoFillServerSession
oneway interface IAutoFillServerCallback {
// TODO(b/33197203): document methods
void showResponse(in FillResponse response);
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index fa9786a..3e8087b 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -27,8 +27,7 @@
// TODO(b/33197203): document class and methods
oneway interface IAutoFillService {
// TODO(b/33197203): rename method to make them more consistent
- void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback,
- in Bundle extras, int flags);
+ void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback, int flags);
void authenticateFillResponse(in Bundle extras, int flags);
void authenticateDataset(in Bundle extras, int flags);
void onConnected();
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index d5022d8..e2fb588 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -58,7 +58,7 @@
/**
* Notifies the Android System that an
* {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, SaveCallback)} was successfully fulfilled by the service.
+ * SaveCallback)} was successfully fulfilled by the service.
*
* @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved.
*
@@ -85,7 +85,7 @@
/**
* Notifies the Android System that an
* {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, SaveCallback)} could not be fulfilled by the service.
+ * SaveCallback)} could not be fulfilled by the service.
*
* @param message error message to be displayed to the user.
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ba9bb67..0657bef 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1765,12 +1765,6 @@
*/
int mAccessibilityViewId = NO_ID;
- /**
- * The stable ID of this view for auto-fill purposes.
- */
- private int mAutoFillId = NO_ID;
-
-
private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent;
@@ -4045,9 +4039,9 @@
* input fields and tags (like {@code id}).
* </ul>
*/
- // TODO(b/33197203) (b/34078930): improve documentation: mention all cases, show examples, etc.
- // In particular, be more specific about webview restrictions
- public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x1;
+ // TODO(b/33197203): cannot conflict with flags defined on AutoFillManager until they're removed
+ // (when save is refactored).
+ public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x10000000;
/**
* Set when the user explicitly asked a {@link android.service.autofill.AutoFillService} to save
@@ -4057,7 +4051,9 @@
* (Personally Identifiable Information). For example, the text of password fields should be
* included since that's what's typically saved.
*/
- public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x2;
+ // TODO(b/33197203): cannot conflict with flags defined on AutoFillManager until they're removed
+ // (when save is refactored).
+ public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x20000000;
/**
* Set to true when drawing cache is enabled and cannot be created.
@@ -6940,8 +6936,7 @@
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.
- mAutoFillId = getAccessibilityViewId();
- structure.setAutoFillId(mAutoFillId);
+ structure.setAutoFillId(getAccessibilityViewId());
structure.setAutoFillType(getAutoFillType());
}
@@ -7568,20 +7563,6 @@
}
/**
- * Gets the unique identifier of this view for auto-fill purposes.
- *
- * <p>It's only set after {@link #onProvideAutoFillStructure(ViewStructure, int)} is called.
- *
- * @return The view autofill id or {@link #NO_ID} if
- * {@link #onProvideAutoFillStructure(ViewStructure, int)} was not called yet.
- *
- * @hide
- */
- public int getAutoFillViewId() {
- return mAutoFillId;
- }
-
- /**
* Gets the unique identifier of the window in which this View reseides.
*
* @return The window accessibility id.
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
index cd9842f..cf56e0e 100644
--- a/core/java/android/view/autofill/AutoFillManager.java
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -36,12 +36,16 @@
/**
* Flag used to show the auto-fill UI affordance for a view.
*/
- public static final int FLAG_UPDATE_UI_SHOW = 1 << 0;
+ // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
+ // save is refactored).
+ public static final int FLAG_UPDATE_UI_SHOW = 0x1;
/**
* Flag used to hide the auto-fill UI affordance for a view.
*/
- public static final int FLAG_UPDATE_UI_HIDE = 1 << 1;
+ // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
+ // save is refactored).
+ public static final int FLAG_UPDATE_UI_HIDE = 0x2;
private final IAutoFillManagerService mService;
@@ -64,11 +68,10 @@
* {@link #FLAG_UPDATE_UI_HIDE}.
*/
public void updateAutoFillInput(View view, int flags) {
- if (DEBUG) {
- Log.v(TAG, "updateAutoFillInput(" + view.getAutoFillViewId() + "): flags=" + flags);
- }
+ final Rect bounds = new Rect();
+ view.getBoundsOnScreen(bounds);
- updateAutoFillInput(view, false, View.NO_ID, null, flags);
+ requestAutoFill(new AutoFillId(view.getAccessibilityViewId()), bounds, flags);
}
/**
@@ -79,56 +82,22 @@
*
* @param parent parent view.
* @param childId id identifying the virtual child inside the parent view.
- * @param boundaries boundaries of the child (inside the parent; could be {@code null} when
+ * @param bounds absolute boundaries of the child in the window (could be {@code null} when
* flag is {@link #FLAG_UPDATE_UI_HIDE}.
* @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
* {@link #FLAG_UPDATE_UI_HIDE}.
*/
- public void updateAutoFillInput(View parent, int childId, @Nullable Rect boundaries,
+ public void updateAutoFillInput(View parent, int childId, @Nullable Rect bounds,
int flags) {
+ requestAutoFill(new AutoFillId(parent.getAccessibilityViewId(), childId), bounds, flags);
+ }
+
+ private void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
if (DEBUG) {
- Log.v(TAG, "updateAutoFillInput(" + parent.getAutoFillViewId() + ", " + childId
- + "): boundaries=" + boundaries + ", flags=" + flags);
+ Log.v(TAG, "requestAutoFill(): id=" + id + ", bounds=" + bounds + ", flags=" + flags);
}
- updateAutoFillInput(parent, true, childId, boundaries, flags);
- }
-
- private void updateAutoFillInput(View view, boolean virtual, int childId, Rect boundaries,
- int flags) {
- if ((flags & FLAG_UPDATE_UI_SHOW) != 0) {
- final int viewId = view.getAutoFillViewId();
- final AutoFillId id = virtual
- ? new AutoFillId(viewId, childId)
- : new AutoFillId(viewId);
- showAutoFillInput(id, boundaries);
- return;
- }
- // TODO(b/33197203): handle FLAG_UPDATE_UI_HIDE
- }
-
- private void showAutoFillInput(AutoFillId id, Rect boundaries) {
- final int autoFillViewId = id.getViewId();
- /*
- * TODO(b/33197203): currently SHOW_AUTO_FILL_BAR is only set once per activity (i.e, when
- * the view does not have an auto-fill id), but it should be called again for views that
- * were not part of the initial auto-fill dataset returned by the service. For example:
- *
- * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
- * 2.User taps `first_name`.
- * 3.Service returns a dataset with ids for `first_name` and `last_name`.
- * 4.When user taps `first_name` (again) or `last_name`, flag should not have
- * SHOW_AUTO_FILL_BAR set, but when user taps `address`, it should (since that field was
- * not part of the initial dataset).
- *
- * Similarly, once the activity is auto-filled, the flag logic should be reset (so if the
- * user taps the view again, a new auto-fill request is made)
- */
- if (autoFillViewId != View.NO_ID) {
- return;
- }
-
try {
- mService.showAutoFillInput(id, boundaries);
+ mService.requestAutoFill(id, bounds, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/autofill/AutoFillSession.java b/core/java/android/view/autofill/AutoFillSession.java
new file mode 100644
index 0000000..eec7a82
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillSession.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 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.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+
+import android.app.Activity;
+import android.os.RemoteException;
+import android.service.autofill.IAutoFillAppCallback;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * An auto-fill session associated with an activity.
+ *
+ * @hide
+ */
+public final class AutoFillSession {
+
+ private static final String TAG = "AutoFillSession";
+
+ private final IAutoFillAppCallback mCallback = new IAutoFillAppCallback.Stub() {
+ @Override
+ public void autoFill(Dataset dataset) throws RemoteException {
+ final Activity activity = mActivity.get();
+ if (activity == null) {
+ if (DEBUG) Log.d(TAG, "autoFill(): activity already GCed");
+ return;
+ }
+ // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
+ // dataset.extras to service
+ activity.runOnUiThread(() -> {
+ final View root = activity.getWindow().getDecorView().getRootView();
+ for (DatasetField field : dataset.getFields()) {
+ final AutoFillId id = field.getId();
+ if (id == null) {
+ Log.w(TAG, "autoFill(): null id on " + field);
+ continue;
+ }
+ final int viewId = id.getViewId();
+ final View view = root.findViewByAccessibilityIdTraversal(viewId);
+ if (view == null) {
+ Log.w(TAG, "autoFill(): no View with id " + viewId);
+ continue;
+ }
+
+ // TODO(b/33197203): handle protected value (like credit card)
+ if (id.isVirtual()) {
+ // Delegate virtual fields.
+ setAutoFillDelegateCallback();
+ final VirtualViewDelegate delegate = view
+ .getAutoFillVirtualViewDelegate(
+ mAutoFillDelegateCallback);
+ if (delegate == null) {
+ Log.w(TAG, "autoFill(): cannot fill virtual " + id
+ + "; no VirtualViewDelegate for view "
+ + view.getClass());
+ continue;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "autoFill(): delegating " + id
+ + " to VirtualViewDelegate " + delegate);
+ }
+ delegate.autoFill(id.getVirtualChildId(), field.getValue());
+ } else {
+ // Handle non-virtual fields itself.
+ view.autoFill(field.getValue());
+ }
+ }
+ });
+ }
+ };
+
+ private final WeakReference<Activity> mActivity;
+
+ @GuardedBy("this")
+ private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+
+ public AutoFillSession(Activity activity) {
+ mActivity = new WeakReference<>(activity);
+ }
+
+ public IAutoFillAppCallback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Lazily sets the {@link #mAutoFillDelegateCallback}.
+ */
+ private void setAutoFillDelegateCallback() {
+ synchronized (this) {
+ if (mAutoFillDelegateCallback == null) {
+ mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
+ // TODO(b/33197203): implement
+ };
+ }
+ }
+ }
+
+}
diff --git a/core/java/android/view/autofill/Dataset.java b/core/java/android/view/autofill/Dataset.java
index b11eecc..18a08f9 100644
--- a/core/java/android/view/autofill/Dataset.java
+++ b/core/java/android/view/autofill/Dataset.java
@@ -284,7 +284,7 @@
* Sets a {@link Bundle} that will be passed to subsequent calls to
* {@link android.service.autofill.AutoFillService} methods such as
* {@link android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
- * Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
+ * Bundle, android.service.autofill.SaveCallback)}, using
* {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} as the key.
*
* <p>It can be used to keep service state in between calls.
diff --git a/core/java/android/view/autofill/FillResponse.java b/core/java/android/view/autofill/FillResponse.java
index 67eb85a..48dbb84 100644
--- a/core/java/android/view/autofill/FillResponse.java
+++ b/core/java/android/view/autofill/FillResponse.java
@@ -149,9 +149,9 @@
* Dataset.Builder#setExtras(Bundle)} methods to pass {@link Bundle}s with service-specific data use
* to identify this response on future calls (like {@link
* android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
- * Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback)}) - such bundles
- * will be available as the {@link android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS}
- * and {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} extras in that method's
+ * Bundle, android.service.autofill.SaveCallback)}) - such bundles will be available as the
+ * {@link android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} and
+ * {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} extras in that method's
* {@code extras} argument.
*/
public final class FillResponse implements Parcelable {
@@ -369,9 +369,8 @@
/**
* Adds ids of additional fields that the service would be interested to save (through
* {@link android.service.autofill.AutoFillService#onSaveRequest(
- * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
- * android.service.autofill.SaveCallback)}) but were not indirectly set through {@link
- * #addDataset(Dataset)}.
+ * android.app.assist.AssistStructure, Bundle, android.service.autofill.SaveCallback)})
+ * but were not indirectly set through {@link #addDataset(Dataset)}.
*
* <p>See {@link FillResponse} for examples.
*/
@@ -386,8 +385,8 @@
* Sets a {@link Bundle} that will be passed to subsequent calls to {@link
* android.service.autofill.AutoFillService} methods such as {@link
* android.service.autofill.AutoFillService#onSaveRequest(
- * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
- * android.service.autofill.SaveCallback)}, using {@link
+ * android.app.assist.AssistStructure, Bundle, android.service.autofill.SaveCallback)},
+ * using {@link
* android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key.
*
* <p>It can be used when to keep service state in between calls.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 072fe4a..c0bec69 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9030,8 +9030,7 @@
final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
if (afm != null) {
if (DEBUG_AUTOFILL) {
- Log.v(LOG_TAG, "onFocusChanged(): id=" + getAutoFillViewId() + ", focused= "
- + focused);
+ Log.v(LOG_TAG, "onFocusChanged(false): id=" + getAccessibilityViewId());
}
afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_HIDE);
}
@@ -10615,7 +10614,7 @@
protected void viewClicked(InputMethodManager imm) {
final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
if (afm != null) {
- if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAutoFillViewId());
+ if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAccessibilityViewId());
// TODO(b/33197203): integrate with onFocus and/or move to view?
afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_SHOW);
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 47ac1ce..637656c 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -21,6 +21,7 @@
import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
import android.Manifest;
+import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -31,7 +32,6 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -39,16 +39,15 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.autofill.IAutoFillManagerService;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.util.LocalLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import android.view.autofill.AutoFillId;
import com.android.internal.annotations.GuardedBy;
@@ -56,10 +55,12 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.server.FgThread;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
/**
* Entry point service for auto-fill management.
@@ -76,7 +77,8 @@
private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
protected static final int MSG_UNBIND = 1;
- protected static final int MSG_SHOW_AUTO_FILL = 2;
+ protected static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 2;
+ protected static final int MSG_REQUEST_AUTO_FILL = 3;
private final AutoFillManagerServiceStub mServiceStub;
private final AutoFillUI mUi;
@@ -91,11 +93,23 @@
public void executeMessage(Message msg) {
switch (msg.what) {
case MSG_UNBIND: {
- removeStaleServiceForUser(msg.arg1);
+ synchronized (mLock) {
+ removeCachedServiceLocked(msg.arg1);
+ }
return;
- } case MSG_SHOW_AUTO_FILL: {
+ } case MSG_REQUEST_AUTO_FILL_FOR_USER: {
+ final int userId = msg.arg1;
+ final int flags = msg.arg2;
+ handleAutoFillForUser(userId, flags);
+ return;
+ } case MSG_REQUEST_AUTO_FILL: {
final SomeArgs args = (SomeArgs) msg.obj;
- showAutoFillInput(msg.arg1, (AutoFillId) args.arg1, (Rect) args.arg2);
+ final int userId = msg.arg1;
+ final int flags = msg.arg2;
+ final IBinder activityToken = (IBinder) args.arg1;
+ final AutoFillId autoFillId = (AutoFillId) args.arg2;
+ final Rect bounds = (Rect) args.arg3;
+ handleAutoFill(activityToken, userId, autoFillId, bounds, flags);
return;
} default: {
Slog.w(TAG, "Invalid message: " + msg);
@@ -123,6 +137,9 @@
@GuardedBy("mLock")
private SparseArray<AutoFillManagerServiceImpl> mServicesCache = new SparseArray<>();
+ // TODO(b/33197203): set a different max (or disable it) on low-memory devices.
+ private final LocalLog mRequestsHistory = new LocalLog(100);
+
public AutoFillManagerService(Context context) {
super(context);
@@ -166,11 +183,11 @@
if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
+ serviceComponent + ", info: " + serviceInfo);
if (serviceInfo == null) {
- Slog.w(TAG, "no service info for " + serviceComponent);
+ if (DEBUG) Slog.d(TAG, "no service info for " + serviceComponent);
return null;
}
- return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, FgThread.getHandler(),
- userId, serviceInfo.applicationInfo.uid, serviceComponent,
+ return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, mRequestsHistory,
+ FgThread.getHandler(), userId, serviceInfo.applicationInfo.uid, serviceComponent,
SERVICE_BINDING_LIFETIME_MS);
}
@@ -198,87 +215,92 @@
}
// Keep service connection alive for a while, in case user needs to interact with it
// (for example, to save the data that was inputted in)
+ if (mHandlerCaller.hasMessages(MSG_UNBIND)) {
+ mHandlerCaller.removeMessages(MSG_UNBIND);
+ }
mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessageI(MSG_UNBIND, userId),
SERVICE_BINDING_LIFETIME_MS);
return service;
}
/**
- * Removes a cached service, but respecting its TTL.
+ * Removes a cached service for a given user.
*/
- private void removeStaleServiceForUser(int userId) {
- synchronized (mLock) {
- removeCachedService(userId, false);
- }
- }
-
- /**
- * Removes a cached service, even if it has TTL.
- */
- void removeCachedServiceForUserLocked(int userId) {
- removeCachedService(userId, true);
- }
-
- private void removeCachedService(int userId, boolean force) {
+ void removeCachedServiceLocked(int userId) {
if (DEBUG) Log.d(TAG, "removing cached service for userId " + userId);
final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
if (service == null) {
- Log.w(TAG, "removeCachedServiceForUser(): no cached service for userId " + userId);
- return;
- }
- if (!force) {
- // Check TTL first.
- final long now = SystemClock.uptimeMillis();
- if (service.mEstimateTimeOfDeath > now) {
- if (DEBUG) {
- final StringBuilder msg = new StringBuilder("service has some TTL left: ");
- TimeUtils.formatDuration(service.mEstimateTimeOfDeath - now, msg);
- Log.d(TAG, msg.toString());
- }
- return;
+ if (DEBUG) {
+ Log.d(TAG, "removeCachedServiceForUser(): no cached service for userId " + userId);
}
+ return;
}
mServicesCache.delete(userId);
service.stopLocked();
-
}
-
- private void requestAutoFillLocked(IBinder activityToken, int userId, Bundle extras,
- int flags) {
- final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
- if (service != null) {
- service.requestAutoFill(activityToken, extras, flags);
+ private void handleAutoFill(IBinder activityToken, int userId, AutoFillId autoFillId,
+ Rect bounds, int flags) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service != null) {
+ // TODO(b/33197203): must pass AUTO_FILL_FLAG_TYPE_FILL because AM is expecting
+ // either that flag or AUTO_FILL_FLAG_TYPE_SAVE; should go away once save is
+ // refactored
+ flags |= AUTO_FILL_FLAG_TYPE_FILL;
+ service.requestAutoFillLocked(activityToken, autoFillId, bounds, flags);
+ }
}
}
- private void showAutoFillInput(int userId, AutoFillId id, Rect rect) {
- if (DEBUG) Slog.d(TAG, "handler.showAutoFillInput(): id=" + id + ", rect=" + rect);
-
+ private void handleAutoFillForUser(int userId, int flags) {
+ if (DEBUG) {
+ Slog.d(TAG, "handler.requestAutoFillForUser(): id=" + userId + ", flags=" + flags);
+ }
+ final List<IBinder> topActivities = LocalServices
+ .getService(ActivityManagerInternal.class).getTopVisibleActivities();
+ if (DEBUG)
+ Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
+ if (topActivities.isEmpty()) {
+ Slog.w(TAG, "Could not get top activity");
+ return;
+ }
+ final IBinder activityToken = topActivities.get(0);
synchronized (mLock) {
- requestAutoFillLocked(null, userId, null, AUTO_FILL_FLAG_TYPE_FILL);
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "no service for user " + userId);
+ return;
+ }
+ service.requestAutoFillLocked(activityToken, null, null, flags);
}
}
final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
@Override
- public void showAutoFillInput(AutoFillId id, Rect boundaries) {
- if (DEBUG) Slog.d(TAG, "showAutoFillInput(): id=" + id + ", boundaries=" + boundaries);
+ public void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
+ if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", autoFillId=" + id
+ + ", bounds=" + bounds);
- // TODO(b/33197203): fail if it's not called by same uid as the top activity
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW_AUTO_FILL,
- UserHandle.getCallingUserId(), id, boundaries));
+ // Make sure its called by the top activity.
+ final int uid = Binder.getCallingUid();
+ final IBinder activityToken = LocalServices.getService(ActivityManagerInternal.class)
+ .getTopVisibleActivity(uid);
+ if (activityToken == null) {
+ throw new SecurityException("uid " + uid + " does not own the top activity");
+ }
+
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL,
+ UserHandle.getCallingUserId(), flags, activityToken, id, bounds));
}
@Override
- public void requestAutoFill(IBinder activityToken, int userId, Bundle extras, int flags) {
- if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
+ public void requestAutoFillForUser(int userId, int flags) {
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- synchronized (mLock) {
- requestAutoFillLocked(activityToken, userId, extras, flags);
- }
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageII(
+ MSG_REQUEST_AUTO_FILL_FOR_USER, userId, flags));
}
@Override
@@ -304,6 +326,8 @@
}
}
}
+ pw.println("Requests history:");
+ mRequestsHistory.reverseDump(fd, pw, args);
}
@Override
@@ -326,7 +350,7 @@
public void onChange(boolean selfChange, Uri uri, int userId) {
if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId);
synchronized (mLock) {
- removeCachedServiceForUserLocked(userId);
+ removeCachedServiceLocked(userId);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index 77e7b31..ad5cc20 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -16,16 +16,18 @@
package com.android.server.autofill;
-import static com.android.server.autofill.Helper.DEBUG;
-import static com.android.server.autofill.Helper.bundleToString;
import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_ERROR;
import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_REQUESTED;
import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_SUCCESS;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
+import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_HIDE;
+
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.bundleToString;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.assist.AssistStructure;
import android.content.BroadcastReceiver;
@@ -35,6 +37,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.graphics.Rect;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -53,6 +56,7 @@
import android.service.autofill.IAutoFillServerCallback;
import android.service.autofill.IAutoFillService;
import android.service.voice.VoiceInteractionSession;
+import android.util.LocalLog;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -64,9 +68,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
-import com.android.server.LocalServices;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@@ -86,6 +90,7 @@
private final int mUserId;
private final int mUid;
private final ComponentName mComponent;
+ private final String mComponentName;
private final Context mContext;
private final IActivityManager mAm;
private final Object mLock;
@@ -93,14 +98,15 @@
private final AutoFillManagerService mManagerService;
private final AutoFillUI mUi;
- // TODO(b/33197203): improve its usage
- // - set maximum number of entries
- // - disable on low-memory devices.
- private final List<String> mRequestHistory = new LinkedList<>();
+ // Token used for fingerprint authentication
+ // TODO(b/33197203): create on demand?
+ private final IBinder mAuthToken = new Binder();
@GuardedBy("mLock")
private final List<QueuedRequest> mQueuedRequests = new LinkedList<>();
+ private final LocalLog mRequestsHistory;
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -140,9 +146,10 @@
if (DEBUG) Slog.d(TAG, "queued requests:" + mQueuedRequests.size());
}
for (final QueuedRequest request: mQueuedRequests) {
- requestAutoFillLocked(request.activityToken, request.extras, request.flags,
- false);
+ requestAutoFillLocked(request.activityToken, request.autoFillId,
+ request.bounds, request.flags, false);
}
+ mQueuedRequests.clear();
}
}
@@ -151,7 +158,7 @@
if (DEBUG) Slog.d(TAG, name + " disconnected");
synchronized (mLock) {
mService = null;
- mManagerService.removeCachedServiceForUserLocked(mUserId);
+ mManagerService.removeCachedServiceLocked(mUserId);
}
}
};
@@ -181,9 +188,9 @@
Slog.w(TAG, "no server callback for id " + resultCode);
return;
}
- session.mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+ session.setAppCallback(appBinder);
}
- mService.autoFill(structure, session.mServerCallback, session.mExtras, flags);
+ mService.autoFill(structure, session.mServerCallback, flags);
}
};
@@ -198,15 +205,17 @@
long mEstimateTimeOfDeath;
AutoFillManagerServiceImpl(AutoFillManagerService managerService, AutoFillUI ui,
- Context context, Object lock, Handler handler, int userId, int uid,
- ComponentName component, long ttl) {
+ Context context, Object lock, LocalLog requestsHistory, Handler handler, int userId,
+ int uid, ComponentName component, long ttl) {
mManagerService = managerService;
mUi = ui;
mContext = context;
mLock = lock;
+ mRequestsHistory = requestsHistory;
mUserId = userId;
mUid = uid;
mComponent = component;
+ mComponentName = mComponent.flattenToShortString();
mAm = ActivityManager.getService();
setLifeExpectancy(ttl);
@@ -254,60 +263,86 @@
/**
* Asks service to auto-fill an activity.
*
- * @param activityToken activity token
- * @param extras bundle to be passed to the {@link AutoFillService} method.
+ * @param activityToken activity token.
+ * @param autoFillId id of the view that requested auto-fill.
* @param flags optional flags.
*/
- void requestAutoFill(@Nullable IBinder activityToken, @Nullable Bundle extras, int flags) {
- synchronized (mLock) {
- if (!mBound) {
- Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
- return;
- }
+ void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId,
+ @Nullable Rect bounds, int flags) {
+ if (!mBound) {
+ Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
+ return;
}
- // 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(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)
- Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
- if (topActivities.isEmpty()) {
- Slog.w(TAG, "Could not get top activity");
- return;
- }
- activityToken = topActivities.get(0);
- }
-
- final String historyItem = TimeUtils.formatForLogging(System.currentTimeMillis())
- + " - " + activityToken;
- synchronized (mLock) {
- mRequestHistory.add(historyItem);
- requestAutoFillLocked(activityToken, extras, flags, true);
- }
+ requestAutoFillLocked(activityToken, autoFillId, bounds, flags, true);
}
- private void requestAutoFillLocked(IBinder activityToken, @Nullable Bundle extras, int flags,
- boolean queueIfNecessary) {
+ private void requestAutoFillLocked(IBinder activityToken, AutoFillId autoFillId, Rect bounds,
+ 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(new QueuedRequest(activityToken, extras, flags));
+ if (DEBUG) Slog.d(TAG, "requestAutoFillLocked(): service not set yet, queuing it");
+ mQueuedRequests.add(new QueuedRequest(activityToken, autoFillId, bounds, flags));
+ return;
+ }
+ if (activityToken == null) {
+ // Sanity check
+ Slog.wtf(TAG, "requestAutoFillLocked(): null activityToken");
+ return;
+ }
+
+ final Session session = getSessionByTokenLocked(activityToken);
+
+ if (session != null) {
+ // Session already exist, update UI instead...
+ /*
+ * TODO(b/33197203): currently, it's always reusing the session, regardless of the
+ * requested autoFillId, but it should start a new session for views that
+ * were not part of the initial auto-fill dataset returned by the service. For example:
+ *
+ * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
+ * 2.User taps `first_name`.
+ * 3.Service returns a dataset with ids for `first_name` and `last_name`.
+ * 4.When user taps `first_name` (again) or `last_name`, session should be reused, but
+ * when user taps `address`, it should start a new session (since that field was
+ * not part of the initial dataset).
+ *
+ * Similarly, once the activity is auto-filled, the flag logic should be reset (so if
+ * the user taps the view again, a new auto-fill request is made)
+ */
+ if (DEBUG) {
+ Slog.d(TAG, "requestAutoFillLocked(): reusing session for token "
+ + activityToken + ", id " + autoFillId + " and flags " + flags);
+ }
+
+ if ((flags & FLAG_UPDATE_UI_HIDE) != 0) {
+ // TODO(b/33197203): handle it?
+ if (DEBUG) Slog.d(TAG, "ignoring FLAG_UPDATE_UI_HIDE request for " + autoFillId);
+
+ return;
+ }
+
+ session.mCurrentAutoFillId = autoFillId;
+ session.mCurrentBounds = bounds;
+ mUi.showResponse(mUserId, session.mId, autoFillId, bounds, session.mCurrentResponse);
return;
}
final int sessionId = ++sSessionIdCounter;
- final Session session = new Session(sessionId, extras);
- mSessions.put(sessionId, session);
+ if (DEBUG) {
+ Slog.d(TAG, "requestAutoFillLocked(): new session (id=" + sessionId + " for token "
+ + activityToken + " and autoFillId " + autoFillId);
+ }
+
+ final Session newSession = new Session(sessionId, activityToken, autoFillId, bounds);
+ mSessions.put(sessionId, newSession);
+
+ final String historyItem = "s=" + mComponentName + " u=" + mUserId + " f=" + flags
+ + " a=" + activityToken + " i=" + autoFillId + " b=" + bounds;
+ mRequestsHistory.log(historyItem);
/*
* TODO(b/33197203): apply security checks below:
@@ -324,10 +359,79 @@
Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
}
} catch (RemoteException e) {
- // Should happen, it's a local call.
+ // Should not happen, it's a local call.
}
}
+ /**
+ * Called by UI to trigger a save request to the service.
+ */
+ void requestSaveLocked(int sessionId) {
+ // TODO(b/33197203): add MetricsLogger call
+ // TODO(b/33197203): use handler?
+ // TODO(b/33197203): show error on UI on Slog.w situations below???
+
+ if (mService == null) {
+ Slog.w(TAG, "requestSave(): service is null");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null) {
+ Slog.w(TAG, "requestSave(): no session with id " + sessionId);
+ return;
+ }
+ final IBinder activityToken = session.mActivityToken.get();
+ if (activityToken == null) {
+ Slog.w(TAG, "activity token for session " + sessionId + " already GCed");
+ return;
+ }
+
+ /*
+ * 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(b/33197203): refactor save logic so it uses a cached AssistStructure, and get
+ the extras to be sent to the service based on the response / dataset in the session.
+ Something like:
+ final Bundle extras = (responseExtras == null && datasetExtras == null)
+ ? null : new Bundle();
+ if (responseExtras != null) {
+ if (DEBUG) Slog.d(TAG, "response extras on save notification: " +
+ bundleToString(responseExtras));
+ extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
+ }
+ if (datasetExtras != null) {
+ if (DEBUG) Slog.d(TAG, "dataset extras on save notification: " +
+ bundleToString(datasetExtras));
+ extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
+ }
+
+ */
+
+ if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken,
+ AUTO_FILL_FLAG_TYPE_SAVE)) {
+ Slog.w(TAG, "failed to save for " + activityToken);
+ }
+ } catch (RemoteException e) {
+ // Should not happen, it's a local call.
+ }
+ }
+
+ private Session getSessionByTokenLocked(IBinder activityToken) {
+ final int size = mSessions.size();
+ for (int i = 0; i < size; i++) {
+ final Session session = mSessions.valueAt(i);
+ if (activityToken.equals(session.mActivityToken.get())) {
+ return session;
+ }
+ }
+ return null;
+ }
+
void stopLocked() {
if (DEBUG) Slog.d(TAG, "stopLocked()");
@@ -443,12 +547,13 @@
pw.print(prefix); pw.print("mUserId="); pw.println(mUserId);
pw.print(prefix); pw.print("mUid="); pw.println(mUid);
- pw.print(prefix); pw.print("mComponent="); pw.println(mComponent.flattenToShortString());
+ pw.print(prefix); pw.print("mComponent="); pw.println(mComponentName);
pw.print(prefix); pw.print("mService: "); pw.println(mService);
pw.print(prefix); pw.print("mBound="); pw.println(mBound);
pw.print(prefix); pw.print("mEstimateTimeOfDeath=");
TimeUtils.formatDuration(mEstimateTimeOfDeath, SystemClock.uptimeMillis(), pw);
- pw.println();
+ pw.println();
+ pw.print(prefix); pw.print("mAuthToken: "); pw.println(mAuthToken);
if (DEBUG) {
// ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
@@ -456,14 +561,6 @@
mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
}
- if (mRequestHistory.isEmpty()) {
- pw.print(prefix); pw.println("No history");
- } else {
- pw.print(prefix); pw.println("History:");
- for (int i = 0; i < mRequestHistory.size(); i++) {
- pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mRequestHistory.get(i));
- }
- }
if (mQueuedRequests.isEmpty()) {
pw.print(prefix); pw.println("No queued requests");
} else {
@@ -480,38 +577,37 @@
} else {
pw.print(prefix); pw.print(size); pw.println(" sessions:");
for (int i = 0; i < size; i++) {
- pw.print(prefix2); pw.print(mSessions.keyAt(i));
- final Session session = mSessions.valueAt(i);
- if (session.mAppCallback == null) {
- pw.println("(no appCallback)");
- } else {
- pw.print(" (app callback: "); pw.print(session.mAppCallback) ; pw.println(")");
- }
+ pw.print(prefix); pw.print("#"); pw.println(i + 1);
+ mSessions.valueAt(i).dumpLocked(prefix2, pw);
}
- pw.println();
}
}
@Override
public String toString() {
return "AutoFillManagerServiceImpl: [userId=" + mUserId + ", uid=" + mUid
- + ", component=" + mComponent.flattenToShortString() + "]";
+ + ", component=" + mComponentName + "]";
}
private static final class QueuedRequest {
final IBinder activityToken;
- final Bundle extras;
+ final AutoFillId autoFillId;
+ final Rect bounds;
final int flags;
- QueuedRequest(IBinder activityToken, Bundle extras, int flags) {
+ QueuedRequest(IBinder activityToken, AutoFillId autoFillId, Rect bounds, int flags) {
this.activityToken = activityToken;
- this.extras = extras;
+ this.autoFillId = autoFillId;
+ this.bounds = bounds;
this.flags = flags;
}
@Override
public String toString() {
- return "flags: " + flags + " token: " + activityToken;
+ if (!DEBUG) return super.toString();
+
+ return "QueuedRequest: [flags=" + flags + ", token=" + activityToken
+ + ", id=" + autoFillId + ", bounds=" + bounds;
}
}
@@ -532,11 +628,17 @@
private final class Session {
private final int mId;
- private final Bundle mExtras;
+ private final WeakReference<IBinder> mActivityToken;
+
private IAutoFillAppCallback mAppCallback;
- // Token used on fingerprint authentication
- private final IBinder mToken = new Binder();
+ // Current view where the auto-fill bar is displayed
+ @GuardedBy("mLock")
+ private AutoFillId mCurrentAutoFillId;
+ @GuardedBy("mLock")
+ private Rect mCurrentBounds;
+ @GuardedBy("mLock")
+ private FillResponse mCurrentResponse;
private final IFingerprintService mFingerprintService;
@@ -710,13 +812,28 @@
}
};
- private Session(int id, Bundle extras) {
+ private Session(int id, IBinder activityToken, AutoFillId autoFillId, Rect bounds) {
this.mId = id;
- this.mExtras = extras;
+ this.mActivityToken = new WeakReference<>(activityToken);
+ this.mCurrentAutoFillId = autoFillId;
+ this.mCurrentBounds = bounds;
this.mFingerprintService = IFingerprintService.Stub
.asInterface(ServiceManager.getService("fingerprint"));
}
+ void setAppCallback(IBinder appBinder) {
+ try {
+ appBinder.linkToDeath(() -> {
+ if (DEBUG) Slog.d(TAG, "app callback died");
+ // TODO(b/33197203): more cleanup here?
+ mAppCallback = null;
+ }, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "linkToDeath() failed: " + e);
+ }
+ mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+ }
+
private void showResponseLocked(FillResponse response, boolean authRequired) {
if (DEBUG) Slog.d(TAG, "showResponse(directly=" + mAutoFillDirectly
+ ", authRequired=" + authRequired +"):" + response);
@@ -735,7 +852,8 @@
if (!authRequired) {
// TODO(b/33197203): add MetricsLogger call
- mUi.showOptions(mUserId, mId, response);
+ mCurrentResponse = response;
+ mUi.showResponse(mUserId, mId, mCurrentAutoFillId, mCurrentBounds, mCurrentResponse);
return;
}
@@ -768,8 +886,8 @@
mDatasetRequiringAuth = dataset;
final boolean requiresFingerprint = dataset.hasCryptoObject();
if (requiresFingerprint) {
- // TODO(b/33197203): check if fingerprint is available first and call error callback
- // with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
+ // TODO(b/33197203): check if fingerprint is available first and call error
+ // callback with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
// Start scanning for the fingerprint.
scanFingerprint(dataset.getCryptoObjectOpId());
// Displays the message asking the user to tap (or fingerprint) for AutoFill.
@@ -785,14 +903,27 @@
}
}
+ void dumpLocked(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mId: "); pw.println(mId);
+ pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken.get());
+ pw.print(prefix); pw.print("mCurrentAutoFillId: "); pw.println(mCurrentAutoFillId);
+ pw.print(prefix); pw.print("mCurrentBounds: "); pw.println(mCurrentBounds);
+ pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
+ pw.print(prefix);
+ pw.print("mResponseRequiringAuth: "); pw.println(mResponseRequiringAuth);
+ pw.print(prefix);
+ pw.print("mDatasetRequiringAuth: "); pw.println(mDatasetRequiringAuth);
+ pw.print(prefix); pw.print("mAutoFillDirectly: "); pw.println(mAutoFillDirectly);
+ }
+
private void autoFillAppLocked(Dataset dataset, boolean removeSelf) {
try {
if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
mAppCallback.autoFill(dataset);
- // TODO(b/33197203): temporarily hack: show the save notification, since save is
- // not integrated with IME yet.
- mUi.showSaveNotification(mUserId, null, dataset);
+ // TODO(b/33197203): temporarily hack: show the save notification after autofilled,
+ // since save is not automatically detected yet.
+ mUi.showSaveUI(mUserId, mId);
} catch (RemoteException e) {
Slog.w(TAG, "Error auto-filling activity: " + e);
@@ -812,7 +943,8 @@
final long token = Binder.clearCallingIdentity();
try {
// TODO(b/33197203): set a timeout?
- mFingerprintService.authenticate(mToken, opId, mUserId, mServiceReceiver, 0, null);
+ mFingerprintService.authenticate(mAuthToken, opId, mUserId, mServiceReceiver, 0,
+ null);
} catch (RemoteException e) {
// Local call, shouldn't happen.
} finally {
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
index 26f2451..4998e3f 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -74,7 +74,7 @@
private int requestAutoFill(int flags) throws RemoteException {
final int userId = getUserIdFromArgs();
- mService.requestAutoFill(null, userId, null, flags);
+ mService.requestAutoFillForUser(userId, flags);
return 0;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index ad525d4..6618a27 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -15,11 +15,8 @@
*/
package com.android.server.autofill;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
import static com.android.server.autofill.Helper.DEBUG;
-import static com.android.server.autofill.Helper.bundleToString;
import android.app.Activity;
import android.app.Notification;
@@ -31,9 +28,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
-import android.service.autofill.AutoFillService;
import android.util.Slog;
import android.view.autofill.AutoFillId;
import android.view.autofill.Dataset;
@@ -87,12 +84,15 @@
/**
* Shows the options from a {@link FillResponse} so the user can pick up the proper
- * {@link Dataset} (when the response has one).
+ * {@link Dataset} (when the response has one) for a given view (identified by
+ * {@code autoFillId}).
*/
- void showOptions(int userId, int sessionId, FillResponse response) {
+ void showResponse(int userId, int sessionId, AutoFillId autoFillId, Rect bounds,
+ FillResponse response) {
+ if (DEBUG) Slog.d(TAG, "showResponse: id=" + autoFillId + ", bounds=" + bounds);
// TODO(b/33197203): proper implementation
- // TODO(b/33197203): make sure if removes the callback from cache
- showOptionsNotification(userId, sessionId, response);
+ // TODO(b/33197203): make sure if removes the session from cache
+ showOptionsNotification(userId, sessionId, autoFillId, response);
}
/**
@@ -127,6 +127,13 @@
}
/**
+ * Shows the UI asking the user to save for auto-fill.
+ */
+ void showSaveUI(int userId, int sessionId) {
+ showSaveNotification(userId, sessionId);
+ }
+
+ /**
* Called by service after the user user the fingerprint sensors to authenticate.
*/
void dismissFingerprintRequest(int userId, boolean success) {
@@ -150,26 +157,10 @@
return service;
}
- private void onSaveRequested(int userId, Bundle responseExtras, Bundle datasetExtras) {
+ private void onSaveRequested(int userId, int sessionId) {
synchronized (mLock) {
final AutoFillManagerServiceImpl service = getServiceLocked(userId);
- if (service == null) return;
-
- final Bundle extras = (responseExtras == null && datasetExtras == null)
- ? null : new Bundle();
-
- if (responseExtras != null) {
- if (DEBUG) Slog.d(TAG, "response extras on save notification: " +
- bundleToString(responseExtras));
- extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
- }
- if (datasetExtras != null) {
- if (DEBUG) Slog.d(TAG, "dataset extras on save notificataion: " +
- bundleToString(datasetExtras));
- extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
- }
-
- service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_SAVE);
+ service.requestSaveLocked(sessionId);
}
}
@@ -253,10 +244,7 @@
Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
return;
}
- final FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
- final Bundle responseExtras = response == null ? null : response.getExtras();
- final Bundle datasetExtras = dataset == null ? null : dataset.getExtras();
final int flags = intent.getIntExtra(EXTRA_FLAGS, 0);
if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
@@ -264,7 +252,7 @@
synchronized (mLock) {
switch (type) {
case TYPE_SAVE:
- onSaveRequested(userId, responseExtras, datasetExtras);
+ onSaveRequested(userId, sessionId);
break;
case TYPE_FINISH_SESSION:
onSessionDone(userId, sessionId);
@@ -315,17 +303,18 @@
* Shows a notification with the results of an auto-fill request, using notications actions
* to emulate the auto-fill bar buttons displaying the dataset names.
*/
- private void showOptionsNotification(int userId, int sessionId, FillResponse response) {
+ private void showOptionsNotification(int userId, int callbackId, AutoFillId autoFillId,
+ FillResponse response) {
final long token = Binder.clearCallingIdentity();
try {
- showOptionsNotificationAsSystem(userId, sessionId, response);
+ showOptionsNotificationAsSystem(userId, callbackId, autoFillId, response);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void showOptionsNotificationAsSystem(int userId, int sessionId,
- FillResponse response) {
+ AutoFillId autoFillId, FillResponse response) {
// Make sure server callback is removed from cache if user cancels the notification.
final Intent deleteIntent = newNotificationIntent(userId, TYPE_FINISH_SESSION)
.putExtra(EXTRA_SESSION_ID, sessionId);
@@ -352,13 +341,13 @@
}
boolean showSave = false;
if (datasets == null ) {
- subTitle = "No options to auto-fill this activity.";
+ subTitle = "No options to auto-fill " + autoFillId;
} else if (datasets.isEmpty()) {
if (savableIds.length == 0) {
- subTitle = "No options to auto-fill this activity.";
+ subTitle = "No options to auto-fill " + autoFillId;
} else {
- subTitle = "No options to auto-fill this activity, but provider can save ids:\n"
- + Arrays.toString(savableIds);
+ subTitle = "No options to auto-fill " + autoFillId
+ + ", but provider can save ids:\n" + Arrays.toString(savableIds);
showSave = true;
}
} else {
@@ -369,7 +358,7 @@
} else {
autoCancel = false;
final int size = datasets.size();
- subTitle = "There are " + size + " option(s).\n"
+ subTitle = "There are " + size + " option(s) to fill " + autoFillId + ".\n"
+ "Use the notification action(s) to select the proper one."
+ "Actions with (F) require fingerprint unlock, and with (P) require"
+ "provider authentication to unlock";
@@ -394,36 +383,28 @@
NotificationManager.from(mContext).notify(TYPE_OPTIONS, userId, notification.build());
if (showSave) {
- showSaveNotification(userId, response, null);
+ showSaveNotification(userId, sessionId);
}
}
- void showSaveNotification(int userId, FillResponse response, Dataset dataset) {
+ void showSaveNotification(int userId, int sessionId) {
final long token = Binder.clearCallingIdentity();
try {
- showSaveNotificationAsSystem(userId, response, dataset);
+ showSaveNotificationAsSystem(userId, sessionId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
- private void showSaveNotificationAsSystem(int userId, FillResponse response, Dataset dataset) {
- final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE);
- if (response != null) {
- saveIntent.putExtra(EXTRA_FILL_RESPONSE, response);
- }
- if (dataset != null) {
- saveIntent.putExtra(EXTRA_DATASET, dataset);
- }
+ private void showSaveNotificationAsSystem(int userId, int sessionId) {
+ final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE)
+ .putExtra(EXTRA_SESSION_ID, sessionId);
+
final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext,
++sResultCode, saveIntent, PendingIntent.FLAG_ONE_SHOT);
final String title = "AutoFill Save";
- // Response is not set after fillign an authenticated dataset...
- final String subTitle = response == null
- ? "Tap notification to ask provider to save fields."
- : "Tap notification to ask provider to save fields: \n"
- + Arrays.toString(response.getSavableIds());
+ final String subTitle = "Tap notification to ask provider to save fields.";
final Notification notification = newNotificationBuilder()
.setAutoCancel(true)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 42d036c..f116a4a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22598,6 +22598,13 @@
}
@Override
+ public IBinder getTopVisibleActivity(int uid) {
+ synchronized (ActivityManagerService.this) {
+ return mStackSupervisor.getTopVisibleActivity(uid);
+ }
+ }
+
+ @Override
public void notifyDockedStackMinimizedChanged(boolean minimized) {
synchronized (ActivityManagerService.this) {
mStackSupervisor.setDockedStackMinimized(minimized);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 389fb8b..b2f8b92 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -4902,4 +4902,24 @@
}
return topActivityTokens;
}
+
+ public IBinder getTopVisibleActivity(int uid) {
+ // TODO(b/33197203): get rid of DEFAULT_DISPLAY here?. Used in
+ // VoiceInteractionManagerServiceImpl#showSessionLocked.
+ final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
+ if (display == null) {
+ return null;
+ }
+ final ArrayList<ActivityStack> stacks = display.mStacks;
+ for (int i = stacks.size() - 1; i >= 0; i--) {
+ ActivityStack stack = stacks.get(i);
+ if (stack.getStackVisibilityLocked(null) == ActivityStack.STACK_VISIBLE) {
+ ActivityRecord top = stack.topActivity();
+ if (top != null && stack == mFocusedStack && top.app.uid == uid) {
+ return top.appToken;
+ }
+ }
+ }
+ return null;
+ }
}