From 3461d3c069468e176fab6bc8b8e78c8e6bc81e8b Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Thu, 19 Jan 2017 08:54:55 -0800 Subject: [PATCH] Initial IME integration. - Created an AutoFillManager class, which provides methods to show the auto-fill bar for views and virtual nodes. - Automatically launches an auto-fill request when the IME is shown (and an AutoFillService is set for the given user) on TextViews. - Updated VirtualNodeListener to use this new API. BUG: 31001899 BUG: 34171325 Test: CtsAutoFillServiceTestCases passes Test: manual verification Change-Id: Id72ce97da70217081b5823cfc7b138412634fcf3 --- api/current.txt | 9 +- api/system-current.txt | 9 +- api/test-current.txt | 9 +- .../android/app/SystemServiceRegistry.java | 10 + .../service/autofill/AutoFillService.java | 8 +- .../service/autofill/FillCallback.java | 1 - .../autofill/IAutoFillManagerService.aidl | 6 + .../service/autofill/SaveCallback.java | 1 - core/java/android/view/View.java | 26 ++- .../android/view/autofill/AutoFillId.java | 7 + .../view/autofill/AutoFillManager.java | 136 +++++++++++++ .../view/autofill/VirtualViewDelegate.java | 24 +-- core/java/android/widget/EditText.java | 2 - core/java/android/widget/TextView.java | 19 ++ .../autofill/AutoFillManagerService.java | 75 ++++--- .../autofill/AutoFillManagerServiceImpl.java | 1 - .../android/server/autofill/AutoFillUI.java | 191 ++++-------------- 17 files changed, 326 insertions(+), 208 deletions(-) create mode 100644 core/java/android/view/autofill/AutoFillManager.java diff --git a/api/current.txt b/api/current.txt index e4ef7b037dff3..43ff980652074 100644 --- a/api/current.txt +++ b/api/current.txt @@ -46094,6 +46094,13 @@ package android.view.autofill { field public static final android.os.Parcelable.Creator CREATOR; } + public final class AutoFillManager { + 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 + } + public final class AutoFillType implements android.os.Parcelable { method public int describeContents(); method public static android.view.autofill.AutoFillType forList(); @@ -46153,7 +46160,7 @@ package android.view.autofill { public static abstract class VirtualViewDelegate.Callback { ctor public VirtualViewDelegate.Callback(); - method public void onFocusChanged(int, boolean); + method public void onAutoFillInputUpdated(int, android.graphics.Rect, int); method public void onNodeRemoved(int...); method public void onValueChanged(int); } diff --git a/api/system-current.txt b/api/system-current.txt index 4c63b93a5a9f1..18e82600691e0 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -49403,6 +49403,13 @@ package android.view.autofill { field public static final android.os.Parcelable.Creator CREATOR; } + public final class AutoFillManager { + 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 + } + public final class AutoFillType implements android.os.Parcelable { method public int describeContents(); method public static android.view.autofill.AutoFillType forList(); @@ -49462,7 +49469,7 @@ package android.view.autofill { public static abstract class VirtualViewDelegate.Callback { ctor public VirtualViewDelegate.Callback(); - method public void onFocusChanged(int, boolean); + method public void onAutoFillInputUpdated(int, android.graphics.Rect, int); method public void onNodeRemoved(int...); method public void onValueChanged(int); } diff --git a/api/test-current.txt b/api/test-current.txt index 4f28ed6ecf9b7..dca9aa6c79d30 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -46392,6 +46392,13 @@ package android.view.autofill { field public static final android.os.Parcelable.Creator CREATOR; } + public final class AutoFillManager { + 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 + } + public final class AutoFillType implements android.os.Parcelable { method public int describeContents(); method public static android.view.autofill.AutoFillType forList(); @@ -46451,7 +46458,7 @@ package android.view.autofill { public static abstract class VirtualViewDelegate.Callback { ctor public VirtualViewDelegate.Callback(); - method public void onFocusChanged(int, boolean); + method public void onAutoFillInputUpdated(int, android.graphics.Rect, int); method public void onNodeRemoved(int...); method public void onValueChanged(int); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 5d8909c1dbae8..5a75a673bf2ad 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -112,6 +112,7 @@ import android.os.health.SystemHealthManager; import android.os.storage.StorageManager; import android.print.IPrintManager; import android.print.PrintManager; +import android.service.autofill.IAutoFillManagerService; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; import android.telecom.TelecomManager; @@ -126,6 +127,7 @@ import android.view.WindowManager; import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.CaptioningManager; +import android.view.autofill.AutoFillManager; import android.view.inputmethod.InputMethodManager; import android.view.textclassifier.TextClassificationManager; import android.view.textservice.TextServicesManager; @@ -804,6 +806,14 @@ final class SystemServiceRegistry { IBinder b = ServiceManager.getServiceOrThrow(Context.FONT_SERVICE); return new FontManager(IFontManager.Stub.asInterface(b)); }}); + registerService(Context.AUTO_FILL_MANAGER_SERVICE, AutoFillManager.class, + new CachedServiceFetcher() { + @Override + public AutoFillManager createService(ContextImpl ctx) throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow(Context.AUTO_FILL_MANAGER_SERVICE); + IAutoFillManagerService service = IAutoFillManagerService.Stub.asInterface(b); + return new AutoFillManager(ctx, service); + }}); } /** diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java index 805d8e5f1a69c..c7c8321a82ed3 100644 --- a/core/java/android/service/autofill/AutoFillService.java +++ b/core/java/android/service/autofill/AutoFillService.java @@ -15,8 +15,6 @@ */ package android.service.autofill; -import static android.service.voice.VoiceInteractionSession.KEY_FLAGS; -import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE; import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL; import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE; @@ -30,13 +28,11 @@ import android.os.CancellationSignal; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.RemoteException; import android.util.Log; import android.view.autofill.AutoFillId; import android.view.autofill.FillResponse; import com.android.internal.os.HandlerCaller; -import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; // TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the @@ -49,8 +45,8 @@ import com.android.internal.os.SomeArgs; */ public abstract class AutoFillService extends Service { - static final String TAG = "AutoFillService"; - static final boolean DEBUG = true; // TODO: set to false once stable + private static final String TAG = "AutoFillService"; + static final boolean DEBUG = true; // TODO(b/33197203): set to false once stable /** * The {@link Intent} that must be declared as handled by the service. diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java index 5a9a9f68486c5..925da8b334627 100644 --- a/core/java/android/service/autofill/FillCallback.java +++ b/core/java/android/service/autofill/FillCallback.java @@ -21,7 +21,6 @@ import static android.service.autofill.AutoFillService.DEBUG; import android.annotation.Nullable; import android.app.Activity; import android.os.Bundle; -import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.autofill.FillResponse; diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl index f8ae57bb88096..ce421072bdc48 100644 --- a/core/java/android/service/autofill/IAutoFillManagerService.aidl +++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl @@ -16,7 +16,9 @@ package android.service.autofill; +import android.graphics.Rect; import android.os.Bundle; +import android.view.autofill.AutoFillId; /** * Mediator between apps being auto-filled and auto-fill service implementations. @@ -24,5 +26,9 @@ import android.os.Bundle; * {@hide} */ oneway interface IAutoFillManagerService { + + void showAutoFillInput(in AutoFillId id, in Rect boundaries); + + // TODO(b/33197203): remove it and refactor onShellCommand void requestAutoFill(IBinder activityToken, int userId, in Bundle extras, int flags); } diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java index 627d74c8e3490..04f8c9f5f74a8 100644 --- a/core/java/android/service/autofill/SaveCallback.java +++ b/core/java/android/service/autofill/SaveCallback.java @@ -21,7 +21,6 @@ import static android.service.autofill.AutoFillService.DEBUG; import android.app.Activity; import android.app.assist.AssistStructure.ViewNode; import android.os.Bundle; -import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.autofill.AutoFillId; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 13555f47c2c59..f85c138bf69e6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1748,6 +1748,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ int mAccessibilityViewId = NO_ID; + /** + * The stable ID of this view for auto-fill purposes. + */ + private int mAutoFillId = NO_ID; + + private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED; SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent; @@ -6911,8 +6917,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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()); - + mAutoFillId = getAccessibilityViewId(); + structure.setAutoFillId(mAutoFillId); structure.setAutoFillType(getAutoFillType()); } @@ -7042,7 +7048,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Describes the auto-fill type that should be used on callas to + * Describes the auto-fill type that should be used on calls to * {@link #autoFill(AutoFillValue)} and * {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}. * @@ -7538,6 +7544,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mAccessibilityViewId; } + /** + * Gets the unique identifier of this view for auto-fill purposes. + * + *

It's only set after {@link #onProvideAutoFillStructure(ViewStructure, int)} is called. + * + * @return The view autofill id or {@link #NO_ID} if + * {@link #onProvideAutoFillStructure(ViewStructure, int)} was not called yet. + * + * @hide + */ + public int getAutoFillViewId() { + return mAutoFillId; + } + /** * Gets the unique identifier of the window in which this View reseides. * diff --git a/core/java/android/view/autofill/AutoFillId.java b/core/java/android/view/autofill/AutoFillId.java index b7b694d260308..e9c1c3bccd727 100644 --- a/core/java/android/view/autofill/AutoFillId.java +++ b/core/java/android/view/autofill/AutoFillId.java @@ -42,6 +42,13 @@ public final class AutoFillId implements Parcelable { mVirtualId = virtualChildId; } + /** @hide */ + public AutoFillId(int parentId, int virtualChildId) { + mVirtual = true; + mViewId = parentId; + mVirtualId = virtualChildId; + } + /** @hide */ public int getViewId() { return mViewId; diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java new file mode 100644 index 0000000000000..cd9842fa6400c --- /dev/null +++ b/core/java/android/view/autofill/AutoFillManager.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.autofill; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Rect; +import android.os.RemoteException; +import android.service.autofill.IAutoFillManagerService; +import android.util.Log; +import android.view.View; + +/** + * App entry point to the AutoFill Framework. + */ +// TODO(b/33197203): improve this javadoc +public final class AutoFillManager { + + private static final String TAG = "AutoFillManager"; + private static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable + + /** + * Flag used to show the auto-fill UI affordance for a view. + */ + public static final int FLAG_UPDATE_UI_SHOW = 1 << 0; + + /** + * Flag used to hide the auto-fill UI affordance for a view. + */ + public static final int FLAG_UPDATE_UI_HIDE = 1 << 1; + + private final IAutoFillManagerService mService; + + /** + * @hide + */ + public AutoFillManager(@SuppressWarnings("unused") Context context, + IAutoFillManagerService service) { + mService = service; + } + + /** + * Updates the auto-fill bar for a given {@link View}. + * + * 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}. + */ + public void updateAutoFillInput(View view, int flags) { + if (DEBUG) { + Log.v(TAG, "updateAutoFillInput(" + view.getAutoFillViewId() + "): flags=" + flags); + } + + updateAutoFillInput(view, false, View.NO_ID, null, flags); + } + + /** + * Updates the auto-fill bar for a virtual child of a given {@link View}. + * + * 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 boundaries boundaries of the child (inside the parent; could be {@code null} when + * flag is {@link #FLAG_UPDATE_UI_HIDE}. + * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or + * {@link #FLAG_UPDATE_UI_HIDE}. + */ + public void updateAutoFillInput(View parent, int childId, @Nullable Rect boundaries, + int flags) { + if (DEBUG) { + Log.v(TAG, "updateAutoFillInput(" + parent.getAutoFillViewId() + ", " + childId + + "): boundaries=" + boundaries + ", flags=" + flags); + } + updateAutoFillInput(parent, true, childId, boundaries, flags); + } + + private void updateAutoFillInput(View view, boolean virtual, int childId, Rect boundaries, + int flags) { + if ((flags & FLAG_UPDATE_UI_SHOW) != 0) { + final int viewId = view.getAutoFillViewId(); + final AutoFillId id = virtual + ? new AutoFillId(viewId, childId) + : new AutoFillId(viewId); + showAutoFillInput(id, boundaries); + return; + } + // TODO(b/33197203): handle FLAG_UPDATE_UI_HIDE + } + + private void showAutoFillInput(AutoFillId id, Rect boundaries) { + final int autoFillViewId = id.getViewId(); + /* + * TODO(b/33197203): currently SHOW_AUTO_FILL_BAR is only set once per activity (i.e, when + * the view does not have an auto-fill id), but it should be called again for views that + * were not part of the initial auto-fill dataset returned by the service. For example: + * + * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`. + * 2.User taps `first_name`. + * 3.Service returns a dataset with ids for `first_name` and `last_name`. + * 4.When user taps `first_name` (again) or `last_name`, flag should not have + * SHOW_AUTO_FILL_BAR set, but when user taps `address`, it should (since that field was + * not part of the initial dataset). + * + * Similarly, once the activity is auto-filled, the flag logic should be reset (so if the + * user taps the view again, a new auto-fill request is made) + */ + if (autoFillViewId != View.NO_ID) { + return; + } + + try { + mService.showAutoFillInput(id, boundaries); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/view/autofill/VirtualViewDelegate.java b/core/java/android/view/autofill/VirtualViewDelegate.java index a19b4e53ba525..278bf4f34c36a 100644 --- a/core/java/android/view/autofill/VirtualViewDelegate.java +++ b/core/java/android/view/autofill/VirtualViewDelegate.java @@ -15,6 +15,8 @@ */ package android.view.autofill; +import android.annotation.Nullable; +import android.graphics.Rect; import android.util.Log; import android.view.View; import android.view.ViewStructure; @@ -71,20 +73,18 @@ public abstract class VirtualViewDelegate { public abstract static class Callback { /** - * Sent when the focus inside the hierarchy changed. + * Sent when the auto-fill bar for a child must be updated. * - *

Typically callled twice - for the nodes that lost and gained focus. - * - *

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 focus changed. - * @param hasFocus {@code true} when focus was gained, {@code false} when it was lost. + * See {@link AutoFillManager#updateAutoFillInput(View, int, android.graphics.Rect, int)} + * for more details. */ - public void onFocusChanged(int virtualId, boolean hasFocus) { - if (DEBUG) Log.d(TAG, "onFocusChanged() for " + virtualId + ": " + hasFocus); + // 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); + } } /** diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index faf0379648973..67067906cb66e 100644 --- a/core/java/android/widget/EditText.java +++ b/core/java/android/widget/EditText.java @@ -169,8 +169,6 @@ public class EditText extends TextView { Log.w(VIEW_LOG_TAG, "EditText.autoFill(): no text on AutoFillValue"); return; } - // TODO(b/33197203): once auto-fill is triggered by the IME, we'll need a new setText() - // or setAutoFillText() method on TextView to avoid re-triggering it. setText(text); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 2f303cdba301a..1f8c411d714e6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -136,6 +136,7 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; +import android.view.autofill.AutoFillManager; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; @@ -264,6 +265,7 @@ import java.util.Locale; public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { static final String LOG_TAG = "TextView"; static final boolean DEBUG_EXTRACT = false; + static final boolean DEBUG_AUTOFILL = false; // Enum for the "typeface" XML parameter. // TODO: How can we get this from the XML instead of hardcoding it here? @@ -9029,6 +9031,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Spannable sp = (Spannable) mText; MetaKeyKeyListener.resetMetaState(sp); } + } else { + final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class); + if (afm != null) { + if (DEBUG_AUTOFILL) { + Log.v(LOG_TAG, "onFocusChanged(): id=" + getAutoFillViewId() + ", focused= " + + focused); + } + afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_HIDE); + } } startStopMarquee(focused); @@ -10607,6 +10618,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @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=" + getAutoFillViewId()); + + // 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); } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java index 87eaf29fb3065..6779657452bae 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java @@ -19,10 +19,6 @@ package com.android.server.autofill; import static android.Manifest.permission.MANAGE_AUTO_FILL; import static android.content.Context.AUTO_FILL_MANAGER_SERVICE; import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL; -import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE; - -import static com.android.server.autofill.AutoFillUI.MSG_SHOW_ALL_NOTIFICATIONS; -import static com.android.server.autofill.AutoFillUI.SHOW_ALL_NOTIFICATIONS_DELAY_MS; import android.Manifest; import android.app.AppGlobals; @@ -32,11 +28,13 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.database.ContentObserver; +import android.graphics.Rect; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ResultReceiver; @@ -51,9 +49,12 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; +import android.view.autofill.AutoFillId; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; +import com.android.internal.os.HandlerCaller; +import com.android.internal.os.SomeArgs; import com.android.server.FgThread; import com.android.server.SystemService; @@ -70,13 +71,12 @@ import java.io.PrintWriter; public final class AutoFillManagerService extends SystemService { private static final String TAG = "AutoFillManagerService"; - static final boolean DEBUG = true; // TODO: change to false once stable + static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS; - private static final int ARG_NOT_USED = 0; - protected static final int MSG_UNBIND = 1; + protected static final int MSG_SHOW_AUTO_FILL = 2; private final AutoFillManagerServiceStub mServiceStub; private final AutoFillUI mUi; @@ -85,23 +85,28 @@ public final class AutoFillManagerService extends SystemService { private final Object mLock = new Object(); - private final Handler mHandler = new Handler() { + private final HandlerCaller.Callback mHandlerCallback = new HandlerCaller.Callback() { + @Override - public void handleMessage(Message msg) { + public void executeMessage(Message msg) { switch (msg.what) { - case MSG_UNBIND: + case MSG_UNBIND: { removeStaleServiceForUser(msg.arg1); return; - case MSG_SHOW_ALL_NOTIFICATIONS: - mUi.showAllNotifications(); + } case MSG_SHOW_AUTO_FILL: { + final SomeArgs args = (SomeArgs) msg.obj; + showAutoFillInput(msg.arg1, (AutoFillId) args.arg1, (Rect) args.arg2); return; - default: + } default: { Slog.w(TAG, "Invalid message: " + msg); + } } } }; + private HandlerCaller mHandlerCaller; + /** * Cache of {@link AutoFillManagerServiceImpl} per user id. *

@@ -122,6 +127,8 @@ public final class AutoFillManagerService extends SystemService { public AutoFillManagerService(Context context) { super(context); + mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true); + mContext = context; mUi = new AutoFillUI(context, this, mLock); mResolver = context.getContentResolver(); @@ -139,14 +146,6 @@ public final class AutoFillManagerService extends SystemService { if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { new SettingsObserver(BackgroundThread.getHandler()); } - if (phase == PHASE_BOOT_COMPLETED) { - // TODO: if sent right away, the notification is not displayed. Since the notification - // mechanism is a temporary approach anyways, just delay it.. - if (DEBUG) - Slog.d(TAG, "Showing notifications in " + SHOW_ALL_NOTIFICATIONS_DELAY_MS + "ms"); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ALL_NOTIFICATIONS), - SHOW_ALL_NOTIFICATIONS_DELAY_MS); - } } private AutoFillManagerServiceImpl newServiceForUser(int userId) { @@ -200,7 +199,7 @@ public final class AutoFillManagerService extends SystemService { } // Keep service connection alive for a while, in case user needs to interact with it // (for example, to save the data that was inputted in) - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UNBIND, userId, ARG_NOT_USED), + mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessageI(MSG_UNBIND, userId), SERVICE_BINDING_LIFETIME_MS); return service; } @@ -245,18 +244,41 @@ public final class AutoFillManagerService extends SystemService { } + + private void requestAutoFillLocked(IBinder activityToken, int userId, Bundle extras, + int flags) { + final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); + if (service != null) { + service.requestAutoFill(activityToken, extras, flags); + } + } + + private void showAutoFillInput(int userId, AutoFillId id, Rect rect) { + if (DEBUG) Slog.d(TAG, "handler.showAutoFillInput(): id=" + id + ", rect=" + rect); + + synchronized (mLock) { + requestAutoFillLocked(null, userId, null, AUTO_FILL_FLAG_TYPE_FILL); + } + } + final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub { + @Override + public void showAutoFillInput(AutoFillId id, Rect boundaries) { + if (DEBUG) Slog.d(TAG, "showAutoFillInput(): id=" + id + ", boundaries=" + boundaries); + + // TODO(b/33197203): fail if it's not called by same uid as the top activity + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW_AUTO_FILL, + UserHandle.getCallingUserId(), id, boundaries)); + } + @Override public void requestAutoFill(IBinder activityToken, int userId, Bundle extras, int flags) { if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId); mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); synchronized (mLock) { - final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); - if (service != null) { - service.requestAutoFill(activityToken, extras, flags); - } + requestAutoFillLocked(activityToken, userId, extras, flags); } } @@ -307,7 +329,6 @@ public final class AutoFillManagerService extends SystemService { if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId); synchronized (mLock) { removeCachedServiceForUserLocked(userId); - mUi.updateNotification(userId); } } } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java index ae21b07567d84..83faf1b42423f 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java @@ -163,7 +163,6 @@ final class AutoFillManagerServiceImpl { } final AssistStructure structure = resultData .getParcelable(VoiceInteractionSession.KEY_STRUCTURE); - final Bundle data = resultData.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS); final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0); final ServerCallback serverCallback; diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java index 08e81d36f9cfb..6fafd5d7167ff 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java @@ -16,40 +16,29 @@ package com.android.server.autofill; import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE; -import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL; import static com.android.server.autofill.AutoFillManagerService.DEBUG; import android.app.Activity; -import android.app.AppGlobals; import android.app.Notification; import android.app.Notification.Action; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; -import android.content.pm.UserInfo; import android.os.Binder; import android.os.Bundle; -import android.os.RemoteException; -import android.os.UserManager; -import android.provider.Settings; import android.service.autofill.AutoFillService; -import android.text.TextUtils; import android.util.Log; import android.util.Slog; -import android.view.autofill.Dataset; import android.view.autofill.AutoFillId; +import android.view.autofill.Dataset; import android.view.autofill.FillResponse; import android.widget.Toast; +import com.android.internal.annotations.GuardedBy; import com.android.server.UiThread; import java.util.Arrays; @@ -69,9 +58,10 @@ final class AutoFillUI { AutoFillUI(Context context, AutoFillManagerService service, Object lock) { mContext = context; - mResolver = context.getContentResolver(); mService = service; mLock = lock; + + setNotificationListener(); } /** @@ -121,56 +111,60 @@ final class AutoFillUI { private static final String EXTRA_FILL_RESPONSE = "fill_response"; private static final String EXTRA_DATASET = "dataset"; - private static final String TYPE_EMULATE = "emulate"; private static final String TYPE_OPTIONS = "options"; private static final String TYPE_DELETE_CALLBACK = "delete_callback"; private static final String TYPE_PICK_DATASET = "pick_dataset"; private static final String TYPE_SAVE = "save"; - static final int MSG_SHOW_ALL_NOTIFICATIONS = 42; - static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000; - + @GuardedBy("mLock") private BroadcastReceiver mNotificationReceiver; - private final ContentResolver mResolver; + @GuardedBy("mLock") private final AutoFillManagerService mService; private final Object mLock; // Hack used to generate unique pending intents static int sResultCode = 0; + private void setNotificationListener() { + synchronized (mLock) { + if (mNotificationReceiver == null) { + mNotificationReceiver = new NotificationReceiver(); + mContext.registerReceiver(mNotificationReceiver, + new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT)); + } + } + } + final class NotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { final int userId = intent.getIntExtra(EXTRA_USER_ID, -1); - final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId); - if (service == null) { - Slog.w(TAG, "no auto-fill service for user " + userId); - return; - } - - final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1); - final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE); - if (type == null) { - Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent); - return; - } - final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE); - final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET); - final Bundle datasetArgs = dataset == null ? null : dataset.getExtras(); - final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras(); - - // Bundle sent on AutoFillService methods - only set if service provided a bundle - final Bundle extras = (datasetArgs == null && fillDataArgs == null) - ? null : new Bundle(); - - if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId - + ", callbackId=" + callbackId); synchronized (mLock) { + final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId); + if (service == null) { + Slog.w(TAG, "no auto-fill service for user " + userId); + return; + } + + final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1); + final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE); + if (type == null) { + Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent); + return; + } + final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE); + final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET); + final Bundle datasetArgs = dataset == null ? null : dataset.getExtras(); + final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras(); + + // Bundle sent on AutoFillService methods - only set if service provided a bundle + final Bundle extras = (datasetArgs == null && fillDataArgs == null) + ? null : new Bundle(); + + if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId + + ", callbackId=" + callbackId); switch (type) { - case TYPE_EMULATE: - service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_FILL); - break; case TYPE_SAVE: if (datasetArgs != null) { if (DEBUG) Log.d(TAG, "filldata args on save notificataion: " + @@ -210,54 +204,6 @@ final class AutoFillUI { } } - private ComponentName getProviderForUser(int userId) { - ComponentName serviceComponent = null; - ServiceInfo serviceInfo = null; - final String componentName = Settings.Secure.getStringForUser( - mResolver, Settings.Secure.AUTO_FILL_SERVICE, userId); - if (!TextUtils.isEmpty(componentName)) { - try { - serviceComponent = ComponentName.unflattenFromString(componentName); - serviceInfo = - AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, userId); - } catch (RuntimeException | RemoteException e) { - Slog.wtf(TAG, "Bad auto-fill service name " + componentName, e); - return null; - } - } - - if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component=" - + serviceComponent + ", info: " + serviceInfo); - if (serviceInfo == null) { - Slog.w(TAG, "no service info for " + serviceComponent); - return null; - } - return serviceComponent; - } - - void showAllNotifications() { - final UserManager userManager = - (UserManager) mContext.getSystemService(Context.USER_SERVICE); - - final List allUsers = userManager.getUsers(true); - - for (UserInfo user : allUsers) { - final ComponentName serviceComponent = getProviderForUser(user.id); - if (serviceComponent != null) { - showMainNotification(serviceComponent, user.id); - } - } - } - - void updateNotification(int userId) { - final ComponentName serviceComponent = getProviderForUser(userId); - if (serviceComponent == null) { - cancelMainNotification(userId); - } else { - showMainNotification(serviceComponent, userId); - } - } - private static Intent newNotificationIntent(int userId, String type) { final Intent intent = new Intent(NOTIFICATION_AUTO_FILL_INTENT); intent.putExtra(EXTRA_USER_ID, userId); @@ -295,65 +241,6 @@ final class AutoFillUI { return builder.append(']').toString(); } - /** - * Shows a permanent notification that triggers the auto-fill workflow for the given user. - * - *

It emulates calling the auto-fill service when the IME is shown. - */ - private void showMainNotification(ComponentName serviceComponent, int userId) { - if (DEBUG) Log.d(TAG, "showNotification() for " + userId + ": " + serviceComponent); - - synchronized (mLock) { - if (mNotificationReceiver == null) { - mNotificationReceiver = new NotificationReceiver(); - mContext.registerReceiver(mNotificationReceiver, - new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT)); - } - } - - final Intent fillIntent = newNotificationIntent(userId, TYPE_EMULATE); - final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext, - -1, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - final String packageName = serviceComponent.getPackageName(); - String providerName = null; - final PackageManager pm = mContext.getPackageManager(); - try { - final ApplicationInfo info = pm.getApplicationInfoAsUser(packageName, 0, userId); - if (info != null) { - providerName = pm.getApplicationLabel(info).toString(); - } - } catch (Exception e) { - providerName = packageName; - } - final String title = "AutoFill IME Emulation"; - final String subTitle = "Tap notification to start auto-fill workflow (by '" + providerName - + "' on top activity on user " + userId + ".\n" - + "Once provider replies, a new notification will show your options."; - - final Notification notification = new Notification.Builder(mContext) - .setCategory(Notification.CATEGORY_SYSTEM) - .setOngoing(true) - .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) - .setLocalOnly(true) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)) - .setContentTitle(title) - .setStyle(new Notification.BigTextStyle().bigText(subTitle)) - .setContentIntent(fillPendingIntent) - .build(); - NotificationManager.from(mContext).notify(TYPE_EMULATE, userId, notification); - } - - /** - * Cancels the permament notification created by - * {@link #showMainNotification(ComponentName, int)}. - */ - private void cancelMainNotification(int userId) { - if (DEBUG) Log.d(TAG, "cancelNotificationLocked(): " + userId); - NotificationManager.from(mContext).cancel(TYPE_EMULATE, userId); - } - /** * Shows a notification with the results of an auto-fill request, using notications actions * to emulate the auto-fill bar buttons displaying the dataset names.