diff options
24 files changed, 589 insertions, 373 deletions
diff --git a/Android.mk b/Android.mk index bd04214e427e..f6e4b0a01bd6 100644 --- a/Android.mk +++ b/Android.mk @@ -251,6 +251,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/storage/IObbActionListener.aidl \ core/java/android/security/IKeystoreService.aidl \ core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl \ + core/java/android/service/autofill/IAutoFillCallback.aidl \ core/java/android/service/autofill/IAutoFillManagerService.aidl \ core/java/android/service/autofill/IAutoFillService.aidl \ core/java/android/service/carrier/ICarrierService.aidl \ diff --git a/api/current.txt b/api/current.txt index df8a0ebfbf01..3ae120af6843 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6265,6 +6265,7 @@ package android.app.assist { public static class AssistStructure.ViewNode { method public float getAlpha(); + method public int getAutoFillId(); method public android.app.assist.AssistStructure.ViewNode getChildAt(int); method public int getChildCount(); method public java.lang.String getClassName(); @@ -34641,13 +34642,26 @@ package android.service.autofill { public abstract class AutoFillService extends android.app.Service { ctor public AutoFillService(); method public final android.os.IBinder onBind(android.content.Intent); - method public void onNewSession(java.lang.String, android.os.Bundle, int, android.app.assist.AssistStructure); + method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback); method public void onReady(); - method public void onSessionFinished(java.lang.String); method public void onShutdown(); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService"; } + public final class FillCallback { + method public void onFailure(java.lang.CharSequence); + method public void onSuccess(android.service.autofill.FillCallback.FillData); + } + + public static final class FillCallback.FillData { + } + + public static class FillCallback.FillData.Builder { + ctor public FillCallback.FillData.Builder(); + method public android.service.autofill.FillCallback.FillData build(); + method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String); + } + } package android.service.carrier { diff --git a/api/system-current.txt b/api/system-current.txt index c6d08e468fef..b45ee69ba0ec 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6456,6 +6456,7 @@ package android.app.assist { public static class AssistStructure.ViewNode { method public float getAlpha(); + method public int getAutoFillId(); method public android.app.assist.AssistStructure.ViewNode getChildAt(int); method public int getChildCount(); method public java.lang.String getClassName(); @@ -37434,13 +37435,26 @@ package android.service.autofill { public abstract class AutoFillService extends android.app.Service { ctor public AutoFillService(); method public final android.os.IBinder onBind(android.content.Intent); - method public void onNewSession(java.lang.String, android.os.Bundle, int, android.app.assist.AssistStructure); + method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback); method public void onReady(); - method public void onSessionFinished(java.lang.String); method public void onShutdown(); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService"; } + public final class FillCallback { + method public void onFailure(java.lang.CharSequence); + method public void onSuccess(android.service.autofill.FillCallback.FillData); + } + + public static final class FillCallback.FillData { + } + + public static class FillCallback.FillData.Builder { + ctor public FillCallback.FillData.Builder(); + method public android.service.autofill.FillCallback.FillData build(); + method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String); + } + } package android.service.carrier { diff --git a/api/test-current.txt b/api/test-current.txt index 0287612e022f..e7037e69d45c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6281,6 +6281,7 @@ package android.app.assist { public static class AssistStructure.ViewNode { method public float getAlpha(); + method public int getAutoFillId(); method public android.app.assist.AssistStructure.ViewNode getChildAt(int); method public int getChildCount(); method public java.lang.String getClassName(); @@ -34731,13 +34732,26 @@ package android.service.autofill { public abstract class AutoFillService extends android.app.Service { ctor public AutoFillService(); method public final android.os.IBinder onBind(android.content.Intent); - method public void onNewSession(java.lang.String, android.os.Bundle, int, android.app.assist.AssistStructure); + method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback); method public void onReady(); - method public void onSessionFinished(java.lang.String); method public void onShutdown(); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService"; } + public final class FillCallback { + method public void onFailure(java.lang.CharSequence); + method public void onSuccess(android.service.autofill.FillCallback.FillData); + } + + public static final class FillCallback.FillData { + } + + public static class FillCallback.FillData.Builder { + ctor public FillCallback.FillData.Builder(); + method public android.service.autofill.FillCallback.FillData build(); + method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String); + } + } package android.service.carrier { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index b38133901b30..6f53f13662be 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -53,7 +53,6 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.Drawable; -import android.hardware.input.InputManager; import android.media.AudioManager; import android.media.session.MediaController; import android.net.Uri; @@ -70,6 +69,9 @@ import android.os.ServiceManager.ServiceNotFoundException; import android.os.StrictMode; import android.os.SystemProperties; import android.os.UserHandle; +import android.service.autofill.FillableInputField; +import android.service.autofill.AutoFillService; +import android.service.autofill.IAutoFillCallback; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -90,8 +92,6 @@ import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextThemeWrapper; import android.view.DragAndDropPermissions; import android.view.DragEvent; -import android.view.InputDevice; -import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; import android.view.KeyboardShortcutInfo; @@ -113,9 +113,11 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView; +import android.widget.EditText; import android.widget.Toast; import android.widget.Toolbar; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; @@ -802,11 +804,13 @@ public class Activity extends ContextThemeWrapper private boolean mReleased; private boolean mUpdated; } - private final ArrayList<ManagedCursor> mManagedCursors = - new ArrayList<ManagedCursor>(); - // protected by synchronized (this) + @GuardedBy("mManagedCursors") + private final ArrayList<ManagedCursor> mManagedCursors = new ArrayList<>(); + + @GuardedBy("this") int mResultCode = RESULT_CANCELED; + @GuardedBy("this") Intent mResultData = null; private TranslucentConversionListener mTranslucentCallback; @@ -837,6 +841,9 @@ public class Activity extends ContextThemeWrapper private boolean mHasCurrentPermissionsRequest; private boolean mEatKeyUpEvent; + @GuardedBy("this") + private IAutoFillCallback mAutoFillCallback; + private static native String getDlWarning(); /** Return the intent that started this activity. */ @@ -1688,6 +1695,53 @@ public class Activity extends ContextThemeWrapper } /** + * Lazily gets the {@code IAutoFillCallback} for this activitity. + * + * <p>This callback is used by the {@link AutoFillService} app to auto-fill the activity fields. + */ + IAutoFillCallback getAutoFillCallback() { + synchronized (this) { + if (mAutoFillCallback == null) { + mAutoFillCallback = new IAutoFillCallback.Stub() { + @Override + public void autofill(@SuppressWarnings("rawtypes") List fields) + throws RemoteException { + runOnUiThread(() -> { + final View root = getWindow().getDecorView().getRootView(); + for (Object field : fields) { + if (!(field instanceof FillableInputField)) { + Slog.w(TAG, "autofill(): invalid type " + field.getClass()); + continue; + } + FillableInputField autoFillField = (FillableInputField) field; + final int viewId = autoFillField.getId(); + final View view = root.findViewByAccessibilityIdTraversal(viewId); + // TODO: should handle other types of view as well, but that will + // require: + // - a new interface like AutoFillable + // - a way for the views to define the type of the autofield value + if ((view instanceof EditText)) { + ((EditText) view).setText(autoFillField.getValue()); + } + } + }); + } + + @Override + public void showError(String message) { + runOnUiThread(() -> { + // TODO: temporary show a toast until it uses the Snack bar. + Toast.makeText(Activity.this, "Auto-fill request failed: " + message, + Toast.LENGTH_LONG).show(); + }); + } + }; + } + } + return mAutoFillCallback; + } + + /** * Request the Keyboard Shortcuts screen to show up. This will trigger * {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity. */ @@ -5974,6 +6028,11 @@ public class Activity extends ContextThemeWrapper getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args); } + if (mAutoFillCallback != null) { + writer.print(prefix); writer.print("mAutoFillCallback: " ); + writer.println(mAutoFillCallback); + } + mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 4a39e4a4cb50..c55d3b8208d7 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -447,6 +447,9 @@ public class ActivityManager { /** @hide requestType for assist context: generate full AssistStructure. */ public static final int ASSIST_CONTEXT_FULL = 1; + /** @hide requestType for assist context: generate full AssistStructure for auto-fill. */ + public static final int ASSIST_CONTEXT_AUTOFILL = 2; + /** @hide Flag for registerUidObserver: report changes in process state. */ public static final int UID_OBSERVER_PROCSTATE = 1<<0; diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 199fda4cd09c..cabdd4b6ac00 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -109,4 +109,4 @@ public abstract class ActivityManagerNative { static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) { ActivityManager.noteAlarmFinish(ps, sourceUid, tag); } -}
\ No newline at end of file +} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a3414f458ef3..d41a7e8102de 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -88,6 +88,8 @@ import android.provider.Downloads; import android.provider.Settings; import android.security.NetworkSecurityPolicy; import android.security.net.config.NetworkSecurityConfigProvider; +import android.service.autofill.IAutoFillCallback; +import android.service.voice.VoiceInteractionSession; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -2881,16 +2883,24 @@ public final class ActivityThread { mLastAssistStructures.remove(i); } } + // Filling for auto-fill has a few differences: + // - it does not need an AssistContent + // - it does not call onProvideAssistData() + // - it needs an IAutoFillCallback + boolean forAutofill = cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTOFILL; + Bundle data = new Bundle(); AssistStructure structure = null; - AssistContent content = new AssistContent(); + AssistContent content = forAutofill ? null : new AssistContent(); ActivityClientRecord r = mActivities.get(cmd.activityToken); Uri referrer = null; if (r != null) { r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data); - r.activity.onProvideAssistData(data); + if (!forAutofill) { + r.activity.onProvideAssistData(data); + } referrer = r.activity.onProvideReferrer(); - if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL) { + if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutofill) { structure = new AssistStructure(r.activity); Intent activityIntent = r.activity.getIntent(); if (activityIntent != null && (r.window == null || @@ -2900,11 +2910,21 @@ public final class ActivityThread { intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)); intent.removeUnsafeExtras(); - content.setDefaultIntent(intent); + if (forAutofill) { + IAutoFillCallback autoFillCallback = r.activity.getAutoFillCallback(); + data.putBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK, + autoFillCallback.asBinder()); + } else { + content.setDefaultIntent(intent); + } } else { - content.setDefaultIntent(new Intent()); + if (!forAutofill) { + content.setDefaultIntent(new Intent()); + } + } + if (!forAutofill) { + r.activity.onProvideAssistContent(content); } - r.activity.onProvideAssistContent(content); } } if (structure == null) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 9fe34c0e39bc..d63d37be9f2d 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -564,6 +564,8 @@ interface IActivityManager { void moveStackToDisplay(int stackId, int displayId) = 403; void enterPictureInPictureModeWithAspectRatio(in IBinder token, float aspectRatio) = 404; void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio) = 405; + boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras, + in IBinder activityToken) = 406; // Please keep these transaction codes the same -- they are also // sent by C++ code. when a new method is added, use the next available transaction id. diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 6b720c0a22f6..a6f23666cc6d 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -515,6 +515,7 @@ public class AssistStructure implements Parcelable { String mIdPackage; String mIdType; String mIdEntry; + int mAutoFillId = View.NO_ID; int mX; int mY; int mScrollX; @@ -539,6 +540,7 @@ public class AssistStructure implements Parcelable { static final int FLAGS_ACTIVATED = 0x00002000; static final int FLAGS_CONTEXT_CLICKABLE = 0x00004000; + static final int FLAGS_HAS_AUTO_FILL_ID = 0x80000000; static final int FLAGS_HAS_MATRIX = 0x40000000; static final int FLAGS_HAS_ALPHA = 0x20000000; static final int FLAGS_HAS_ELEVATION = 0x10000000; @@ -582,6 +584,9 @@ public class AssistStructure implements Parcelable { } } } + if ((flags&FLAGS_HAS_AUTO_FILL_ID) != 0) { + mAutoFillId = in.readInt(); + } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { mX = in.readInt(); mY = in.readInt(); @@ -637,6 +642,9 @@ public class AssistStructure implements Parcelable { if (mId != View.NO_ID) { flags |= FLAGS_HAS_ID; } + if (mAutoFillId != View.NO_ID) { + flags |= FLAGS_HAS_AUTO_FILL_ID; + } if ((mX&~0x7fff) != 0 || (mY&~0x7fff) != 0 || (mWidth&~0x7fff) != 0 | (mHeight&~0x7fff) != 0) { flags |= FLAGS_HAS_LARGE_COORDS; @@ -681,6 +689,9 @@ public class AssistStructure implements Parcelable { } } } + if ((flags&FLAGS_HAS_AUTO_FILL_ID) != 0) { + out.writeInt(mAutoFillId); + } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { out.writeInt(mX); out.writeInt(mY); @@ -751,6 +762,16 @@ public class AssistStructure implements Parcelable { } /** + * Returns the id that can be used to auto-fill the view. + * + * <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not + * for assist. + */ + public int getAutoFillId() { + return mAutoFillId; + } + + /** * Returns the left edge of this view, in pixels, relative to the left edge of its parent. */ public int getLeft() { @@ -1322,6 +1343,11 @@ public class AssistStructure implements Parcelable { public Rect getTempRect() { return mAssist.mTmpRect; } + + @Override + public void setAutoFillId(int autoFillId) { + mNode.mAutoFillId = autoFillId; + } } /** @hide */ diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java index 14ce009bedc6..83b2065f44fe 100644 --- a/core/java/android/service/autofill/AutoFillService.java +++ b/core/java/android/service/autofill/AutoFillService.java @@ -15,27 +15,37 @@ */ package android.service.autofill; +import android.annotation.IntDef; import android.annotation.SdkConstant; +import android.app.Activity; import android.app.Service; import android.app.assist.AssistStructure; import android.content.Intent; import android.os.Bundle; +import android.os.CancellationSignal; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.service.voice.VoiceInteractionSession; import android.util.Log; import com.android.internal.os.HandlerCaller; +import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Top-level service of the current auto-fill service for a given user. + * + * <p>Apps providing auto-fill capabilities must extend this service. */ -// TODO: expand documentation public abstract class AutoFillService extends Service { - private static final String TAG = "AutoFillService"; - private static final boolean DEBUG = true; // TODO: set to false once stable + static final String TAG = "AutoFillService"; + static final boolean DEBUG = true; // TODO: set to false once stable /** * The {@link Intent} that must be declared as handled by the service. @@ -47,11 +57,23 @@ public abstract class AutoFillService extends Service { public static final String SERVICE_INTERFACE = "android.service.autofill.AutoFillService"; private static final int MSG_READY = 1; - private static final int MSG_NEW_SESSION = 2; - private static final int MSG_SESSION_FINISHED = 3; - private static final int MSG_SHUTDOWN = 4; + private static final int MSG_AUTO_FILL = 2; + private static final int MSG_SHUTDOWN = 3; - // TODO: add metadata? + private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { + @Override + public void send(int resultCode, Bundle resultData) throws RemoteException { + final AssistStructure structure = resultData + .getParcelable(VoiceInteractionSession.KEY_STRUCTURE); + + final IBinder binder = resultData + .getBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK); + + mHandlerCaller + .obtainMessageOO(MSG_AUTO_FILL, structure, binder).sendToTarget(); + } + + }; private final IAutoFillService mInterface = new IAutoFillService.Stub() { @Override @@ -60,15 +82,8 @@ public abstract class AutoFillService extends Service { } @Override - public void newSession(String token, Bundle data, int flags, - AssistStructure structure) { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO(MSG_NEW_SESSION, - flags, token, data, structure)); - } - - @Override - public void finishSession(String token) { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_SESSION_FINISHED, token)); + public IResultReceiver getAssistReceiver() { + return mAssistReceiver; } @Override @@ -85,17 +100,11 @@ public abstract class AutoFillService extends Service { case MSG_READY: { onReady(); break; - } case MSG_NEW_SESSION: { + } case MSG_AUTO_FILL: { final SomeArgs args = (SomeArgs) msg.obj; - final int flags = args.argi1; - final String token = (String) args.arg1; - final Bundle data = (Bundle) args.arg2; - final AssistStructure assistStructure = (AssistStructure) args.arg3; - onNewSession(token, data, flags, assistStructure); - break; - } case MSG_SESSION_FINISHED: { - final String token = (String) msg.obj; - onSessionFinished(token); + final AssistStructure structure = (AssistStructure) args.arg1; + final IBinder binder = (IBinder) args.arg2; + autoFillActivity(structure, binder); break; } case MSG_SHUTDOWN: { onShutdown(); @@ -121,6 +130,7 @@ public abstract class AutoFillService extends Service { if (SERVICE_INTERFACE.equals(intent.getAction())) { return mInterface.asBinder(); } + Log.w(TAG, "Tried to bind to wrong intent: " + intent); return null; } @@ -129,41 +139,26 @@ public abstract class AutoFillService extends Service { * to receive interaction from it. * * <p>You should generally do initialization here rather than in {@link #onCreate}. - * - * <p>Sub-classes should call it first, since it sets the reference to the sytem-server service. */ - // TODO: rename to onConnected() / add onDisconnected()? + // TODO: rename to onConnect() / update javadoc public void onReady() { if (DEBUG) Log.d(TAG, "onReady()"); } /** - * Called to receive data from the application that the user was requested auto-fill for. + * Handles an auto-fill request. * - * @param token unique token identifying the auto-fill session, it should be used when providing - * the auto-filled fields. - * @param data Arbitrary data supplied by the app through - * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}. - * May be {@code null} if data has been disabled by the user or device policy. - * @param startFlags currently always 0. - * @param structure If available, the structure definition of all windows currently - * displayed by the app. May be {@code null} if auto-fill data has been disabled by the user - * or device policy; will be an empty stub if the application has disabled auto-fill - * by marking its window as secure. + * @param structure {@link Activity}'s view structure . + * @param cancellationSignal signal for observing cancel requests. + * @param callback object used to fulllfill the request. */ - @SuppressWarnings("unused") - // TODO: take the factory approach where this method return a session, and move the callback - // methods (like autofill()) to the session. - public void onNewSession(String token, Bundle data, int startFlags, AssistStructure structure) { - if (DEBUG) Log.d(TAG, "onNewSession(): token=" + token); - } + public abstract void onFillRequest(AssistStructure structure, + CancellationSignal cancellationSignal, FillCallback callback); - /** - * Called when an auto-fill session is finished. - */ - @SuppressWarnings("unused") - public void onSessionFinished(String token) { - if (DEBUG) Log.d(TAG, "onSessionFinished(): token=" + token); + private void autoFillActivity(AssistStructure structure, IBinder binder) { + final FillCallback callback = new FillCallback(binder); + // TODO: hook up the cancelationSignal + onFillRequest(structure, new CancellationSignal(), callback); } /** @@ -172,7 +167,9 @@ public abstract class AutoFillService extends Service { * * <p> At this point this service may no longer be an active {@link AutoFillService}. */ + // TODO: rename to onDisconnected() / update javadoc public void onShutdown() { if (DEBUG) Log.d(TAG, "onShutdown()"); } + } diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java new file mode 100644 index 000000000000..bdcc93b0397c --- /dev/null +++ b/core/java/android/service/autofill/FillCallback.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import static android.service.autofill.AutoFillService.DEBUG; +import static android.service.autofill.AutoFillService.TAG; + +import android.app.Activity; +import android.app.assist.AssistStructure.ViewNode; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Handles auto-fill requests from the {@link AutoFillService} into the {@link Activity} being + * auto-filled. + */ +public final class FillCallback { + + private final IAutoFillCallback mCallback; + + /** @hide */ + FillCallback(IBinder binder) { + mCallback = IAutoFillCallback.Stub.asInterface(binder); + } + + /** + * Auto-fills the {@link Activity}. + * + * @throws RuntimeException if an error occurred while auto-filling it. + */ + public void onSuccess(FillData data) { + if (DEBUG) Log.d(TAG, "onSuccess(): data=" + data); + + Preconditions.checkArgument(data != null, "data cannot be null"); + + try { + mCallback.autofill(data.asList()); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notifies the activity that the auto-fill request failed. + */ + public void onFailure(CharSequence message) { + if (DEBUG) Log.d(TAG, "onFailure(): message=" + message); + + Preconditions.checkArgument(message != null, "message cannot be null"); + + try { + mCallback.showError(message.toString()); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Data used to fill the fields of an {@link Activity}. + * + * <p>This class is immutable. + */ + public static final class FillData { + + private final List<FillableInputField> mList; + + private FillData(Builder builder) { + final int size = builder.mFields.size(); + final List<FillableInputField> list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + list.add(builder.mFields.valueAt(i)); + } + mList = Collections.unmodifiableList(list); + // TODO: use FastImmutableArraySet or a similar structure instead? + } + + /** + * Gets the response as a {@code List} so it can be used in a binder call. + */ + List<FillableInputField> asList() { + return mList; + } + + @Override + public String toString() { + return "[AutoFillResponse: " + mList + "]"; + } + + /** + * Builder for {@link FillData} objects. + * + * <p>Typical usage: + * + * <pre class="prettyprint"> + * FillCallback.FillData data = new FillCallback.FillData.Builder() + * .setTextField(id1, "value 1") + * .setTextField(id2, "value 2") + * .build() + * </pre> + */ + public static class Builder { + private final SparseArray<FillableInputField> mFields = new SparseArray<>(); + + /** + * Auto-fills a text field. + * + * @param id view id as returned by {@link ViewNode#getAutoFillId()}. + * @param text text to be auto-filled. + * @return same builder so it can be chained. + */ + public Builder setTextField(int id, String text) { + mFields.put(id, FillableInputField.forText(id, text)); + return this; + } + + /** + * Builds a new {@link FillData} instance. + */ + public FillData build() { + return new FillData(this); + } + } + } +} diff --git a/core/java/android/service/autofill/FillableInputField.java b/core/java/android/service/autofill/FillableInputField.java new file mode 100644 index 000000000000..62950b40e5e1 --- /dev/null +++ b/core/java/android/service/autofill/FillableInputField.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.app.assist.AssistStructure.ViewNode; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a view field that can be auto-filled. + * + * <p>Currently only text-fields are supported, so the value of the field can be obtained through + * {@link #getValue()}. + * + * @hide + */ +public final class FillableInputField implements Parcelable { + + private final int mId; + private final String mValue; + + private FillableInputField(int id, String value) { + mId = id; + mValue = value; + } + + private FillableInputField(Parcel parcel) { + mId = parcel.readInt(); + mValue = parcel.readString(); + } + + /** + * Gets the view id as returned by {@link ViewNode#getAutoFillId()}. + */ + public int getId() { + return mId; + } + + /** + * Gets the value of this field. + */ + public String getValue() { + return mValue; + + } + + @Override + public String toString() { + return "[AutoFillField: " + mId + "=" + mValue + "]"; + } + + /** + * Creates an {@code AutoFillField} for a text field. + * + * @param id view id as returned by {@link ViewNode#getAutoFillId()}. + * @param text value to be auto-filled. + */ + public static FillableInputField forText(int id, String text) { + return new FillableInputField(id, text); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mId); + parcel.writeString(mValue); + } + + public static final Parcelable.Creator<FillableInputField> CREATOR = + new Parcelable.Creator<FillableInputField>() { + @Override + public FillableInputField createFromParcel(Parcel source) { + return new FillableInputField(source); + } + + @Override + public FillableInputField[] newArray(int size) { + return new FillableInputField[size]; + } + }; +} diff --git a/core/java/android/service/autofill/IAutoFillCallback.aidl b/core/java/android/service/autofill/IAutoFillCallback.aidl new file mode 100644 index 000000000000..db8ef96976a2 --- /dev/null +++ b/core/java/android/service/autofill/IAutoFillCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import java.util.List; + +/** + * @hide + */ +interface IAutoFillCallback { + void autofill(in List values); + void showError(String message); +} diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl index 2c0623413be6..a91841b98453 100644 --- a/core/java/android/service/autofill/IAutoFillManagerService.aidl +++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl @@ -19,36 +19,20 @@ package android.service.autofill; import android.os.Bundle; /** - * Intermediator between apps being auto-filled and auto-fill service implementations. + * Mediator between apps being auto-filled and auto-fill service implementations. * * {@hide} */ interface IAutoFillManagerService { /** - * Starts an auto-fill session for the top activities for a given user. - * - * It's used to start a new session from system affordances. + * Request auto-fill on the top activity of a given user. * * @param userId user handle. - * @param args the bundle to pass as arguments to the voice interaction session. - * @param flags flags indicating optional session behavior. * @param activityToken optional token of activity that needs to be on top. * - * @return session token, or null if session was not created (for example, if the activity's - * user does not have an auto-fill service associated with). - */ - // TODO: pass callback providing an onAutoFill() method - String startSession(int userId, in Bundle args, int flags, IBinder activityToken); - - /** - * Finishes an auto-fill session. - * - * @param userId user handle. - * @param token session token. - * - * @return true if session existed and was finished. + * @return whether the request succeeded (for example, if the activity's + * user does not have an auto-fill service associated with, it will return false). */ - boolean finishSession(int userId, String token); - + boolean requestAutoFill(int userId, IBinder activityToken); } diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 73d8d5db5004..dca3c700c0a9 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -16,15 +16,17 @@ package android.service.autofill; -import android.os.Bundle; import android.app.assist.AssistStructure; +import android.os.Bundle; +import android.service.autofill.IAutoFillCallback; +import com.android.internal.os.IResultReceiver; /** * @hide */ -oneway interface IAutoFillService { +interface IAutoFillService { + // TODO: rename to onConnected() / onDisconnected() void ready(); - void newSession(String token, in Bundle data, int flags, in AssistStructure structure); - void finishSession(String token); void shutdown(); + IResultReceiver getAssistReceiver(); } diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index e9bbc2de26cc..12aed251904f 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -119,6 +119,8 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall public static final String KEY_CONTENT = "content"; /** @hide */ public static final String KEY_RECEIVER_EXTRAS = "receiverExtras"; + /** @hide */ + public static final String KEY_AUTO_FILL_CALLBACK = "autoFillCallback"; final Context mContext; final HandlerCaller mHandlerCaller; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 441f3302994f..02a85216cc20 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6703,6 +6703,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { structure.setId(id, null, null, null); } + + // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to + // reuse the accessibility id to save space. + structure.setAutoFillId(getAccessibilityViewId()); + structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight - mLeft, mBottom - mTop); if (!hasIdentityMatrix()) { structure.setTransformation(getMatrix()); diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 2e4ba74159f9..e9ff9d0edba6 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -275,4 +275,7 @@ public abstract class ViewStructure { /** @hide */ public abstract Rect getTempRect(); + + /** @hide */ + public abstract void setAutoFillId(int autoFillId); } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java index 3b41877bd36a..8b3775657da0 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java @@ -218,20 +218,11 @@ public final class AutoFillManagerService extends SystemService { final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub { @Override - public String startSession(int userId, Bundle args, int flags, IBinder activityToken) { + public boolean requestAutoFill(int userId, IBinder activityToken) { mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); synchronized (mLock) { - return getImplOrThrowLocked(userId).startSession(args, flags, activityToken); - } - } - - @Override - public boolean finishSession(int userId, String token) { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); - - synchronized (mLock) { - return getImplOrThrowLocked(userId).finishSessionLocked(token); + return getImplOrThrowLocked(userId).requestAutoFill(activityToken); } } @@ -244,12 +235,6 @@ public final class AutoFillManagerService extends SystemService { + ", uid=" + Binder.getCallingUid()); return; } - if (args.length > 0) { - if ("--sessions".equals(args[0])) { - dumpSessions(pw); - return; - } - } synchronized (mLock) { pw.print("mEnableService: "); pw.println(mEnableService); pw.print("mSafeMode: "); pw.println(mSafeMode); @@ -268,20 +253,6 @@ public final class AutoFillManagerService extends SystemService { } } - private void dumpSessions(PrintWriter pw) { - boolean foundOne = false; - synchronized (mLock) { - final int size = mImplByUser.size(); - for (int i = 0; i < size; i++) { - final AutoFillManagerServiceImpl impl = mImplByUser.valueAt(i); - foundOne |= impl.dumpSessionsLocked("", pw); - } - } - if (!foundOne) { - pw.println("No active sessions"); - } - } - @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java index c780062c567c..ae687da43cd0 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java @@ -16,7 +16,9 @@ package com.android.server.autofill; +import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -24,6 +26,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.icu.text.DateFormat; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -41,16 +44,14 @@ import com.android.server.LocalServices; import com.android.server.autofill.AutoFillManagerService.AutoFillManagerServiceStub; import java.io.PrintWriter; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.Date; import java.util.List; -import java.util.Map; -import java.util.UUID; /** * Bridge between the {@code system_server}'s {@link AutoFillManagerService} and the * app's {@link IAutoFillService} implementation. * - * <p>It keeps a list of auto-fill sessions for a specifc user. */ final class AutoFillManagerServiceImpl { @@ -61,13 +62,15 @@ final class AutoFillManagerServiceImpl { final ComponentName mComponent; private final Context mContext; + private final IActivityManager mAm; private final Object mLock; private final AutoFillManagerServiceStub mServiceStub; private final AutoFillServiceInfo mInfo; - // Map of sessions keyed by session tokens. - @GuardedBy("mLock") - private Map<String, AutoFillSession> mSessions = new HashMap<>(); + // TODO: improve its usage + // - set maximum number of entries + // - disable on low-memory devices. + private final List<String> mRequestHistory = new ArrayList<>(); private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -75,7 +78,7 @@ final class AutoFillManagerServiceImpl { if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { final String reason = intent.getStringExtra("reason"); if (DEBUG) Slog.d(TAG, "close system dialogs: " + reason); - // TODO: close any pending UI like account selection + // TODO: close any pending UI like account selection (or remove this receiver) } } }; @@ -113,8 +116,9 @@ final class AutoFillManagerServiceImpl { mServiceStub = stub; mUser = user; mComponent = component; + mAm = ActivityManager.getService(); - AutoFillServiceInfo info; + final AutoFillServiceInfo info; try { info = new AutoFillServiceInfo(component, mUser); } catch (PackageManager.NameNotFoundException e) { @@ -150,19 +154,15 @@ final class AutoFillManagerServiceImpl { if (DEBUG) Slog.d(TAG, "Bound to " + mComponent); } - String startSession(Bundle args, int flags, IBinder activityToken) { - + boolean requestAutoFill(IBinder activityToken) { if (!mBound) { // TODO: should it bind on demand? Or perhaps always run when on on low-memory? - Slog.w(TAG, "startSession() failed because it's not bound to service"); - return null; + Slog.w(TAG, "requestAutoFill() failed because it's not bound to service"); + return false; } - // TODO: session should have activity ids, so same session is reused when called again - // for the same activity. - // TODO: activityToken should probably not be null, but we need to wait until the UI is - // triggering the call (for now it's trough 'adb shell cmd autofill start session' + // triggering the call (for now it's trough 'adb shell cmd autofill request' if (activityToken == null) { // Let's get top activities from all visible stacks. @@ -175,40 +175,43 @@ final class AutoFillManagerServiceImpl { Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities); if (topActivities.isEmpty()) { Slog.w(TAG, "Could not get top activity"); - return null; + return false; } activityToken = topActivities.get(0); } synchronized (mLock) { - return startSessionLocked(args, flags, activityToken); + return requestAutoFillLocked(activityToken); } } - // TODO: remove args and flags if not needed? - private String startSessionLocked(Bundle args, int flags, IBinder activityToken) { - - final String sessionToken = UUID.randomUUID().toString(); - - if (DEBUG) Slog.d(TAG, "Starting session for user " + mUser - + ": sessionToken=" + sessionToken + ", activityToken=" + activityToken); + private boolean requestAutoFillLocked(IBinder activityToken) { + mRequestHistory.add( + DateFormat.getDateTimeInstance().format(new Date()) + " - " + activityToken); + if (DEBUG) Slog.d(TAG, "Requesting for user " + mUser + " and activity " + activityToken); - final AutoFillSession session = - new AutoFillSession(mService, mLock, sessionToken, activityToken); - session.startLocked(); - mSessions.put(sessionToken, session); - - return sessionToken; - } + // Sanity check + if (mService == null) { + Slog.w(TAG, "requestAutoFillLocked(: service is null"); + return false; + } - // TODO: need a way to automatically call it when the activity is destroyed. - boolean finishSessionLocked(String token) { - if (DEBUG) Slog.d(TAG, "Removing session " + token + " for user " + mUser); - final AutoFillSession session = mSessions.remove(token); - if (session != null) { - session.finishLocked(); + /* + * TODO: apply security checks below: + * - checks if disabled by secure settings / device policy + * - log operation using noteOp() + * - check flags + * - display disclosure if needed + */ + try { + // TODO: add MetricsLogger call + if (!mAm.requestAutoFillData(mService.getAssistReceiver(), null, activityToken)) { + return false; + } + } catch (RemoteException e) { + // Should happen, it's a local call. } - return session != null; + return true; } void shutdownLocked() { @@ -253,23 +256,15 @@ final class AutoFillManagerServiceImpl { mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix); } - if (!dumpSessionsLocked(prefix, pw)) { - pw.print(prefix); pw.print("No active sessions for user "); pw.println(mUser); - } - } - - boolean dumpSessionsLocked(String prefix, PrintWriter pw) { - if (mSessions.isEmpty()) { - return false; - } - - pw.print(mSessions.size());pw.println(" active sessions:"); - final String sessionPrefix = prefix + prefix; - for (AutoFillSession session : mSessions.values()) { - pw.println(); - session.dumpLocked(sessionPrefix, pw); + if (mRequestHistory.isEmpty()) { + pw.print(prefix); pw.println("No history"); + } else { + pw.print(prefix); pw.println("History:"); + final String prefix2 = prefix + prefix; + for (int i = 0; i < mRequestHistory.size(); i++) { + pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mRequestHistory.get(i)); + } } - return true; } @Override diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java index 4e08ed6c7653..c9037fc9df90 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java @@ -40,10 +40,8 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand { final PrintWriter pw = getOutPrintWriter(); try { switch (cmd) { - case "start": - return runStart(pw); - case "finish": - return runFinish(pw); + case "request": + return requestAutoFill(); default: return handleDefaultCommands(cmd); } @@ -60,62 +58,16 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Prints this help text."); pw.println(""); - pw.println(" start session [--user USER_ID]"); - pw.println(" Starts an auto-fill session. " - + "Prints 'token:SESSION_TOKEN if successful, or error message"); + pw.println(" request [--user USER_ID]"); + pw.println(" Request auto-fill on the top activity. "); pw.println(""); - pw.println(" finish session <TOKEN> [--user USER_ID]"); - pw.println(" Finishes a session with the given TOKEN. " - + "Prints empty string if successful, or error message."); - pw.println(""); - } - } - - private int runStart(PrintWriter pw) throws RemoteException { - final String type = getNextArg(); - if (type == null) { - pw.println("Error: didn't specify type of data to start"); - return -1; - } - switch (type) { - case "session": - return startAutoFillSession(pw); - } - pw.println("Error: unknown start type '" + type + "'"); - return -1; - } - - private int runFinish(PrintWriter pw) throws RemoteException { - final String type = getNextArg(); - if (type == null) { - pw.println("Error: didn't specify type of data to finish"); - return -1; - } - switch (type) { - case "session": - return finishAutoFillSession(pw); } - pw.println("Error: unknown finish type '" + type + "'"); - return -1; - } - - private int startAutoFillSession(PrintWriter pw) throws RemoteException { - final int userId = getUserIdFromArgs(); - final String token = mService.startSession(userId, null, 0, null); - pw.print("token:"); pw.println(token); - return 0; } - private int finishAutoFillSession(PrintWriter pw) throws RemoteException { - final String token = getNextArgRequired(); + private int requestAutoFill() throws RemoteException { final int userId = getUserIdFromArgs(); - - boolean finished = mService.finishSession(userId, token); - if (!finished) { - pw.println("No such session"); - return 1; - } - return 0; + final boolean ok = mService.requestAutoFill(userId, null); + return ok ? 0 : 1; } private int getUserIdFromArgs() { diff --git a/services/autofill/java/com/android/server/autofill/AutoFillSession.java b/services/autofill/java/com/android/server/autofill/AutoFillSession.java deleted file mode 100644 index 44637c3f5f46..000000000000 --- a/services/autofill/java/com/android/server/autofill/AutoFillSession.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.autofill; - -import android.app.ActivityManager; -import android.app.ActivityManagerNative; -import android.app.IActivityManager; -import android.app.assist.AssistStructure; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.service.autofill.AutoFillService; -import android.service.autofill.IAutoFillService; -import android.service.voice.VoiceInteractionSession; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.os.IResultReceiver; - -import java.io.PrintWriter; - -/** - * An auto-fill session between the system's {@link AutoFillManagerServiceImpl} and the provider's - * {@link AutoFillService} implementation. - */ -final class AutoFillSession { - - private static final String TAG = "AutoFillSession"; - - private static final boolean FOCUSED = true; - private static final boolean NEW_SESSION_ID = true; - - private final IAutoFillService mService; - private final String mSessionToken; - private final IBinder mActivityToken; - private final Object mLock; - private final IActivityManager mAm; - - private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { - @Override - public void send(int resultCode, Bundle resultData) throws RemoteException { - synchronized (mLock) { - mPendingResponse = false; - mAssistResponse = resultData; - deliverSessionDataLocked(); - } - } - }; - - // Assist data is filled asynchronously. - @GuardedBy("mLock") - private Bundle mAssistResponse; - @GuardedBy("mLock") - private boolean mPendingResponse; - - AutoFillSession(IAutoFillService service, Object lock, String sessionToken, - IBinder activityToken) { - mService = service; - mSessionToken = sessionToken; - mActivityToken = activityToken; - mLock = lock; - mAm = ActivityManagerNative.getDefault(); - } - - void startLocked() { - /* - * TODO: apply security checks below: - * - checks if disabled by secure settings / device policy - * - log operation using noteOp() - * - check flags - * - display disclosure if needed - */ - mAssistResponse = null; - mPendingResponse = true; - try { - // TODO: add MetricsLogger call - if (!mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL, - mAssistReceiver, (Bundle) null, mActivityToken, FOCUSED, NEW_SESSION_ID)) { - mPendingResponse = false; - Slog.w(TAG, "requestAssistContextExtras() rejected"); - } - } catch (RemoteException e) { - // Should happen, it's a local call. - } - } - - void finishLocked() { - try { - mService.finishSession(mSessionToken); - } catch (RemoteException e) { - Slog.e(TAG, "auto-fill service failed to finish session " + mSessionToken, e); - } - } - - private void deliverSessionDataLocked() { - if (mAssistResponse == null) { - Slog.w(TAG, "No assist data for session " + mSessionToken); - return; - } - - final Bundle assistData = mAssistResponse.getBundle(VoiceInteractionSession.KEY_DATA); - final AssistStructure structure = - mAssistResponse.getParcelable(VoiceInteractionSession.KEY_STRUCTURE); - try { - mService.newSession(mSessionToken, assistData, 0, structure); - } catch (RemoteException e) { - Slog.e(TAG, "auto-fill service failed to start session " + mSessionToken, e); - } finally { - mPendingResponse = false; - // We could set mAssistResponse to null here, but we don't so it's shown on dump() - } - } - - void dumpLocked(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mSessionToken="); pw.println(mSessionToken); - pw.print(prefix); pw.print("mActivityToken="); pw.println(mActivityToken); - pw.print(prefix); pw.print("mPendingResponse="); pw.println(mPendingResponse); - pw.print(prefix); pw.print("mAssistResponse="); pw.println(mAssistResponse); - } - -} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index d60f1159bef0..e080fd971da1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12142,6 +12142,15 @@ public class ActivityManagerService extends IActivityManager.Stub != null; } + @Override + public boolean requestAutoFillData(IResultReceiver receiver, Bundle receiverExtras, + IBinder activityToken) { + return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null, receiver, + receiverExtras, activityToken, true, true, + UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT) + != null; + } + private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint, IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout) { @@ -12266,6 +12275,12 @@ public class ActivityManagerService extends IActivityManager.Stub sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content); sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS, pae.receiverExtras); + IBinder autoFillCallback = + extras.getBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK); + if (autoFillCallback != null) { + sendBundle.putBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK, + autoFillCallback); + } } } if (sendReceiver != null) { |