Merge "Refined session management so Save can be automatically called."
diff --git a/api/current.txt b/api/current.txt
index 50f7338..b59af05 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44467,7 +44467,8 @@
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
method public android.view.autofill.AutoFillType getAutoFillType();
- method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate(android.view.autofill.VirtualViewDelegate.Callback);
+ method public android.view.autofill.AutoFillValue getAutoFillValue();
+ method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate();
method public android.graphics.drawable.Drawable getBackground();
method public android.content.res.ColorStateList getBackgroundTintList();
method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
@@ -46816,11 +46817,11 @@
}
public final class AutoFillManager {
- method public void onValueChanged(android.view.View, android.view.autofill.AutoFillValue);
- method public void updateAutoFillInput(android.view.View, int);
- method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
- field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
- field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+ method public void focusChanged(android.view.View, boolean);
+ method public void reset();
+ method public void valueChanged(android.view.View);
+ method public void virtualFocusChanged(android.view.View, int, android.graphics.Rect, android.view.autofill.AutoFillValue, boolean);
+ method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutoFillValue);
}
public final class AutoFillType implements android.os.Parcelable {
@@ -46882,13 +46883,6 @@
method public abstract void autoFill(int, android.view.autofill.AutoFillValue);
}
- public static abstract class VirtualViewDelegate.Callback {
- ctor public VirtualViewDelegate.Callback();
- method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
- method public void onNodeRemoved(int...);
- method public void onValueChanged(int);
- }
-
}
package android.view.inputmethod {
diff --git a/api/system-current.txt b/api/system-current.txt
index e73ad4d..70a7a2d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -47874,7 +47874,8 @@
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
method public android.view.autofill.AutoFillType getAutoFillType();
- method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate(android.view.autofill.VirtualViewDelegate.Callback);
+ method public android.view.autofill.AutoFillValue getAutoFillValue();
+ method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate();
method public android.graphics.drawable.Drawable getBackground();
method public android.content.res.ColorStateList getBackgroundTintList();
method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
@@ -50226,11 +50227,11 @@
}
public final class AutoFillManager {
- method public void onValueChanged(android.view.View, android.view.autofill.AutoFillValue);
- method public void updateAutoFillInput(android.view.View, int);
- method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
- field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
- field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+ method public void focusChanged(android.view.View, boolean);
+ method public void reset();
+ method public void valueChanged(android.view.View);
+ method public void virtualFocusChanged(android.view.View, int, android.graphics.Rect, android.view.autofill.AutoFillValue, boolean);
+ method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutoFillValue);
}
public final class AutoFillType implements android.os.Parcelable {
@@ -50292,13 +50293,6 @@
method public abstract void autoFill(int, android.view.autofill.AutoFillValue);
}
- public static abstract class VirtualViewDelegate.Callback {
- ctor public VirtualViewDelegate.Callback();
- method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
- method public void onNodeRemoved(int...);
- method public void onValueChanged(int);
- }
-
}
package android.view.inputmethod {
diff --git a/api/test-current.txt b/api/test-current.txt
index 3cb21d2..7597585 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -44773,7 +44773,8 @@
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
method public android.view.autofill.AutoFillType getAutoFillType();
- method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate(android.view.autofill.VirtualViewDelegate.Callback);
+ method public android.view.autofill.AutoFillValue getAutoFillValue();
+ method public android.view.autofill.VirtualViewDelegate getAutoFillVirtualViewDelegate();
method public android.graphics.drawable.Drawable getBackground();
method public android.content.res.ColorStateList getBackgroundTintList();
method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
@@ -47129,11 +47130,11 @@
}
public final class AutoFillManager {
- method public void onValueChanged(android.view.View, android.view.autofill.AutoFillValue);
- method public void updateAutoFillInput(android.view.View, int);
- method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
- field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
- field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+ method public void focusChanged(android.view.View, boolean);
+ method public void reset();
+ method public void valueChanged(android.view.View);
+ method public void virtualFocusChanged(android.view.View, int, android.graphics.Rect, android.view.autofill.AutoFillValue, boolean);
+ method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutoFillValue);
}
public final class AutoFillType implements android.os.Parcelable {
@@ -47195,13 +47196,6 @@
method public abstract void autoFill(int, android.view.autofill.AutoFillValue);
}
- public static abstract class VirtualViewDelegate.Callback {
- ctor public VirtualViewDelegate.Callback();
- method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
- method public void onNodeRemoved(int...);
- method public void onValueChanged(int);
- }
-
}
package android.view.inputmethod {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6f95309..6a1e74e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -49,7 +49,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -74,8 +73,6 @@
import android.os.StrictMode;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.service.autofill.AutoFillService;
-import android.service.autofill.IAutoFillAppCallback;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -116,7 +113,6 @@
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
-import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillManager;
import android.view.autofill.AutoFillSession;
import android.widget.AdapterView;
@@ -127,7 +123,6 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -1700,16 +1695,21 @@
}
/**
- * Lazily gets the {@link IAutoFillAppCallback} for this activitity.
- *
- * <p>This callback is used by the {@link AutoFillService} app to auto-fill the activity fields.
+ * Lazily attachs the activity to the current {@link AutoFillSession} (if any).
*/
- IAutoFillAppCallback getAutoFillCallback() {
+ void attachToAutoFillSession() {
synchronized (this) {
if (mAutoFillSession == null) {
- mAutoFillSession = new AutoFillSession(this);
+ final AutoFillManager afm = getSystemService(AutoFillManager.class);
+ if (afm != null) {
+ mAutoFillSession = afm.getSession();
+ if (mAutoFillSession != null) {
+ mAutoFillSession.attachActivity(this);
+ } else {
+ Log.w(TAG, "attachToAutoFillSession(): not in a session");
+ }
+ }
}
- return mAutoFillSession.getCallback();
}
}
@@ -1798,6 +1798,10 @@
getApplication().dispatchActivityStopped(this);
mTranslucentCallback = null;
mCalled = true;
+ if (mAutoFillSession != null && isFinishing()) {
+ mAutoFillSession.finishSession();
+ mAutoFillSession = null;
+ }
}
/**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index e848080..89510d9 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -154,12 +154,6 @@
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 d5371f8..dffd81f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2931,7 +2931,7 @@
if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutoFill) {
structure = new AssistStructure(r.activity, forAutoFill);
Intent activityIntent = r.activity.getIntent();
- boolean addAutoFillCallback = false;
+ boolean attachToSession = false;
// 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)
boolean notSecure = r.window == null ||
@@ -2939,7 +2939,7 @@
& WindowManager.LayoutParams.FLAG_SECURE) == 0;
if (activityIntent != null && notSecure) {
if (forAutoFill) {
- addAutoFillCallback = true;
+ attachToSession = true;
} else {
Intent intent = new Intent(activityIntent);
intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@@ -2953,18 +2953,13 @@
} else {
// activityIntent is unlikely to be null, but if it is, we should still
// set the auto-fill callback.
- addAutoFillCallback = notSecure;
+ attachToSession = notSecure;
}
}
if (!forAutoFill) {
r.activity.onProvideAssistContent(content);
- } else if (addAutoFillCallback) {
- IAutoFillAppCallback cb = r.activity.getAutoFillCallback();
- if (cb != null) {
- data.putBinder(AutoFillService.KEY_CALLBACK, cb.asBinder());
- } else {
- Slog.w(TAG, "handleRequestAssistContextExtras(): callback was GCed");
- }
+ } else if (attachToSession) {
+ r.activity.attachToAutoFillSession();
}
}
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 99ae96d..c842f78 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -585,7 +585,7 @@
void unregisterTaskStackListener(ITaskStackListener listener);
void moveStackToDisplay(int stackId, int displayId);
boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras,
- int resultCode, in IBinder activityToken);
+ in IBinder activityToken);
void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
int restartUserInBackground(int userId);
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index aa4d26c..ab80aa3 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -67,8 +67,10 @@
*/
public static final String SERVICE_META_DATA = "android.autofill";
- // Internal bundle keys.
- /** @hide */ public static final String KEY_CALLBACK = "callback";
+ // Internal extras
+ /** @hide */
+ public static final String EXTRA_ACTIVITY_TOKEN =
+ "android.service.autofill.EXTRA_ACTIVITY_TOKEN";
// Handler messages.
private static final int MSG_CONNECT = 1;
diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
index d9c161c..c2e72e8 100644
--- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
@@ -38,4 +38,10 @@
* Start an intent sender from the context of the filled app
*/
void startIntentSender(in IntentSender intent, in Intent fillInIntent);
+
+ /**
+ * Called by system_service to enable auto-fill in a session, after it was asynchronously
+ * started by the manager.
+ */
+ void enableSession();
}
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index 088e649..b39c0c8 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -18,6 +18,7 @@
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.IBinder;
import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillValue;
@@ -27,16 +28,13 @@
* {@hide}
*/
oneway interface IAutoFillManagerService {
+ // Methods called by AutoFillManager
+ void startSession(in IBinder activityToken, in IBinder appCallback, in AutoFillId autoFillId,
+ in Rect bounds, in AutoFillValue value);
+ void updateSession(in IBinder activityToken, in AutoFillId id, in Rect bounds,
+ in AutoFillValue value, int flags);
+ void finishSession(in IBinder activityToken);
- // Called by AutoFillManager (app).
- void requestAutoFill(in AutoFillId id, in Rect bounds, int flags);
-
- // Called by AutoFillManager (app).
- void onValueChanged(in AutoFillId id, in AutoFillValue value);
-
- // Called by ShellCommand only.
- void requestAutoFillForUser(int userId);
-
- // Called by ShellCommand only.
+ // Methods called by ShellCommand
void requestSaveForUser(int userId);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a150529..5ea5b0e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6424,9 +6424,7 @@
if (isAutoFillable()) {
AutoFillManager afm = getAutoFillManager();
if (afm != null) {
- afm.updateAutoFillInput(this, gainFocus
- ? AutoFillManager.FLAG_UPDATE_UI_SHOW
- : AutoFillManager.FLAG_UPDATE_UI_HIDE);
+ afm.focusChanged(this, gainFocus);
}
}
@@ -6916,10 +6914,16 @@
}
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.setAutoFillType(getAutoFillType());
+ final AutoFillType autoFillType = getAutoFillType();
+ // Don't need to fill auto-fill info if view does not support it.
+ // For example, only TextViews that are editable support auto-fill
+ if (autoFillType != 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());
+ structure.setAutoFillType(autoFillType);
+ structure.setAutoFillValue(getAutoFillValue());
+ }
}
structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight - mLeft, mBottom - mTop);
@@ -7015,16 +7019,15 @@
* hierachy on {@link #onProvideAutoFillVirtualStructure(ViewStructure, int)}.
*/
@Nullable
- public VirtualViewDelegate getAutoFillVirtualViewDelegate(
- @SuppressWarnings("unused") VirtualViewDelegate.Callback callback) {
+ public VirtualViewDelegate getAutoFillVirtualViewDelegate() {
return null;
}
/**
* Automatically fills the content of this view with the {@code value}.
*
- * <p>By default does nothing, but views should override it (and {@link #getAutoFillType()} to
- * support the AutoFill Framework.
+ * <p>By default does nothing, but views should override it (and {@link #getAutoFillType()
+ * and #getAutoFillValue()} to support the AutoFill Framework.
*
* <p>Typically, it is implemented by:
*
@@ -7058,6 +7061,18 @@
return null;
}
+ /**
+ * Gets the {@link View}'s current auto-fill value.
+ *
+ * <p>By default returns {@code null}, but views should override it,
+ * {@link #autoFill(AutoFillValue)}, and {@link #getAutoFillType()} to support the AutoFill
+ * Framework.
+ */
+ @Nullable
+ public AutoFillValue getAutoFillValue() {
+ return null;
+ }
+
@Nullable
private AutoFillManager getAutoFillManager() {
return mContext.getSystemService(AutoFillManager.class);
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
index 147d72a..6c20f07 100644
--- a/core/java/android/view/autofill/AutoFillManager.java
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -16,11 +16,12 @@
package android.view.autofill;
-import static android.view.autofill.Helper.DEBUG;
+import static android.view.autofill.Helper.VERBOSE;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
+import android.os.IBinder;
import android.os.RemoteException;
import android.service.autofill.IAutoFillManagerService;
import android.util.Log;
@@ -30,94 +31,184 @@
* App entry point to the AutoFill Framework.
*/
// TODO(b/33197203): improve this javadoc
+//TODO(b/33197203): restrict manager calls to activity
public final class AutoFillManager {
private static final String TAG = "AutoFillManager";
- /**
- * Flag used to show the auto-fill UI affordance for a view.
- */
- 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 = 0x2;
+ /** @hide */ public static final int FLAG_START_SESSION = 0x1;
+ /** @hide */ public static final int FLAG_FOCUS_GAINED = 0x2;
+ /** @hide */ public static final int FLAG_FOCUS_LOST = 0x4;
+ /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x8;
private final IAutoFillManagerService mService;
+ private final Context mContext;
+
+ private AutoFillSession mSession;
/**
* @hide
*/
- public AutoFillManager(@SuppressWarnings("unused") Context context,
- IAutoFillManagerService service) {
+ public AutoFillManager(Context context, IAutoFillManagerService service) {
+ mContext = context;
mService = service;
}
/**
- * Updates the auto-fill bar for a given {@link View}.
+ * Called to indicate the focus on an auto-fillable {@link View} changed.
*
- * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
- * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
- *
- * @param view view to be updated.
- * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
- * {@link #FLAG_UPDATE_UI_HIDE}.
+ * @param view view whose focus changed.
+ * @param gainFocus whether focus was gained or lost.
*/
- public void updateAutoFillInput(View view, int flags) {
+ public void focusChanged(View view, boolean gainFocus) {
+ if (mSession == null) {
+ // Starts new session.
+ final Rect bounds = new Rect();
+ view.getBoundsOnScreen(bounds);
+ final AutoFillId id = getAutoFillId(view);
+ final AutoFillValue value = view.getAutoFillValue();
+ startSession(id, bounds, value);
+ return;
+ }
+
+ if (!mSession.isEnabled()) {
+ // Auto-fill is disabled for this session.
+ return;
+ }
+
+ // Update focus on existing session.
final Rect bounds = new Rect();
view.getBoundsOnScreen(bounds);
-
- requestAutoFill(getAutoFillId(view), bounds, flags);
- }
-
- /**
- * Updates the auto-fill bar for a virtual child of a given {@link View}.
- *
- * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
- * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
- *
- * @param parent parent view.
- * @param childId id identifying the virtual child inside the parent view.
- * @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 bounds,
- int flags) {
- requestAutoFill(new AutoFillId(parent.getAccessibilityViewId(), childId), bounds, flags);
- }
-
- /**
- * Notifies the framework that the value of a view changed.
- * @param view view whose value was updated
- * @param value new value.
- */
- public void onValueChanged(View view, AutoFillValue value) {
- // TODO(b/33197203): optimize it by not calling service when the view does not belong to
- // the session.
final AutoFillId id = getAutoFillId(view);
- if (DEBUG) Log.v(TAG, "onValueChanged(): id=" + id + ", value=" + value);
+ final AutoFillValue value = view.getAutoFillValue();
+ updateSession(id, bounds, value, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
+ }
+
+ /**
+ * Called to indicate the focus on an auto-fillable virtual {@link View} changed.
+ *
+ * @param parent parent view whose focus changed.
+ * @param childId id identifying the virtual child inside the parent view.
+ * @param bounds child boundaries, relative to the top window.
+ * @param value current value of the child; can be {@code null} when focus is lost, but must be
+ * set when focus is gained.
+ * @param gainFocus whether focus was gained or lost.
+ */
+ public void virtualFocusChanged(View parent, int childId, Rect bounds,
+ @Nullable AutoFillValue value, boolean gainFocus) {
+ if (mSession == null) {
+ // Starts new session.
+ final AutoFillId id = getAutoFillId(parent, childId);
+ startSession(id, bounds, value);
+ return;
+ }
+
+ if (!mSession.isEnabled()) {
+ // Auto-fill is disabled for this session.
+ return;
+ }
+
+ // Update focus on existing session.
+ final AutoFillId id = getAutoFillId(parent, childId);
+ updateSession(id, bounds, value, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
+ }
+
+ /**
+ * Called to indicate the value of an auto-fillable {@link View} changed.
+ *
+ * @param view view whose focus changed.
+ */
+ public void valueChanged(View view) {
+ if (mSession == null) return;
+
+ final AutoFillId id = getAutoFillId(view);
+ final AutoFillValue value = view.getAutoFillValue();
+ updateSession(id, null, value, FLAG_VALUE_CHANGED);
+ }
+
+
+ /**
+ * Called to indicate the value of an auto-fillable virtual {@link View} changed.
+ *
+ * @param parent parent view whose value changed.
+ * @param childId id identifying the virtual child inside the parent view.
+ * @param value new value of the child.
+ */
+ public void virtualValueChanged(View parent, int childId, AutoFillValue value) {
+ if (mSession == null) return;
+
+ final AutoFillId id = getAutoFillId(parent, childId);
+ updateSession(id, null, value, FLAG_VALUE_CHANGED);
+ }
+
+ /**
+ * Called to indicate the current auto-fill context should be reset.
+ *
+ * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should
+ * call this method after the form is submitted and another page is rendered.
+ */
+ public void reset() {
+ if (mSession == null) return;
+
+ final IBinder activityToken = mSession.mToken.get();
+ if (activityToken == null) {
+ Log.wtf(TAG, "finishSession(): token already GC'ed");
+ return;
+ }
try {
- mService.onValueChanged(id, value);
+ mService.finishSession(activityToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } finally {
+ mSession = null;
}
}
+ /**
+ * Gets the current session, if any.
+ *
+ * @hide
+ */
+ @Nullable
+ public AutoFillSession getSession() {
+ return mSession;
+ }
+
private AutoFillId getAutoFillId(View view) {
return new AutoFillId(view.getAccessibilityViewId());
}
- private void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
- // TODO(b/33197203): optimize it by not calling service when the view does not belong to
- // the session.
- if (DEBUG) {
- Log.v(TAG, "requestAutoFill(): id=" + id + ", bounds=" + bounds + ", flags=" + flags);
+ private AutoFillId getAutoFillId(View parent, int childId) {
+ return new AutoFillId(parent.getAccessibilityViewId(), childId);
+ }
+
+ private void startSession(AutoFillId id, Rect bounds, AutoFillValue value) {
+ if (VERBOSE) {
+ Log.v(TAG, "startSession(): id=" + id + ", bounds=" + bounds + ", value=" + value);
+ }
+
+ final IBinder activityToken = mContext.getActivityToken();
+ mSession = new AutoFillSession(this, activityToken);
+ final IBinder appCallback = mSession.getCallback().asBinder();
+ try {
+ mService.startSession(activityToken, appCallback, id, bounds, value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void updateSession(AutoFillId id, Rect bounds, AutoFillValue value, int flags) {
+ if (VERBOSE) {
+ Log.v(TAG, "updateSession(): id=" + id + ", bounds=" + bounds + ", value=" + value
+ + ", flags=" + flags);
+ }
+
+ final IBinder activityToken = mSession.mToken.get();
+ if (activityToken == null) {
+ return;
}
try {
- mService.requestAutoFill(id, bounds, flags);
+ mService.updateSession(activityToken, id, bounds, value, 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
index efc1df6..e10ba37 100644
--- a/core/java/android/view/autofill/AutoFillSession.java
+++ b/core/java/android/view/autofill/AutoFillSession.java
@@ -21,12 +21,11 @@
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
+import android.os.IBinder;
import android.service.autofill.IAutoFillAppCallback;
import android.util.Log;
import android.view.View;
-import com.android.internal.annotations.GuardedBy;
-
import java.lang.ref.WeakReference;
/**
@@ -39,6 +38,14 @@
private static final String TAG = "AutoFillSession";
private final IAutoFillAppCallback mCallback = new IAutoFillAppCallback.Stub() {
+
+ @Override
+ public void enableSession() {
+ if (DEBUG) Log.d(TAG, "enableSession()");
+
+ mEnabled = true;
+ }
+
@Override
public void autoFill(Dataset dataset) {
final Activity activity = mActivity.get();
@@ -64,10 +71,8 @@
// TODO(b/33197203): handle protected value (like credit card)
if (id.isVirtual()) {
// Delegate virtual fields.
- setAutoFillDelegateCallback();
final VirtualViewDelegate delegate = view
- .getAutoFillVirtualViewDelegate(
- mAutoFillDelegateCallback);
+ .getAutoFillVirtualViewDelegate();
if (delegate == null) {
Log.w(TAG, "autoFill(): cannot fill virtual " + id
+ "; no VirtualViewDelegate for view "
@@ -102,29 +107,60 @@
}
};
- private final WeakReference<Activity> mActivity;
+ private final AutoFillManager mAfm;
+ private WeakReference<Activity> mActivity;
- @GuardedBy("this")
- private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+ // Reference to the token, which is used by the server.
+ final WeakReference<IBinder> mToken;
- public AutoFillSession(Activity activity) {
+ private boolean mEnabled;
+
+ public AutoFillSession(AutoFillManager afm, IBinder token) {
+ mToken = new WeakReference<>(token);
+ mAfm = afm;
+ }
+
+ /**
+ * Called by the {@link Activity} when it was asked to provider auto-fill data.
+ */
+ public void attachActivity(Activity activity) {
+ if (mActivity != null) {
+ Log.w(TAG, "attachActivity(): already attached");
+ return;
+ }
mActivity = new WeakReference<>(activity);
}
+ /**
+ * Checks whether auto-fill is enabled for this session, as decided by the
+ * {@code AutoFillManagerService}.
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Notifies the manager that a session finished.
+ */
+ // TODO(b/33197203): hook it to other lifecycle events like fragments transition
+ public void finishSession() {
+ if (mAfm != null) {
+ try {
+ mAfm.reset();
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Failed to finish session for " + mToken.get() + ": " + e);
+ }
+ }
+ }
+
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
- };
- }
- }
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ return "AutoFillSession[activityoken=" + mToken.get() + "]";
}
}
diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java
index 57b23ef..b31f4af 100644
--- a/core/java/android/view/autofill/AutoFillValue.java
+++ b/core/java/android/view/autofill/AutoFillValue.java
@@ -18,6 +18,7 @@
import static android.view.autofill.Helper.DEBUG;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
@@ -32,12 +33,12 @@
*/
public final class AutoFillValue implements Parcelable {
- private final CharSequence mText;
+ private final String mText;
private final int mListIndex;
private final boolean mToggle;
private AutoFillValue(CharSequence text, int listIndex, boolean toggle) {
- mText = text;
+ mText = (text == null) ? null : text.toString();
mListIndex = listIndex;
mToggle = toggle;
}
@@ -74,6 +75,32 @@
/////////////////////////////////////
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mListIndex;
+ result = prime * result + ((mText == null) ? 0 : mText.hashCode());
+ result = prime * result + (mToggle ? 1231 : 1237);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ final AutoFillValue other = (AutoFillValue) obj;
+ if (mListIndex != other.mListIndex) return false;
+ if (mText == null) {
+ if (other.mText != null) return false;
+ } else {
+ if (!mText.equals(other.mText)) return false;
+ }
+ if (mToggle != other.mToggle) return false;
+ return true;
+ }
+
+ @Override
public String toString() {
if (!DEBUG) return super.toString();
@@ -92,13 +119,13 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeCharSequence(mText);
+ parcel.writeString(mText);
parcel.writeInt(mListIndex);
parcel.writeInt(mToggle ? 1 : 0);
}
private AutoFillValue(Parcel parcel) {
- mText = parcel.readCharSequence();
+ mText = parcel.readString();
mListIndex = parcel.readInt();
mToggle = parcel.readInt() == 1;
}
@@ -127,8 +154,9 @@
* <p>See {@link AutoFillType#isText()} for more info.
*/
// TODO(b/33197203): use cache
- public static AutoFillValue forText(CharSequence value) {
- return new AutoFillValue(value, 0, false);
+ @Nullable
+ public static AutoFillValue forText(@Nullable CharSequence value) {
+ return value == null ? null : new AutoFillValue(value, 0, false);
}
/**
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
index 14cf9e8..a9844d7 100644
--- a/core/java/android/view/autofill/Helper.java
+++ b/core/java/android/view/autofill/Helper.java
@@ -26,6 +26,7 @@
public final class Helper {
static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
+ static final boolean VERBOSE = false;
static final String REDACTED = "[REDACTED]";
static StringBuilder append(StringBuilder builder, Bundle bundle) {
diff --git a/core/java/android/view/autofill/VirtualViewDelegate.java b/core/java/android/view/autofill/VirtualViewDelegate.java
index e465c67..3dda7f7 100644
--- a/core/java/android/view/autofill/VirtualViewDelegate.java
+++ b/core/java/android/view/autofill/VirtualViewDelegate.java
@@ -15,9 +15,6 @@
*/
package android.view.autofill;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.util.Log;
import android.view.View;
import android.view.ViewStructure;
@@ -33,7 +30,7 @@
* class.
*
* <p>Objects of this class are typically created by overriding
- * {@link View#getAutoFillVirtualViewDelegate(Callback)} and saving the passed callback, which must
+ * {@link View#getAutoFillVirtualViewDelegate()} and saving the passed callback, which must
* be notified upon changes on the hierarchy.
*
* <p>The main use case of these API is to enable custom views that draws its content - such as
@@ -43,82 +40,22 @@
* <li>Client populates the virtual hierarchy on
* {@link View#onProvideAutoFillVirtualStructure(android.view.ViewStructure, int)}
* <li>Android System generates the proper {@link AutoFillId} - encapsulating the view and the
- * virtual node ids - and pass it to the {@link android.service.autofill.AutoFillService}.
+ * virtual child ids - and pass it to the {@link android.service.autofill.AutoFillService}.
* <li>The service uses the {@link AutoFillId} to populate the auto-fill {@link Dataset}s and pass
* it back to the Android System.
* <li>Android System uses the {@link AutoFillId} to find the proper custom view and calls
* {@link #autoFill(int, AutoFillValue)} on that view passing the virtual id.
- * <li>This provider than finds the node in the hierarchy and auto-fills it.
+ * <li>This provider than finds the child in the hierarchy and auto-fills it.
* </ol>
*
*/
public abstract class VirtualViewDelegate {
- // TODO(b/33197203): set to false once stable
- private static final boolean DEBUG = true;
-
- private static final String TAG = "VirtualViewDelegate";
-
/**
* Auto-fills a virtual view with the {@code value}.
*
- * @param virtualId id identifying the virtual node inside the custom view.
+ * @param virtualId id identifying the virtual child inside the custom view.
* @param value value to be auto-filled.
*/
public abstract void autoFill(int virtualId, AutoFillValue value);
-
- /**
- * Callback used to notify the AutoFill Framework of changes made on the view hierarchy while
- * an {@link android.app.Activity} is being auto filled.
- */
- public abstract static class Callback {
-
- /**
- * Sent when the auto-fill bar for a child must be updated.
- *
- * See {@link AutoFillManager#updateAutoFillInput(View, int, android.graphics.Rect, int)}
- * for more details.
- */
- // TODO(b/33197203): do we really need it, or should the parent view just call
- // AutoFillManager.updateAutoFillInput() directly?
- public void onAutoFillInputUpdated(int virtualId, @Nullable Rect boundaries, int flags) {
- if (DEBUG) {
- Log.v(TAG, "onAutoFillInputUpdated(): virtualId=" + virtualId + ", boundaries="
- + boundaries + ", flags=" + flags);
- }
- }
-
- /**
- * Sent when the value of a node was changed.
- *
- * <p>This method should only be called when the change was not caused by the AutoFill
- * Framework itselft (i.e, through {@link VirtualViewDelegate#autoFill(int, AutoFillValue)},
- * but by external causes (for example, when the user changed the value through the view's
- * UI).
- *
- * @param virtualId id of the node whose value changed.
- */
- public void onValueChanged(int virtualId) {
- if (DEBUG) Log.d(TAG, "onValueChanged() for" + virtualId);
- }
-
- /**
- * Sent when nodes were removed (or had their ids changed) after the hierarchy has been
- * committed to
- * {@link View#onProvideAutoFillVirtualStructure(android.view.ViewStructure, int)}.
- *
- * <p>For example, when the view is rendering an {@code HTML} page, it should call this
- * method when:
- * <ul>
- * <li>User navigated to another page and some (or all) nodes are gone.
- * <li>The page's {@code DOM} was changed by {@code JavaScript} and some nodes moved (and
- * are now identified by different ids).
- * </ul>
- *
- * @param virtualIds id of the nodes that were removed.
- */
- public void onNodeRemoved(int... virtualIds) {
- if (DEBUG) Log.d(TAG, "onNodeRemoved(): " + virtualIds);
- }
- }
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 500f381..6f687fe 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -32,7 +32,6 @@
import android.view.SoundEffectConstants;
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
-import android.view.ViewStructure;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.autofill.AutoFillType;
@@ -562,17 +561,14 @@
stream.addProperty("checked", isChecked());
}
- // TODO(b/33197203): add unit/CTS tests for auto-fill methods
+ // TODO(b/33197203): add unit/CTS tests for auto-fill methods (and make sure they handle enable)
- @Override
- public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
- super.onProvideAutoFillStructure(structure, flags);
- structure.setAutoFillValue(AutoFillValue.forToggle(isChecked()));
- // TODO(b/33197203): add unit/CTS tests for auto-fill methods
- }
+ // TODO(b/33197203): override onProvideAutoFillStructure and add a change listener
@Override
public void autoFill(AutoFillValue value) {
+ if (!isEnabled()) return;
+
setChecked(value.getToggleValue());
}
@@ -580,4 +576,9 @@
public AutoFillType getAutoFillType() {
return AutoFillType.forToggle();
}
+
+ @Override
+ public AutoFillValue getAutoFillValue() {
+ return isEnabled() ? null : AutoFillValue.forToggle(isChecked());
+ }
}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 72dc1cc..8ba4694 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -24,7 +24,6 @@
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewStructure;
import android.view.autofill.AutoFillType;
import android.view.autofill.AutoFillValue;
@@ -404,16 +403,14 @@
}
}
- // TODO(b/33197203): add unit/CTS tests for auto-fill methods
+ // TODO(b/33197203): add unit/CTS tests for auto-fill methods (and make sure they handle enable)
- @Override
- public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
- super.onProvideAutoFillStructure(structure, flags);
- structure.setAutoFillValue(AutoFillValue.forList(getCheckedRadioButtonId()));
- }
+ // TODO(b/33197203): override onProvideAutoFillStructure and add a change listener
@Override
public void autoFill(AutoFillValue value) {
+ if (!isEnabled()) return;
+
final int index = value.getListValue();
final View child = getChildAt(index);
if (child == null) {
@@ -427,4 +424,9 @@
public AutoFillType getAutoFillType() {
return AutoFillType.forList();
}
+
+ @Override
+ public AutoFillValue getAutoFillValue() {
+ return isEnabled() ? AutoFillValue.forList(getCheckedRadioButtonId()) : null;
+ }
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a5ff291..7ef0a06 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9736,9 +9736,6 @@
// Simple case: this is a single line.
final CharSequence text = getText();
structure.setText(text, getSelectionStart(), getSelectionEnd());
- if (forAutoFill && isTextEditable()) {
- structure.setAutoFillValue(AutoFillValue.forText(text));
- }
} else {
// Complex case: multi-line, could be scrolled or within a scroll container
// so some lines are not visible.
@@ -9795,9 +9792,6 @@
text = text.subSequence(expandedTopChar, expandedBottomChar);
}
structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
- if (forAutoFill && isTextEditable()) {
- structure.setAutoFillValue(AutoFillValue.forText(text));
- }
final int[] lineOffsets = new int[bottomLine - topLine + 1];
final int[] lineBaselines = new int[bottomLine - topLine + 1];
final int baselineOffset = getBaselineOffset();
@@ -9845,17 +9839,7 @@
final CharSequence text = value.getTextValue();
if (text != null && isTextEditable()) {
- if (mAutoFillChangeWatcher == null || mAutoFillChangeWatcher.mOnAutoFill) {
- setText(text, mBufferType, true, 0);
- } else {
- // Must disable listener first so it's not triggered.
- mAutoFillChangeWatcher.mOnAutoFill = true;
- try {
- setText(text, mBufferType, true, 0);
- } finally {
- mAutoFillChangeWatcher.mOnAutoFill = false;
- }
- }
+ setText(text, mBufferType, true, 0);
}
}
@@ -9865,6 +9849,12 @@
return isTextEditable() ? AutoFillType.forText(getInputType()) : null;
}
+ @Override
+ @Nullable
+ public AutoFillValue getAutoFillValue() {
+ return isTextEditable() ? AutoFillValue.forText(getText()) : null;
+ }
+
/** @hide */
@Override
public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
@@ -10724,14 +10714,6 @@
* @hide
*/
protected void viewClicked(InputMethodManager imm) {
- final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
- if (afm != null) {
- 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);
- }
-
if (imm != null) {
imm.viewClicked(this);
}
@@ -11213,7 +11195,6 @@
// TODO(b/33197203): implements SpanWatcher too?
private final class AutoFillChangeWatcher implements TextWatcher {
- private boolean mOnAutoFill;
private final AutoFillManager mAfm = mContext.getSystemService(AutoFillManager.class);
@Override
@@ -11226,18 +11207,11 @@
@Override
public void afterTextChanged(Editable s) {
- if (mOnAutoFill) {
- if (DEBUG_AUTOFILL) {
- Log.v(LOG_TAG, "AutoFillChangeWatcher.afterTextChanged() skipped during "
- + "autoFill(): s=" + s);
- }
- return;
- }
if (mAfm != null) {
if (DEBUG_AUTOFILL) {
Log.v(LOG_TAG, "AutoFillChangeWatcher.afterTextChanged(): s=" + s);
}
- mAfm.onValueChanged(TextView.this, AutoFillValue.forText(s));
+ mAfm.valueChanged(TextView.this);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 178a697..9347350 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -18,8 +18,11 @@
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.VERBOSE;
import android.Manifest;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.content.ComponentName;
@@ -34,7 +37,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -70,12 +72,11 @@
public final class AutoFillManagerService extends SystemService {
private static final String TAG = "AutoFillManagerService";
- static final boolean DEBUG = false;
- protected static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 1;
- protected static final int MSG_REQUEST_AUTO_FILL = 2;
- private static final int MSG_REQUEST_SAVE_FOR_USER = 3;
- private static final int MSG_ON_VALUE_CHANGED = 4;
+ private static final int MSG_START_SESSION = 1;
+ private static final int MSG_UPDATE_SESSION = 2;
+ private static final int MSG_FINISH_SESSION = 3;
+ private static final int MSG_REQUEST_SAVE_FOR_USER = 4;
private final Context mContext;
private final AutoFillUI mUi;
@@ -84,30 +85,31 @@
private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
switch (msg.what) {
- case MSG_REQUEST_AUTO_FILL_FOR_USER: {
- handleAutoFillForUser(msg.arg1);
+ case MSG_START_SESSION: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final int userId = msg.arg1;
+ final IBinder activityToken = (IBinder) args.arg1;
+ final IBinder appCallback = (IBinder) args.arg2;
+ final AutoFillId autoFillId = (AutoFillId) args.arg3;
+ final Rect bounds = (Rect) args.arg4;
+ final AutoFillValue value = (AutoFillValue) args.arg5;
+ handleStartSession(userId, activityToken, appCallback, autoFillId, bounds, value);
+ return;
+ } case MSG_FINISH_SESSION: {
+ handleFinishSession(msg.arg1, (IBinder) msg.obj);
return;
} case MSG_REQUEST_SAVE_FOR_USER: {
handleSaveForUser(msg.arg1);
return;
- } case MSG_REQUEST_AUTO_FILL: {
+ } case MSG_UPDATE_SESSION: {
final SomeArgs args = (SomeArgs) msg.obj;
- 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;
- args.recycle();
- handleAutoFill(activityToken, userId, autoFillId, bounds, flags);
- return;
- } case MSG_ON_VALUE_CHANGED: {
- final SomeArgs args = (SomeArgs) msg.obj;
- final int userId = msg.arg1;
- final IBinder activityToken = (IBinder) args.arg1;
- final AutoFillId autoFillId = (AutoFillId) args.arg2;
- final AutoFillValue newValue = (AutoFillValue) args.arg3;
- args.recycle();
- handleValueChanged(activityToken, userId, autoFillId, newValue);
+ final AutoFillValue value = (AutoFillValue) args.arg4;
+ final int userId = args.argi5;
+ final int flags = args.argi6;
+ handleUpdateSession(userId, activityToken, autoFillId, bounds, value, flags);
return;
} default: {
Slog.w(TAG, "Invalid message: " + msg);
@@ -188,8 +190,11 @@
/**
* Gets the service instance for an user.
+ *
+ * @return service instance or {@code null} if user does not have a service set.
*/
- AutoFillManagerServiceImpl getOrCreateServiceForUserLocked(int userId) {
+ @Nullable
+ AutoFillManagerServiceImpl getServiceForUserLocked(int userId) {
AutoFillManagerServiceImpl service = mServicesCache.get(userId);
if (service == null) {
service = newServiceForUser(userId);
@@ -209,107 +214,114 @@
}
}
- private void handleAutoFill(IBinder activityToken, int userId, AutoFillId autoFillId,
- Rect bounds, int flags) {
+ private void handleStartSession(int userId, IBinder activityToken, IBinder appCallback,
+ AutoFillId autoFillId, Rect bounds, AutoFillValue value) {
synchronized (mLock) {
- final AutoFillManagerServiceImpl service = getOrCreateServiceForUserLocked(userId);
- if (service != null) {
- service.requestAutoFillLocked(activityToken, autoFillId, bounds, flags);
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service == null) {
+ return;
}
+ service.startSessionLocked(activityToken, appCallback, autoFillId, bounds, value);
}
}
- private void handleValueChanged(IBinder activityToken, int userId, AutoFillId autoFillId,
- AutoFillValue newValue) {
+ private void handleFinishSession(int userId, IBinder activityToken) {
synchronized (mLock) {
- final AutoFillManagerServiceImpl service = getOrCreateServiceForUserLocked(userId);
- if (service != null) {
- service.onValueChangeLocked(activityToken, autoFillId, newValue);
+ final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
+ if (service == null) {
+ return;
}
+ service.finishSessionLocked(activityToken);
+ }
+ }
+
+ private void handleUpdateSession(int userId, IBinder activityToken, AutoFillId autoFillId,
+ Rect bounds, AutoFillValue value, int flags) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
+ if (service == null) {
+ return;
+ }
+
+ service.updateSessionLocked(activityToken, autoFillId, bounds, value, flags);
}
}
private IBinder getTopActivityForUser() {
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 null;
}
return topActivities.get(0);
}
- private void handleAutoFillForUser(int userId) {
- final IBinder activityToken = getTopActivityForUser();
- if (activityToken != null) {
- synchronized (mLock) {
- final AutoFillManagerServiceImpl service =
- getOrCreateServiceForUserLocked(userId);
- service.requestAutoFillLocked(activityToken, null, null, 0);
- }
- }
-
- }
-
private void handleSaveForUser(int userId) {
final IBinder activityToken = getTopActivityForUser();
if (activityToken != null) {
synchronized (mLock) {
- final AutoFillManagerServiceImpl service =
- getOrCreateServiceForUserLocked(userId);
+ final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
+ if (service == null) {
+ Log.w(TAG, "handleSaveForUser(): no cached service for userId " + userId);
+ return;
+ }
+
service.requestSaveForUserLocked(activityToken);
}
}
}
- private IBinder getTopActivity() {
- final int uid = Binder.getCallingUid();
- final IBinder activityToken = LocalServices.getService(ActivityManagerInternal.class)
- .getTopVisibleActivity(uid);
- if (activityToken == null) {
- // Make sure its called by the top activity.
- if (uid == Process.SYSTEM_UID) {
- // TODO(b/33197203, b/34819567, b/34171325): figure out proper way to handle it
- if (DEBUG) Log.w(TAG, "requestAutoFill(): ignoring call from system");
-
- return null;
- }
- throw new SecurityException("uid " + uid + " does not own the top activity");
- }
-
- return activityToken;
- }
-
final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
@Override
- public void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
- final IBinder activityToken = getTopActivity();
- if (activityToken != null) {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL,
- UserHandle.getCallingUserId(), flags, activityToken, id, bounds));
+ public void startSession(IBinder activityToken, IBinder appCallback, AutoFillId autoFillId,
+ Rect bounds, AutoFillValue value) throws RemoteException {
+ // TODO(b/33197203): make sure it's called by resumed / focused activity
+
+ final int userId = UserHandle.getCallingUserId();
+ if (VERBOSE) {
+ Slog.v(TAG, "startSession: autoFillId=" + autoFillId + ", bounds=" + bounds
+ + ", value=" + value);
}
+
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = activityToken;
+ args.arg2 = appCallback;
+ args.arg3 = autoFillId;
+ args.arg4 = bounds;
+ args.arg5 = value;
+
+ mHandlerCaller.sendMessage(mHandlerCaller.getHandler().obtainMessage(MSG_START_SESSION,
+ userId, 0, args));
}
@Override
- public void requestAutoFillForUser(int userId) {
- mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageI(
- MSG_REQUEST_AUTO_FILL_FOR_USER, userId));
+ public void updateSession(IBinder activityToken, AutoFillId id, Rect bounds,
+ AutoFillValue value, int flags) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "updateSession: flags=" + flags + ", autoFillId=" + id
+ + ", bounds=" + bounds + ", value=" + value);
+ }
+
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOOII(MSG_UPDATE_SESSION,
+ activityToken, id, bounds, value, UserHandle.getCallingUserId(), flags));
+ }
+
+ @Override
+ public void finishSession(IBinder activityToken) throws RemoteException {
+ if (VERBOSE) Slog.v(TAG, "finishSession(): " + activityToken);
+
+ mHandlerCaller.sendMessage(mHandlerCaller.getHandler().obtainMessage(MSG_FINISH_SESSION,
+ UserHandle.getCallingUserId(), 0, activityToken));
}
@Override
public void requestSaveForUser(int userId) {
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageI(MSG_REQUEST_SAVE_FOR_USER, userId));
- }
-
- @Override
- public void onValueChanged(AutoFillId id, AutoFillValue value) {
- final IBinder activityToken = getTopActivity();
- if (activityToken != null) {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO(MSG_ON_VALUE_CHANGED,
- UserHandle.getCallingUserId(), activityToken, id, value));
- }
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageI(MSG_REQUEST_SAVE_FOR_USER,
+ userId));
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index e32e21d..88f1bda 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -16,11 +16,17 @@
package com.android.server.autofill;
-import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_SHOW;
-import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_HIDE;
+import static android.service.autofill.AutoFillService.EXTRA_ACTIVITY_TOKEN;
+import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
+import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
+import static android.view.autofill.AutoFillManager.FLAG_FOCUS_GAINED;
+import static android.view.autofill.AutoFillManager.FLAG_FOCUS_LOST;
+import static android.view.autofill.AutoFillManager.FLAG_START_SESSION;
+import static android.view.autofill.AutoFillManager.FLAG_VALUE_CHANGED;
import static com.android.server.autofill.Helper.DEBUG;
import static com.android.server.autofill.Helper.VERBOSE;
+import static com.android.server.autofill.Helper.findValue;
import android.annotation.Nullable;
import android.app.Activity;
@@ -40,6 +46,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.ICancellationSignal;
+import android.os.Looper;
import android.os.RemoteException;
import android.service.autofill.AutoFillService;
import android.service.autofill.AutoFillServiceInfo;
@@ -47,25 +54,22 @@
import android.service.autofill.IAutoFillAppCallback;
import android.service.autofill.IAutoFillService;
import android.service.autofill.IFillCallback;
-import android.service.voice.VoiceInteractionSession;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.PrintWriterPrinter;
import android.util.Slog;
-import android.util.SparseArray;
import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillValue;
import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.HandlerCaller;
import com.android.internal.os.IResultReceiver;
import com.android.server.FgThread;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -78,8 +82,7 @@
private static final String TAG = "AutoFillManagerServiceImpl";
- /** Used do assign ids to new ServerCallback instances. */
- private static int sSessionIdCounter = 0;
+ private static final int MSG_SERVICE_SAVE = 1;
private final int mUserId;
private final ComponentName mComponent;
@@ -103,8 +106,20 @@
}
};
+ private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
+ switch (msg.what) {
+ case MSG_SERVICE_SAVE:
+ handleSessionSave((IBinder) msg.obj);
+ break;
+ default:
+ Slog.d(TAG, "invalid msg: " + msg);
+ }
+ };
+
+ private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
+ mHandlerCallback, true);
/**
- * Cache of pending {@link Session}s, keyed by {@link Session#mId}.
+ * Cache of pending {@link Session}s, keyed by {@code activityToken}.
*
* <p>They're kept until the {@link AutoFillService} finished handling a request, an error
* occurs, or the session times out.
@@ -112,58 +127,46 @@
// TODO(b/33197203): need to make sure service is bound while callback is pending and/or
// use WeakReference
@GuardedBy("mLock")
- private final SparseArray<Session> mSessions = new SparseArray<>();
+ private final ArrayMap<IBinder, Session> mSessions = new ArrayMap<>();
/**
- * Receiver of assist data from the app's {@link Activity}, uses the {@code resultData} as
- * the {@link Session#mId}.
+ * Receiver of assist data from the app's {@link Activity}.
*/
private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
if (DEBUG) Slog.d(TAG, "resultCode on mAssistReceiver: " + resultCode);
- final IBinder appBinder = resultData.getBinder(AutoFillService.KEY_CALLBACK);
- if (appBinder == null) {
- Slog.w(TAG, "no app callback on mAssistReceiver's resultData");
- return;
- }
+ final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
- final AssistStructure structure = resultData
- .getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
if (structure == null) {
Slog.w(TAG, "no assist structure for id " + resultCode);
return;
}
+ final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS);
+ if (receiverExtras == null) {
+ // Should not happen
+ Slog.wtf(TAG, "No " + KEY_RECEIVER_EXTRAS + " on receiver");
+ return;
+ }
+
+ final IBinder activityToken = receiverExtras.getBinder(EXTRA_ACTIVITY_TOKEN);
final Session session;
synchronized (mLock) {
- session = mSessions.get(resultCode);
+ session = mSessions.get(activityToken);
if (session == null) {
- Slog.w(TAG, "no server callback for id " + resultCode);
+ Slog.w(TAG, "no server session for activityToken " + activityToken);
return;
}
+ // TODO(b/33197203): since service is fetching the data (to use for save later),
+ // we should optimize what's sent (for example, remove layout containers,
+ // color / font info, etc...)
+ session.mStructure = structure;
}
- // TODO(b/33197203): since service is fetching the data (to use for save later),
- // we should optimize what's sent (for example, remove layout containers,
- // color / font info, etc...)
-
- // TODO(b/33197203, b/33269702): Must fetch the data so it's available later on
- // handleSave(), even if if the activity is gone by then, but structure.ensureData()
- // gives a ONE_WAY warning because system_service could block on app calls.
- // We need to change AssistStructure so it provides a "one-way" writeToParcel()
- // method that sends all the data
- structure.ensureData();
-
- structure.sanitizeForParceling(true);
-
- if (VERBOSE) {
- Slog.v(TAG, "Dumping " + structure + " before calling service.autoFill()");
- structure.dump();
- }
-
- session.onApplicationDataAvailable(structure, appBinder);
+ // TODO(b/33197203): Need to pipe the bundle
+ session.mRemoteFillService.onFillRequest(structure, null);
}
};
@@ -190,46 +193,50 @@
* Used by {@link AutoFillManagerServiceShellCommand} to request save for the current top app.
*/
void requestSaveForUserLocked(IBinder activityToken) {
- final Session session = getOrCreateSessionByTokenLocked(activityToken);
- session.onSaveLocked();
+ final Session session = mSessions.get(activityToken);
+ if (session == null) {
+ Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken);
+ return;
+ }
+
+ session.callSaveLocked();
}
- /**
- * Asks service to auto-fill an activity.
- *
- * @param activityToken activity token.
- * @param autoFillId id of the view that requested auto-fill.
- * @param bounds boundaries of the view that requested auto-fill.
- * @param flags optional flags.
- */
- void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId,
- @Nullable Rect bounds, int flags) {
- final String historyItem = "s=" + mComponentName + " u=" + mUserId + " f=" + flags
- + " a=" + activityToken + " i=" + autoFillId + " b=" + bounds;
+ void startSessionLocked(IBinder activityToken, IBinder appCallbackToken, AutoFillId autoFillId,
+ Rect bounds, AutoFillValue value) {
+ final String historyItem = "s=" + mComponentName + " u=" + mUserId + " a=" + activityToken
+ + " i=" + autoFillId + " b=" + bounds + " v=" + value;
mRequestsHistory.log(historyItem);
// TODO(b/33197203): Handle partitioning
- Session session = getOrCreateSessionByTokenLocked(activityToken);
- session.updateAutoFillInput(flags, autoFillId, null, bounds);
- }
-
- private Session getOrCreateSessionByTokenLocked(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;
- }
+ final Session session = mSessions.get(activityToken);
+ if (session != null) {
+ // Already started...
+ return;
}
- return createSessionByTokenLocked(activityToken);
+
+ final Session newSession = createSessionByTokenLocked(activityToken, appCallbackToken);
+ newSession.updateLocked(autoFillId, bounds, value, FLAG_START_SESSION);
+ newSession.enableSessionLocked();
}
- private Session createSessionByTokenLocked(IBinder activityToken) {
- final int sessionId = ++sSessionIdCounter;
- if (DEBUG) Slog.d(TAG, "creating session for " + activityToken + ": " + sessionId);
+ void finishSessionLocked(IBinder activityToken) {
+ if (DEBUG) Slog.d(TAG, "finishSessionLocked(): " + activityToken);
+ final Session session = mSessions.get(activityToken);
- final Session newSession = new Session(mContext, activityToken, sessionId);
- mSessions.put(sessionId, newSession);
+ if (session == null) {
+ Slog.w(TAG, "finishSessionLocked(): no session for " + activityToken);
+ return;
+ }
+
+ mUi.hideFillUi();
+ session.showSaveLocked();
+ }
+
+ private Session createSessionByTokenLocked(IBinder activityToken, IBinder appCallbackToken) {
+
+ final Session newSession = new Session(mContext, activityToken, appCallbackToken);
+ mSessions.put(activityToken, newSession);
/*
* TODO(b/33197203): apply security checks below:
@@ -240,7 +247,9 @@
*/
try {
// TODO(b/33197203): add MetricsLogger call
- if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken)) {
+ final Bundle receiverExtras = new Bundle();
+ receiverExtras.putBinder(EXTRA_ACTIVITY_TOKEN, activityToken);
+ if (!mAm.requestAutoFillData(mAssistReceiver, receiverExtras, activityToken)) {
// 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);
@@ -251,39 +260,53 @@
return newSession;
}
- /**
- * Callback indicating the value of a field change in the app.
- */
- void onValueChangeLocked(IBinder activityToken, AutoFillId autoFillId, AutoFillValue newValue) {
+ void updateSessionLocked(IBinder activityToken, AutoFillId autoFillId, Rect bounds,
+ AutoFillValue value, int flags) {
+
// TODO(b/33197203): add MetricsLogger call
- final Session session = getOrCreateSessionByTokenLocked(activityToken);
- session.updateValueLocked(autoFillId, newValue);
+ final Session session = mSessions.get(activityToken);
+ if (session == null) {
+ Slog.w(TAG, "updateSessionLocked(): session gone for " + activityToken);
+ return;
+ }
+
+ session.updateLocked(autoFillId, bounds, value, flags);
}
- void removeSessionLocked(int id) {
- if (DEBUG) Slog.d(TAG, "Removing session " + id);
- mSessions.get(id);
+ private void handleSessionSave(IBinder activityToken) {
+
+ synchronized (mLock) {
+ final Session session = mSessions.get(activityToken);
+ if (session == null) {
+ Slog.w(TAG, "handleSessionSave(): already gone: " + activityToken);
+
+ return;
+ }
+ session.callSaveLocked();
+ }
}
void destroyLocked() {
+ if (VERBOSE) Slog.v(TAG, "destroyLocked()");
+
mContext.unregisterReceiver(mBroadcastReceiver);
- final int sessionCount = mSessions.size();
- for (int i = sessionCount - 1; i >= 0; i--) {
- Session session = mSessions.valueAt(i);
- session.destroy();
- mSessions.removeAt(i);
+ for (Session session : mSessions.values()) {
+ session.destroyLocked();
}
+ mSessions.clear();
}
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
- if (DEBUG) {
+
+ pw.print(prefix); pw.println("Component:"); pw.println(mComponentName);
+
+ if (VERBOSE) {
// ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
pw.print(prefix); pw.println("ServiceInfo:");
mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
}
- pw.print(prefix); pw.print("sSessionIdCounter="); pw.println(sSessionIdCounter);
final int size = mSessions.size();
if (size == 0) {
pw.print(prefix); pw.println("No sessions");
@@ -317,13 +340,17 @@
@Nullable AutoFillValue value);
}
+ final AutoFillId mId;
private final Listener mListener;
// // TODO(b/33197203): does it really need a reference to the session's response?
private FillResponse mResponse;
private AutoFillValue mAutoFillValue;
private Rect mBounds;
- ViewState(Listener listener) {
+ private boolean mValueUpdated;
+
+ ViewState(AutoFillId id, Listener listener) {
+ mId = id;
mListener = listener;
}
@@ -335,6 +362,9 @@
maybeCallOnFillReady();
}
+ // TODO(b/33197203): need to refactor / rename / document this method to make it clear that
+ // it can change the value and update the UI; similarly, should replace code that
+ // directly sets mAutoFilLValue to use encapsulation.
void update(@Nullable AutoFillValue autoFillValue, @Nullable Rect bounds) {
if (autoFillValue != null) {
mAutoFillValue = autoFillValue;
@@ -360,9 +390,17 @@
public String toString() {
if (!DEBUG) return super.toString();
- return "ViewState: [response=" + mResponse + ", value=" + mAutoFillValue
- + ", bounds=" + mBounds + "]";
+ return "ViewState: [id=" + mId + ", value=" + mAutoFillValue + ", bounds=" + mBounds
+ + ", updated = " + mValueUpdated + "]";
}
+
+ void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("id:" ); pw.println(mId);
+ pw.print(prefix); pw.print("value:" ); pw.println(mAutoFillValue);
+ pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
+ pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds);
+ }
+
}
/**
@@ -383,9 +421,7 @@
// - When service is unbound.
final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
AutoFillUI.AutoFillUiCallback {
- private final int mId;
-
- private final WeakReference<IBinder> mActivityToken;
+ private final IBinder mActivityToken;
@GuardedBy("mLock")
private final Map<AutoFillId, ViewState> mViewStates = new ArrayMap<>();
@@ -394,7 +430,7 @@
@Nullable
private ViewState mCurrentViewState;
- private IAutoFillAppCallback mAppCallback;
+ private final IAutoFillAppCallback mAppCallback;
@GuardedBy("mLock")
RemoteFillService mRemoteFillService;
@@ -404,10 +440,11 @@
private FillResponse mCurrentResponse;
/**
- * Map of ids that must be updated so they're send to {@link #onSaveLocked()}.
+ * Used to remember which {@link Dataset} filled the session.
*/
+ // TODO(b/33197203): might need more than one once we support partitions
@GuardedBy("mLock")
- private Map<AutoFillId, AutoFillValue> mUpdatedValues;
+ private Dataset mAutoFilledDataset;
/**
* Assist structure sent by the app; it will be updated (sanitized, change values for save)
@@ -416,18 +453,29 @@
@GuardedBy("mLock")
private AssistStructure mStructure;
- private Session(Context context, IBinder activityToken, int id) {
- mActivityToken = new WeakReference<>(activityToken);
+ private Session(Context context, IBinder activityToken, IBinder appCallback) {
mRemoteFillService = new RemoteFillService(context, mComponent, mUserId, this);
- mId = id;
+ mActivityToken = activityToken;
+
+ mAppCallback = IAutoFillAppCallback.Stub.asInterface(appCallback);
+ try {
+ appCallback.linkToDeath(() -> {
+ if (DEBUG) Slog.d(TAG, "app binder died");
+
+ removeSelf();
+ }, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "linkToDeath() on mAppCallback failed: " + e);
+ }
}
+
// FillServiceCallbacks
@Override
public void onFillRequestSuccess(FillResponse response) {
// TODO(b/33197203): add MetricsLogger call
if (response == null) {
- destroy();
+ removeSelf();
return;
}
synchronized (mLock) {
@@ -440,13 +488,15 @@
public void onFillRequestFailure(CharSequence message) {
// TODO(b/33197203): add MetricsLogger call
getUiForShowing().showError(message);
- destroy();
+ removeSelf();
}
// FillServiceCallbacks
@Override
public void onSaveRequestSuccess() {
- // TODO: Implement
+ // TODO(b/33197203): add MetricsLogger call
+ // Nothing left to do...
+ removeSelf();
}
// FillServiceCallbacks
@@ -454,7 +504,7 @@
public void onSaveRequestFailure(CharSequence message) {
// TODO(b/33197203): add MetricsLogger call
getUiForShowing().showError(message);
- destroy();
+ removeSelf();
}
// FillServiceCallbacks
@@ -466,7 +516,7 @@
// FillServiceCallbacks
@Override
public void onServiceDied(RemoteFillService service) {
- // TODO: Implement
+ // TODO(b/33197203): implement
}
// AutoFillUiCallback
@@ -478,58 +528,93 @@
// AutoFillUiCallback
@Override
public void save() {
- synchronized (mLock) {
- onSaveLocked();
- }
- }
-
- private Session(int id, IBinder activityToken) {
- mId = id;
- mActivityToken = new WeakReference<>(activityToken);
+ mHandlerCaller.getHandler().obtainMessage(MSG_SERVICE_SAVE, mActivityToken)
+ .sendToTarget();
}
/**
- * Callback used to indivate a field has been updated.
+ * Show the save UI, when session can be saved.
*/
- void updateValueLocked(AutoFillId id, AutoFillValue newValue) {
- if (DEBUG) Slog.d(TAG, "updateValueLocked(): id=" + id + ", newValue=" + newValue);
-
- // TODO(b/33197203): ignore if not part of the savable ids.
- if (mUpdatedValues == null) {
- // Lazy initializes it
- mUpdatedValues = new HashMap<>();
+ public void showSaveLocked() {
+ if (mStructure == null) {
+ // Sanity check; should not happen...
+ Slog.wtf(TAG, "showSaveLocked(): no mStructure");
+ return;
}
- mUpdatedValues.put(id, newValue);
+ final ArraySet<AutoFillId> savableIds = mCurrentResponse.getSavableIds();
+ if (VERBOSE) Slog.v(TAG, "showSaveLocked(): savableIds=" + savableIds);
+
+ if (savableIds.isEmpty()) {
+ if (DEBUG) Slog.d(TAG, "showSaveLocked(): service doesn't want to save");
+ return;
+ }
+
+ final int size = savableIds.size();
+ for (int i = 0; i < size; i++) {
+ final AutoFillId id = savableIds.valueAt(i);
+ final ViewState state = mViewStates.get(id);
+ if (state != null && state.mValueUpdated) {
+ final AutoFillValue filledValue = findValue(mAutoFilledDataset, id);
+ if (state.mAutoFillValue == null || state.mAutoFillValue.equals(filledValue)) {
+ continue;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
+ + state.mAutoFillValue);
+ }
+
+ mUi.showSaveUi();
+ return;
+ }
+ }
+ // Nothing changed...
+ if (DEBUG) Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities");
}
/**
* Calls service when user requested save.
*/
- void onSaveLocked() {
- if (DEBUG) Slog.d(TAG, "onSaveLocked(): mUpdateValues=" + mUpdatedValues);
+ private void callSaveLocked() {
+ if (DEBUG) Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
- if (mStructure == null) {
- // Sanity check; should not happen...
- Slog.wtf(TAG, "onSaveLocked(): no mStructure");
- return;
- }
+ // TODO(b/33197203): hookup extras and make sure they're tested by CTS
+ final Bundle extras = null;
+// // TODO(b/33197203): make sure the extras are tested by CTS
+// final Bundle responseExtras = mCurrentResponse == null ? null
+// : mCurrentResponse.getExtras();
+// final Bundle datasetExtras = mAutoFilledDataset == null ? null
+// : mAutoFilledDataset.getExtras();
+// final Bundle extras = (responseExtras == null && datasetExtras == null)
+// ? null : new Bundle();
+// if (responseExtras != null) {
+// if (DEBUG) {
+// Slog.d(TAG, "response extras on save extras: "
+// + bundleToString(responseExtras));
+// }
+// extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
+// }
+// if (datasetExtras != null) {
+// if (DEBUG) {
+// Slog.d(TAG, "dataset extras on save extras: " + bundleToString(datasetExtras));
+// }
+// extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
+// }
- if (mUpdatedValues == null || mUpdatedValues.isEmpty()) {
- // Nothing changed
- if (DEBUG) Slog.d(TAG, "onSave(): when no changes, comes no responsibilities");
- return;
- }
- // TODO(b/33197203): make sure the extras are tested by CTS
- for (Entry<AutoFillId, AutoFillValue> entry : mUpdatedValues.entrySet()) {
+ for (Entry<AutoFillId, ViewState> entry : mViewStates.entrySet()) {
+ final AutoFillValue value = entry.getValue().mAutoFillValue;
+ if (value == null) {
+ if (VERBOSE) Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey());
+ continue;
+ }
final AutoFillId id = entry.getKey();
final ViewNode node = findViewNodeByIdLocked(id);
if (node == null) {
- Slog.w(TAG, "onSaveLocked(): did not find node with id " + id);
+ Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
continue;
}
- final AutoFillValue value = entry.getValue();
- if (DEBUG) Slog.d(TAG, "onSaveLocked(): updating " + id + " to " + value);
+ if (DEBUG) Slog.d(TAG, "callSaveLocked(): updating " + id + " to " + value);
+
node.updateAutoFillValue(value);
}
@@ -540,62 +625,77 @@
mStructure.dump();
}
- mRemoteFillService.onSaveRequest(mStructure, mCurrentResponse.getExtras());
+ mRemoteFillService.onSaveRequest(mStructure, extras);
}
- void onApplicationDataAvailable(AssistStructure structure, IBinder appCallback) {
- setAppCallback(appCallback);
- mStructure = structure;
- // TODO(b/33197203): Need to pipe the bundle
- mRemoteFillService.onFillRequest(structure, null);
- }
+ void updateLocked(AutoFillId id, Rect bounds, AutoFillValue value, int flags) {
+ if (DEBUG) Slog.d(TAG, "updateLocked(): id=" + id + ", flags=" + flags);
- private void setAppCallback(IBinder appBinder) {
- try {
- appBinder.linkToDeath(() -> {
- if (DEBUG) Slog.d(TAG, "app callback died");
- // TODO(b/33197203): more cleanup here?
- mAppCallback = null;
- destroy();
- }, 0);
- } catch (RemoteException e) {
- Slog.w(TAG, "linkToDeath() failed: " + e);
+ if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
+ // TODO(b/33197203): ignoring because we don't support partitions yet
+ if (DEBUG) Slog.d(TAG, "updateLocked(): ignoring " + flags + " after auto-filled");
+ return;
}
- mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
- }
- void updateAutoFillInput(int flags, AutoFillId autoFillId,
- @Nullable AutoFillValue autoFillValue, @Nullable Rect bounds) {
- synchronized (mLock) {
- ViewState viewState = mViewStates.get(autoFillId);
- if (viewState == null) {
- viewState = new ViewState(this);
- mViewStates.put(autoFillId, viewState);
+ ViewState viewState = mViewStates.get(id);
+ if (viewState == null) {
+ viewState = new ViewState(id, this);
+ mViewStates.put(id, viewState);
+ }
+
+ if ((flags & FLAG_START_SESSION) != 0 ) {
+ // View is triggering auto-fill.
+ mCurrentViewState = viewState;
+ viewState.update(value, bounds);
+ return;
+ }
+
+ if ((flags & FLAG_VALUE_CHANGED) != 0 && value != null &&
+ !value.equals(viewState.mAutoFillValue)) {
+ viewState.mValueUpdated = true;
+
+ // Must check if this update was caused by auto-filling the view, in which
+ // case we just update the value, but not the UI.
+ if (mAutoFilledDataset != null) {
+ final AutoFillValue filledValue = findValue(mAutoFilledDataset, id);
+ if (value.equals(filledValue)) {
+ viewState.mAutoFillValue = value;
+ return;
+ }
}
- if ((flags & FLAG_UPDATE_UI_SHOW) != 0) {
- // Remove the UI if the ViewState has changed.
- if (mCurrentViewState != viewState) {
- mUi.hideFillUi();
- mCurrentViewState = viewState;
- }
-
- // If the ViewState is ready to be displayed, onReady() will be called.
- viewState.update(autoFillValue, bounds);
-
- // TODO(b/33197203): Remove when there is a response per activity.
- if (mCurrentResponse != null) {
- viewState.setResponse(mCurrentResponse);
- }
- } else if ((flags & FLAG_UPDATE_UI_HIDE) != 0) {
- if (mCurrentViewState == viewState) {
- mUi.hideFillUi();
- mCurrentViewState = null;
- }
- } else {
- Slog.w(TAG, "unknown flags " + flags);
- }
+ // Just change value, don't update the UI
+ viewState.mAutoFillValue = value;
+ return;
}
+
+ if ((flags & FLAG_FOCUS_GAINED) != 0) {
+ // Remove the UI if the ViewState has changed.
+ if (mCurrentViewState != viewState) {
+ mUi.hideFillUi();
+ mCurrentViewState = viewState;
+ }
+
+ // If the ViewState is ready to be displayed, onReady() will be called.
+ viewState.update(value, bounds);
+
+ // TODO(b/33197203): Remove when there is a response per activity.
+ if (mCurrentResponse != null) {
+ viewState.setResponse(mCurrentResponse);
+ }
+
+ return;
+ }
+
+ if ((flags & FLAG_FOCUS_LOST) != 0) {
+ if (mCurrentViewState == viewState) {
+ mUi.hideFillUi();
+ mCurrentViewState = null;
+ }
+ return;
+ }
+
+ Slog.w(TAG, "unknown flags " + flags);
}
@Override
@@ -613,7 +713,7 @@
}
private void processResponseLocked(FillResponse response) {
- if (DEBUG) Slog.d(TAG, "showResponse(authRequired="
+ if (DEBUG) Slog.d(TAG, "processResponseLocked(authRequired="
+ response.getAuthentication() +"):" + response);
// TODO(b/33197203): add MetricsLogger calls
@@ -621,8 +721,8 @@
mCurrentResponse = response;
if (mCurrentResponse.getAuthentication() != null) {
- // ...or handle authentication.
- Intent fillInIntent = createAuthFillInIntent(response.getId(), mStructure,
+ // Handle authentication.
+ final Intent fillInIntent = createAuthFillInIntent(response.getId(), mStructure,
new Bundle(), new FillCallback(new IFillCallback.Stub() {
@Override
public void onCancellable(ICancellationSignal cancellation) {
@@ -639,17 +739,28 @@
@Override
public void onFailure(CharSequence message) {
getUiForShowing().showError(message);
- destroy();
+ removeSelf();
}
}));
getUiForShowing().showFillResponseAuthRequest(
mCurrentResponse.getAuthentication(), fillInIntent);
- } else {
- // TODO(b/33197203): Consider using mCurrentResponse, depends on partitioning design
- if (mCurrentViewState != null) {
- mCurrentViewState.setResponse(mCurrentResponse);
- }
+ return;
+ }
+
+ final ArraySet<AutoFillId> savableIds = mCurrentResponse.getSavableIds();
+ if (savableIds == null || savableIds.isEmpty()) {
+ // NOTE: it's assuming the response has no datasets, since when a dataset is added
+ // it's view id is automatically added to savable_ids
+ if (DEBUG) Slog.d(TAG, "processResponseLocked(): nothing to do");
+
+ removeSelf();
+ return;
+ }
+
+ // TODO(b/33197203): Consider using mCurrentResponse, depends on partitioning design
+ if (mCurrentViewState != null) {
+ mCurrentViewState.setResponse(mCurrentResponse);
}
}
@@ -658,8 +769,6 @@
// Autofill it directly...
if (dataset.getAuthentication() == null) {
autoFillApp(dataset);
- // For now just show this on every fill
- getUiForShowing().showSaveUi();
return;
}
@@ -675,7 +784,7 @@
public void onSuccess(FillResponse response) {
mCurrentResponse = createAuthenticatedResponse(
mCurrentResponse, response);
- Dataset augmentedDataset = Helper.findDatasetById(dataset.getId(),
+ final Dataset augmentedDataset = Helper.findDatasetById(dataset.getId(),
mCurrentResponse);
if (augmentedDataset != null) {
autoFill(augmentedDataset);
@@ -685,7 +794,7 @@
@Override
public void onFailure(CharSequence message) {
getUiForShowing().showError(message);
- destroy();
+ removeSelf();
}
}));
@@ -712,24 +821,25 @@
}
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("mActivityToken: "); pw.println(mActivityToken);
pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
+ pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState);
pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size());
final String prefix2 = prefix + " ";
for (Map.Entry<AutoFillId, ViewState> entry : mViewStates.entrySet()) {
- pw.print(prefix2);
- pw.print(entry.getKey()); pw.print(": " ); pw.println(entry.getValue());
+ pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
+ entry.getValue().dump(prefix2, pw);
}
- pw.print(prefix); pw.print("mUpdatedValues: "); pw.println(mUpdatedValues);
- pw.print(prefix); pw.print("mStructure: " );
- // TODO(b/33197203): add method do dump AssistStructure on pw
- if (mStructure != null) {
- pw.println("look at logcat" );
- mStructure.dump(); // dumps to logcat
- } else {
- pw.println("null");
+ if (VERBOSE) {
+ pw.print(prefix); pw.print("mStructure: " );
+ // TODO(b/33197203): add method do dump AssistStructure on pw
+ if (mStructure != null) {
+ pw.println("look at logcat" );
+ mStructure.dump(); // dumps to logcat
+ } else {
+ pw.println("null");
+ }
}
mRemoteFillService.dump(prefix, pw);
@@ -739,15 +849,27 @@
synchronized (mLock) {
try {
if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
+
mAppCallback.autoFill(dataset);
+ mAutoFilledDataset = dataset;
} catch (RemoteException e) {
Slog.w(TAG, "Error auto-filling activity: " + e);
}
}
}
+ void enableSessionLocked() {
+ if (DEBUG) Slog.d(TAG, "enableSessionLocked()");
+
+ try {
+ mAppCallback.enableSession();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error enabling session: " + e);
+ }
+ }
+
private AutoFillUI getUiForShowing() {
- mUi.setCallback(this, mId);
+ mUi.setCallback(this, mActivityToken);
return mUi;
}
@@ -784,12 +906,18 @@
return null;
}
- private void destroy() {
+ private void destroyLocked() {
+ mRemoteFillService.destroy();
+ mUi.hideAll();
+ mUi.setCallback(null, null);
+ }
+
+ private void removeSelf() {
+ if (VERBOSE) Slog.v(TAG, "removeSelf()");
+
synchronized (mLock) {
- mRemoteFillService.destroy();
- mUi.hideAll();
- mUi.setCallback(null, 0);
- removeSessionLocked(mId);
+ destroyLocked();
+ mSessions.remove(mActivityToken);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
index 5c6009a..201a889 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -40,8 +40,6 @@
final PrintWriter pw = getOutPrintWriter();
try {
switch (cmd) {
- case "fill":
- return requestAutoFill();
case "save":
return requestSave();
default:
@@ -60,20 +58,12 @@
pw.println(" help");
pw.println(" Prints this help text.");
pw.println("");
- 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 {
- final int userId = getUserIdFromArgs();
- mService.requestAutoFillForUser(userId);
- return 0;
- }
-
private int requestSave() throws RemoteException {
final int userId = getUserIdFromArgs();
mService.requestSaveForUser(userId);
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index da54d85..c482c40 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -15,7 +15,6 @@
*/
package com.android.server.autofill;
-
import static com.android.server.autofill.Helper.DEBUG;
import android.app.Notification;
@@ -29,7 +28,10 @@
import android.content.IntentSender;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.IBinder;
import android.util.ArraySet;
+import android.os.Looper;
+import android.text.format.DateUtils;
import android.util.Slog;
import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
@@ -40,6 +42,7 @@
import android.view.WindowManager.LayoutParams;
import android.widget.Toast;
+import com.android.internal.os.HandlerCaller;
import com.android.server.UiThread;
import com.android.server.autofill.AutoFillManagerServiceImpl.ViewState;
@@ -51,6 +54,8 @@
// TODO(b/33197203): document exactly what once the auto-fill bar is implemented
final class AutoFillUI {
private static final String TAG = "AutoFillUI";
+ private static final long SNACK_BAR_LIFETIME_MS = 30 * DateUtils.SECOND_IN_MILLIS;
+ private static final int MSG_HIDE_SNACK_BAR = 1;
private static final String EXTRA_AUTH_INTENT_SENDER =
"com.android.server.autofill.extra.AUTH_INTENT_SENDER";
@@ -70,13 +75,21 @@
private String mFilterText;
private AutoFillUiCallback mCallback;
- private int mClientId;
+ private IBinder mActivityToken;
- public interface AutoFillUiCallback {
- void authenticate(IntentSender intent, Intent fillInIntent);
- void fill(Dataset dataset);
- void save();
- }
+ private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
+ switch (msg.what) {
+ case MSG_HIDE_SNACK_BAR: {
+ hideSnackbarUiThread();
+ return;
+ }
+ default: {
+ Slog.w(TAG, "Invalid message: " + msg);
+ }
+ }
+ };
+ private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
+ mHandlerCallback, true);
/**
* Custom snackbar UI used for saving autofill or other informational messages.
@@ -88,10 +101,10 @@
mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
- void setCallback(AutoFillUiCallback callback, int clientId) {
+ void setCallback(AutoFillUiCallback callback, IBinder activityToken) {
hideAll();
mCallback = callback;
- mClientId = clientId;
+ mActivityToken = activityToken;
}
/**
@@ -144,9 +157,21 @@
if (!hasCallback()) {
return;
}
+
+ if (datasets == null) {
+ // TODO(b/33197203): shouldn't be called, but keeping the WTF for a while just to be
+ // safe, otherwise it would crash system server...
+ Slog.wtf(TAG, "showFillUI(): no dataset");
+ return;
+ }
+
+ // TODO(b/33197203): call to hideAll() was making it janky because then mViewState is set
+ // to null and hence the first check inside the lambada fails, causing it to be displayed
+ // twice in some cases.
hideAll();
+
UiThread.getHandler().runWithScissors(() -> {
- if (mViewState != viewState) {
+ if (mViewState == null || !mViewState.mId.equals(viewState.mId)) {
mViewState = viewState;
mFillView = new DatasetPicker(mContext, datasets,
@@ -158,12 +183,14 @@
callback.fill(dataset);
hideFillUi();
});
- // TODO: No magical numbers
+ // TODO(b/33197203): No magical numbers
mFillWindow = new AnchoredWindow(
mWm, mFillView, 800, ViewGroup.LayoutParams.WRAP_CONTENT);
- if (DEBUG) Slog.d(TAG, "show FillUi");
+ if (DEBUG) Slog.d(TAG, "show FillUi: " + viewState.mId);
}
+
+ // TODO(b/33197203): If bounds are the same we would not show, fix this
if (!bounds.equals(mBounds)) {
if (DEBUG) Slog.d(TAG, "update FillUi bounds: " + mBounds);
mBounds = bounds;
@@ -216,6 +243,7 @@
@Override
public void onCancelClick() {
+ // TODO(b/33197203): add MetricsLogger call
hideSnackbarUiThread();
}
}));
@@ -237,7 +265,7 @@
pw.println("AufoFill UI");
final String prefix = " ";
pw.print(prefix); pw.print("sResultCode: "); pw.println(sResultCode);
- pw.print(prefix); pw.print("mClientId: "); pw.println(mClientId);
+ pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar);
pw.print(prefix); pw.print("mViewState: "); pw.println(mViewState);
pw.print(prefix); pw.print("mBounds: "); pw.println(mBounds);
@@ -264,9 +292,16 @@
mSnackbar = snackBar;
mWm.addView(mSnackbar, params);
}, 0);
+
+ if (DEBUG) {
+ Slog.d(TAG, "showSnackbar(): auto dismissing it in " + SNACK_BAR_LIFETIME_MS + " ms");
+ }
+ mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessage(MSG_HIDE_SNACK_BAR),
+ SNACK_BAR_LIFETIME_MS);
}
private void hideSnackbarUiThread() {
+ mHandlerCaller.getHandler().removeMessages(MSG_HIDE_SNACK_BAR);
if (mSnackbar != null) {
mWm.removeView(mSnackbar);
mSnackbar = null;
@@ -279,6 +314,12 @@
}
}
+ interface AutoFillUiCallback {
+ void authenticate(IntentSender intent, Intent fillInIntent);
+ void fill(Dataset dataset);
+ void save();
+ }
+
/////////////////////////////////////////////////////////////////////////////////
// TODO(b/33197203): temporary code using a notification to request auto-fill. //
// Will be removed once UX decide the right way to present it to the user. //
@@ -345,7 +386,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- NotificationManager.from(mContext).notify(mClientId, notification.build());
+ NotificationManager.from(mContext).notify(0, notification.build());
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -355,7 +396,7 @@
private void hideFillResponseAuthUiUiThread() {
final long identity = Binder.clearCallingIdentity();
try {
- NotificationManager.from(mContext).cancel(mClientId);
+ NotificationManager.from(mContext).cancel(0);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 7fff410..7400a64 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -16,11 +16,15 @@
package com.android.server.autofill;
+import android.annotation.Nullable;
import android.os.Bundle;
import android.util.ArraySet;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillValue;
import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
@@ -55,8 +59,22 @@
return builder.toString();
}
- private Helper() {
- throw new UnsupportedOperationException("contains static members only");
+ /**
+ * Gets the value of a {@link Dataset} field by its id, or {@code null} if not found.
+ */
+ @Nullable
+ static AutoFillValue findValue(Dataset dataset, AutoFillId id) {
+ if (dataset != null) {
+ final ArrayList<AutoFillId> ids = dataset.getFieldIds();
+ final int size = ids.size();
+ for (int i = 0; i < size; i++) {
+ if (id.equals(ids.get(i))) {
+ return dataset.getFieldValues().get(i);
+ }
+
+ }
+ }
+ return null;
}
/**
@@ -80,4 +98,8 @@
}
return null;
}
+
+ private Helper() {
+ throw new UnsupportedOperationException("contains static members only");
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d75048d..ae76b61 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -701,18 +701,15 @@
public AssistStructure structure = null;
public AssistContent content = null;
public Bundle receiverExtras;
- public int resultCode;
public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
- String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _resultCode,
- int _userHandle) {
+ String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
activity = _activity;
extras = _extras;
intent = _intent;
hint = _hint;
receiver = _receiver;
receiverExtras = _receiverExtras;
- resultCode = _resultCode;
userHandle = _userHandle;
}
@Override
@@ -12457,7 +12454,7 @@
@Override
public Bundle getAssistContextExtras(int requestType) {
PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
- null, 0, null, true /* focused */, true /* newSessionId */,
+ null, null, true /* focused */, true /* newSessionId */,
UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT);
if (pae == null) {
return null;
@@ -12524,29 +12521,29 @@
public boolean requestAssistContextExtras(int requestType, IResultReceiver receiver,
Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
- 0, activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
+ activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
PENDING_ASSIST_EXTRAS_LONG_TIMEOUT) != null;
}
@Override
public boolean requestAutoFillData(IResultReceiver receiver, Bundle receiverExtras,
- int resultCode, IBinder activityToken) {
+ IBinder activityToken) {
// NOTE: we could always use ActivityManager.ASSIST_CONTEXT_FULL and let ActivityThread
// rely on the flags to decide whether the handleRequestAssistContextExtras() is for
// auto-fill, but it's safer to explicitly use new AutoFill types, in case the Assist
// requests use flags in the future as well (since their flags value might collide with the
// auto-fill flag values).
return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTO_FILL, null, null,
- receiver, receiverExtras, resultCode, activityToken, true, true,
- UserHandle.getCallingUserId(), null,
- PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT) != null;
+ receiver, receiverExtras, activityToken, true, true, UserHandle.getCallingUserId(),
+ null, PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT) != null;
}
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
- IResultReceiver receiver, Bundle receiverExtras, int resultCode, IBinder activityToken,
+ IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout) {
enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
"enqueueAssistContext()");
+
synchronized (this) {
ActivityRecord activity = getFocusedStack().topActivity();
if (activity == null) {
@@ -12582,8 +12579,10 @@
}
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,
- resultCode, userHandle);
+ userHandle);
+
// Increment the sessionId if necessary
if (newSessionId) {
mViSessionId++;
@@ -12666,15 +12665,11 @@
sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content);
sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
pae.receiverExtras);
- IBinder cb = extras.getBinder(AutoFillService.KEY_CALLBACK);
- if (cb != null) {
- sendBundle.putBinder(AutoFillService.KEY_CALLBACK, cb);
- }
}
}
if (sendReceiver != null) {
try {
- sendReceiver.send(pae.resultCode, sendBundle);
+ sendReceiver.send(0, sendBundle);
} catch (RemoteException e) {
}
return;
@@ -12699,7 +12694,7 @@
public boolean launchAssistIntent(Intent intent, int requestType, String hint, int userHandle,
Bundle args) {
- return enqueueAssistContext(requestType, intent, hint, null, null, 0, null,
+ return enqueueAssistContext(requestType, intent, hint, null, null, null,
true /* focused */, true /* newSessionId */, userHandle, args,
PENDING_ASSIST_EXTRAS_TIMEOUT) != null;
}
@@ -22739,13 +22734,6 @@
}
@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 da7dc7d..e954363 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -4913,24 +4913,4 @@
}
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;
- }
}