diff options
| author | 2016-10-25 14:57:11 -0700 | |
|---|---|---|
| committer | 2016-11-11 16:02:55 -0800 | |
| commit | 29a5b0d0f1cc7fd6cbfe97c816b8a687d9e438cc (patch) | |
| tree | 25f9c559ec8ae1ee3c9ecab0a9dc1982d5b50e8d | |
| parent | 7519e166983e48988957e67679ef79b9661a2b34 (diff) | |
Added a callback for AutoFillService.
So far AutoFillService only received the assist data from framework; in
this CL, it also offers a method where the auto-fill provider can send
the auto-fill data back to framework.
The workflow is:
- AFMSI calls a new AM method (requestAutoFillData(), instead
  of requestAssistContextExtras()).
- The assist receiver is located in the app, not on system service.
- AM uses a new request type (ASSIST_CONTEXT_AUTOFILL) to request the
  assist data to the activity.
- ViewStructure has a new setAutoFillId() method which is used to set an
  unique id for the view.
- View uses the accessibility id to implement the auto-fill id.
- When the activity fullfills the request, it creates an IAutoFillCallback
  remote object - that will be used to set the auto-fill fields - and
  returns it in the assist bundle (using the
  VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK key).
- The app-visible AutoFillService class offers an onFillRequest() method,
  which contains the assist data and a FillCallback used to handle it.
BUG: 31001899
Test: manually built and ran it
Change-Id: I3d208c14e81022dc96dd03f38bbe25a778b24a67
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) { |