method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
- method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
}
method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String);
}
+ public final class SaveCallback {
+ method public void onFailure(java.lang.CharSequence);
+ method public void onSuccess(int[]);
+ }
+
}
package android.service.carrier {
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public void dispatchProvideStructure(android.view.ViewStructure);
+ method public deprecated void dispatchProvideStructure(android.view.ViewStructure);
+ method public void dispatchProvideStructure(android.view.ViewStructure, int);
method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSetActivated(boolean);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public void onProvideStructure(android.view.ViewStructure);
- method public void onProvideVirtualStructure(android.view.ViewStructure);
+ method public deprecated void onProvideStructure(android.view.ViewStructure);
+ method public void onProvideStructure(android.view.ViewStructure, int);
+ method public deprecated void onProvideVirtualStructure(android.view.ViewStructure);
+ method public void onProvideVirtualStructure(android.view.ViewStructure, int);
method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
method protected void onRestoreInstanceState(android.os.Parcelable);
method public void onRtlPropertiesChanged(int);
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2
+ field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
- method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
}
method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String);
}
+ public final class SaveCallback {
+ method public void onFailure(java.lang.CharSequence);
+ method public void onSuccess(int[]);
+ }
+
}
package android.service.carrier {
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public void dispatchProvideStructure(android.view.ViewStructure);
+ method public deprecated void dispatchProvideStructure(android.view.ViewStructure);
+ method public void dispatchProvideStructure(android.view.ViewStructure, int);
method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSetActivated(boolean);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public void onProvideStructure(android.view.ViewStructure);
- method public void onProvideVirtualStructure(android.view.ViewStructure);
+ method public deprecated void onProvideStructure(android.view.ViewStructure);
+ method public void onProvideStructure(android.view.ViewStructure, int);
+ method public deprecated void onProvideVirtualStructure(android.view.ViewStructure);
+ method public void onProvideVirtualStructure(android.view.ViewStructure, int);
method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
method protected void onRestoreInstanceState(android.os.Parcelable);
method public void onRtlPropertiesChanged(int);
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2
+ field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
method public abstract void onMeasure(int, int);
method public abstract void onOverScrolled(int, int, boolean, boolean);
method public abstract void onProvideVirtualStructure(android.view.ViewStructure);
+ method public default void onProvideVirtualStructure(android.view.ViewStructure, int);
method public abstract void onScrollChanged(int, int, int, int);
method public abstract void onSizeChanged(int, int, int, int);
method public abstract void onStartTemporaryDetach();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
- method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
}
method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String);
}
+ public final class SaveCallback {
+ method public void onFailure(java.lang.CharSequence);
+ method public void onSuccess(int[]);
+ }
+
}
package android.service.carrier {
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public void dispatchProvideStructure(android.view.ViewStructure);
+ method public deprecated void dispatchProvideStructure(android.view.ViewStructure);
+ method public void dispatchProvideStructure(android.view.ViewStructure, int);
method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSetActivated(boolean);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
- method public void onProvideStructure(android.view.ViewStructure);
- method public void onProvideVirtualStructure(android.view.ViewStructure);
+ method public deprecated void onProvideStructure(android.view.ViewStructure);
+ method public void onProvideStructure(android.view.ViewStructure, int);
+ method public deprecated void onProvideVirtualStructure(android.view.ViewStructure);
+ method public void onProvideVirtualStructure(android.view.ViewStructure, int);
method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
method protected void onRestoreInstanceState(android.os.Parcelable);
method public void onRtlPropertiesChanged(int);
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2
+ field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
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:
+ // TODO(b/33197203): 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)) {
@Override
public void showError(String message) {
runOnUiThread(() -> {
- // TODO: temporary show a toast until it uses the Snack bar.
+ // TODO(b/33197203): temporary show a toast until it uses the Snack bar.
Toast.makeText(Activity.this, "Auto-fill request failed: " + message,
Toast.LENGTH_LONG).show();
});
/** @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;
import android.provider.Settings;
import android.security.NetworkSecurityPolicy;
import android.security.net.config.NetworkSecurityConfigProvider;
+import android.service.autofill.AutoFillService;
import android.service.autofill.IAutoFillCallback;
import android.service.voice.VoiceInteractionSession;
import android.util.AndroidRuntimeException;
IBinder requestToken;
int requestType;
int sessionId;
+ int flags;
}
static final class ActivityConfigChangeData {
@Override
public void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
- int requestType, int sessionId) {
+ int requestType, int sessionId, int flags) {
RequestAssistContextExtras cmd = new RequestAssistContextExtras();
cmd.activityToken = activityToken;
cmd.requestToken = requestToken;
cmd.requestType = requestType;
cmd.sessionId = sessionId;
+ cmd.flags = flags;
sendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd);
}
}
public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
+ // Filling for auto-fill has a few differences:
+ // - it does not need an AssistContent
+ // - it does not call onProvideAssistData()
+ // - it needs an IAutoFillCallback
+ // - it sets the flags so views can provide autofill-specific data (such as passwords)
+ boolean forAutoFill = (cmd.flags
+ & (View.ASSIST_FLAG_SANITIZED_TEXT
+ | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+
+ // TODO(b/33197203): decide if lastSessionId logic applies to auto-fill sessions
if (mLastSessionId != cmd.sessionId) {
// Clear the existing structures
mLastSessionId = cmd.sessionId;
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 = forAutofill ? null : 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);
- if (!forAutofill) {
+ if (!forAutoFill) {
+ r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
r.activity.onProvideAssistData(data);
}
referrer = r.activity.onProvideReferrer();
- if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutofill) {
- structure = new AssistStructure(r.activity);
+ if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutoFill) {
+ structure = new AssistStructure(r.activity, cmd.flags);
Intent activityIntent = r.activity.getIntent();
+ if (cmd.flags > 0) {
+ data.putInt(VoiceInteractionSession.KEY_FLAGS, cmd.flags);
+ }
+ // TODO(b/33197203): re-evaluate conditions below for auto-fill. In particular,
+ // FLAG_SECURE might be allowed on AUTO_FILL but not on AUTO_FILL_SAVE)
if (activityIntent != null && (r.window == null ||
(r.window.getAttributes().flags
& WindowManager.LayoutParams.FLAG_SECURE) == 0)) {
- Intent intent = new Intent(activityIntent);
- intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
- intent.removeUnsafeExtras();
- if (forAutofill) {
+ if (forAutoFill) {
IAutoFillCallback autoFillCallback = r.activity.getAutoFillCallback();
- data.putBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK,
- autoFillCallback.asBinder());
+ data.putBinder(AutoFillService.KEY_CALLBACK, autoFillCallback.asBinder());
} else {
+ Intent intent = new Intent(activityIntent);
+ intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+ intent.removeUnsafeExtras();
content.setDefaultIntent(intent);
}
} else {
- if (!forAutofill) {
+ if (!forAutoFill) {
content.setDefaultIntent(new Intent());
}
}
- if (!forAutofill) {
+ if (!forAutoFill) {
r.activity.onProvideAssistContent(content);
}
}
if (structure == null) {
structure = new AssistStructure();
}
+ // TODO(b/33197203): decide if lastSessionId logic applies to auto-fill sessions
mLastAssistStructures.add(new WeakReference<>(structure));
IActivityManager mgr = ActivityManager.getService();
try {
void unregisterTaskStackListener(ITaskStackListener listener);
void moveStackToDisplay(int stackId, int displayId);
boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras,
- in IBinder activityToken);
+ in IBinder activityToken, int flags);
void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
// WARNING: when these transactions are updated, check if they are any callers on the native
void dumpDbInfo(in ParcelFileDescriptor fd, in String[] args);
void unstableProviderDied(IBinder provider);
void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
- int requestType, int sessionId);
+ int requestType, int sessionId, int flags);
void scheduleTranslucentConversionComplete(IBinder token, boolean timeout);
void setProcessState(int state);
void scheduleInstallProvider(in ProviderInfo provider);
final int mDisplayId;
final ViewNode mRoot;
- WindowNode(AssistStructure assist, ViewRootImpl root) {
+ WindowNode(AssistStructure assist, ViewRootImpl root, int flags) {
View view = root.getView();
Rect rect = new Rect();
view.getBoundsOnScreen(rect);
if ((root.getWindowFlags()& WindowManager.LayoutParams.FLAG_SECURE) != 0) {
// This is a secure window, so it doesn't want a screenshot, and that
// means we should also not copy out its view hierarchy.
- view.onProvideStructure(builder);
+
+ // Must explicitly set which method to calls since View subclasses might
+ // have implemented the deprecated method.
+ if (flags == 0) {
+ view.onProvideStructure(builder);
+ } else {
+ view.onProvideStructure(builder, flags);
+ }
builder.setAssistBlocked(true);
return;
}
- view.dispatchProvideStructure(builder);
+ if (flags == 0) {
+ view.dispatchProvideStructure(builder);
+ } else {
+ view.dispatchProvideStructure(builder, flags);
+ }
}
WindowNode(ParcelTransferReader reader) {
}
/** @hide */
- public AssistStructure(Activity activity) {
+ public AssistStructure(Activity activity, int flags) {
mHaveData = true;
mActivityComponent = activity.getComponentName();
ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
activity.getActivityToken());
for (int i=0; i<views.size(); i++) {
ViewRootImpl root = views.get(i);
- mWindowNodes.add(new WindowNode(this, root));
+ mWindowNodes.add(new WindowNode(this, root, flags));
}
}
*/
package android.service.autofill;
+import static android.service.voice.VoiceInteractionSession.KEY_FLAGS;
+import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
+import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT;
+import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT;
+
import android.annotation.SdkConstant;
import android.app.Activity;
import android.app.Service;
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;
+// TODO(b/33197203): improve javadoc (class and methods)
+
/**
* Top-level service of the current auto-fill service for a given user.
*
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
+ // Bundle keys.
+ /** @hide */
+ public static final String KEY_CALLBACK = "callback";
+
+ // Handler messages.
private static final int MSG_CONNECT = 1;
private static final int MSG_AUTO_FILL_ACTIVITY = 2;
private static final int MSG_DISCONNECT = 3;
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);
+ final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
+ final IBinder binder = resultData.getBinder(KEY_CALLBACK);
+ final int flags = resultData.getInt(KEY_FLAGS, 0);
mHandlerCaller
- .obtainMessageOO(MSG_AUTO_FILL_ACTIVITY, structure, binder).sendToTarget();
+ .obtainMessageIOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, binder).sendToTarget();
}
};
final SomeArgs args = (SomeArgs) msg.obj;
final AssistStructure structure = (AssistStructure) args.arg1;
final IBinder binder = (IBinder) args.arg2;
- requestAutoFill(structure, binder);
+ final int flags = msg.arg1;
+ requestAutoFill(structure, flags, binder);
break;
} case MSG_DISCONNECT: {
onDisconnected();
}
/**
- * Handles an auto-fill request.
+ * Called when user requests service to auto-fill an {@link Activity}.
*
* @param structure {@link Activity}'s view structure .
+ * @param data bundle with optional parameters (currently none) which is passed along on
+ * subsequent calls (so it can be used by the service to share data).
* @param cancellationSignal signal for observing cancel requests.
- * @param callback object used to fulllfill the request.
*/
public abstract void onFillRequest(AssistStructure structure,
- CancellationSignal cancellationSignal, FillCallback callback);
+ Bundle data, CancellationSignal cancellationSignal, FillCallback callback);
- private void requestAutoFill(AssistStructure structure, IBinder binder) {
- final FillCallback callback = new FillCallback(binder);
- // TODO: hook up the cancelationSignal
- onFillRequest(structure, new CancellationSignal(), callback);
+ /**
+ * Called when user requests service to save the fields of an {@link Activity}.
+ *
+ * @param structure {@link Activity}'s view structure.
+ * @param data same bundle passed to
+ * {@link #onFillRequest(AssistStructure, Bundle, CancellationSignal, FillCallback)};
+ * might also contain with optional parameters (currently none).
+ * @param cancellationSignal signal for observing cancel requests.
+ * @param callback object used to notify the result of the request.
+ */
+ public abstract void onSaveRequest(AssistStructure structure,
+ Bundle data, CancellationSignal cancellationSignal, SaveCallback callback);
+
+ private void requestAutoFill(AssistStructure structure, int flags, IBinder binder) {
+ // TODO(b/33197203): pass the Bundle received from mAssistReceiver instead?
+ final Bundle data = new Bundle();
+ switch (flags) {
+ case ASSIST_FLAG_SANITIZED_TEXT:
+ final FillCallback fillCallback = new FillCallback(binder);
+ // TODO(b/33197203): hook up the cancelationSignal
+ onFillRequest(structure, data, new CancellationSignal(), fillCallback);
+ break;
+ case ASSIST_FLAG_NON_SANITIZED_TEXT:
+ final SaveCallback saveCallback = new SaveCallback(binder);
+ // TODO(b/33197203): hook up the cancelationSignal
+ onSaveRequest(structure, null, new CancellationSignal(), saveCallback);
+ break;
+ default:
+ Log.w(TAG, "invalid flag on requestAutoFill(): " + flags);
+ }
}
/**
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;
*/
public final class FillCallback {
+ private static final String TAG = "FillCallback";
+
private final IAutoFillCallback mCallback;
/** @hide */
}
}
+ /**
+ * Notifies the {@link Activity} that the auto-fill request failed.
+ *
+ * @param message error message to be displayed.
+ *
+ * @throws RuntimeException if an error occurred while notifying the activity.
+ */
public void onFailure(CharSequence message) {
if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
*/
oneway interface IAutoFillManagerService {
- /**
- * Request auto-fill on the top activity of a given user.
- *
- * @param userId user handle.
- * @param activityToken optional token of activity that needs to be on top.
- */
- void requestAutoFill(int userId, IBinder activityToken);
+ void requestAutoFill(IBinder activityToken, int userId, int flags);
}
--- /dev/null
+/*
+ * 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 android.app.Activity;
+import android.app.assist.AssistStructure.ViewNode;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Handles save requests from the {@link AutoFillService} into the {@link Activity} being
+ * auto-filled.
+ */
+public final class SaveCallback {
+
+ private static final String TAG = "SaveCallback";
+
+ private final IAutoFillCallback mCallback;
+
+ /** @hide */
+ SaveCallback(IBinder binder) {
+ mCallback = IAutoFillCallback.Stub.asInterface(binder);
+ }
+
+ /**
+ * Notifies the {@link Activity} that the save request succeeded.
+ *
+ * @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved.
+ *
+ * @throws RuntimeException if an error occurred while saving the data.
+ */
+ public void onSuccess(int[] ids) {
+ Preconditions.checkArgument(ids != null, "ids cannot be null");
+
+ Preconditions.checkArgument(ids.length > 0, "ids cannot be empty");
+
+ if (DEBUG) Log.d(TAG, "onSuccess(): ids=" + ids.length);
+
+ // TODO(b/33197203): display which ids were saved
+ }
+
+ /**
+ * Notifies the {@link Activity} that the save request failed.
+ *
+ * @param message error message to be displayed.
+ *
+ * @throws RuntimeException if an error occurred while notifying the activity.
+ */
+ 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();
+ }
+ }
+}
/** @hide */
public static final String KEY_RECEIVER_EXTRAS = "receiverExtras";
/** @hide */
- public static final String KEY_AUTO_FILL_CALLBACK = "autoFillCallback";
+ public static final String KEY_FLAGS = "flags";
final Context mContext;
final HandlerCaller mHandlerCaller;
int mLayerType = LAYER_TYPE_NONE;
Paint mLayerPaint;
+
+ /**
+ * <p>When setting a {@link android.app.assist.AssistStructure}, its nodes should not contain
+ * PII (Personally Identifiable Information).
+ */
+ // TODO(b/33197203) (b/33269702): improve documentation: mention all cases, show examples, etc.
+ public static final int ASSIST_FLAG_SANITIZED_TEXT = 0x1;
+
+ /**
+ * <p>When setting a {@link android.app.assist.AssistStructure}, its nodes should contain all
+ * type of data, even sensitive PII (Personally Identifiable Information) like passwords or
+ * credit card numbers.
+ */
+ public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 0x2;
+
/**
* Set to true when drawing cache is enabled and cannot be created.
*
* {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
* @param structure Fill in with structured view data. The default implementation
* fills in all data that can be inferred from the view itself.
+ *
+ * @deprecated As of API O sub-classes should override
+ * {@link #onProvideStructure(ViewStructure, int)} instead.
*/
+ // TODO(b/33197203): set proper API above
+ @Deprecated
public void onProvideStructure(ViewStructure structure) {
+ onProvideStructure(structure, 0);
+ }
+
+ /**
+ * Called when assist structure is being retrieved from a view as part of
+ * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} or as part
+ * of an auto-fill request.
+ *
+ * <p>The default implementation fills in all data that can be inferred from the view itself.
+ *
+ * <p>The structure must be filled according to the request type, which is set in the
+ * {@code flags} parameter - see the documentation on each flag for more details.
+ *
+ * @param structure Fill in with structured view data. The default implementation
+ * fills in all data that can be inferred from the view itself.
+ * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and
+ * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info).
+ */
+ public void onProvideStructure(ViewStructure structure, int flags) {
+ boolean forAutoFill = (flags
+ & (View.ASSIST_FLAG_SANITIZED_TEXT
+ | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
final int id = mID;
if (id > 0 && (id&0xff000000) != 0 && (id&0x00ff0000) != 0
&& (id&0x0000ffff) != 0) {
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());
+ if (forAutoFill) {
+ // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
+ // reuse the accessibility id to save space.
+ structure.setAutoFillId(getAccessibilityViewId());
+ }
structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight - mLeft, mBottom - mTop);
if (!hasIdentityMatrix()) {
* uses {@link #getAccessibilityNodeProvider()} to try to generate this from the
* view's virtual accessibility nodes, if any. You can override this for a more
* optimal implementation providing this data.
+ *
+ * @deprecated As of API O, sub-classes should override
+ * {@link #onProvideVirtualStructure(ViewStructure, int)} instead.
*/
+ // TODO(b/33197203): set proper API above
+ @Deprecated
public void onProvideVirtualStructure(ViewStructure structure) {
+ onProvideVirtualStructure(structure, 0);
+ }
+
+ /**
+ * Called when assist structure is being retrieved from a view as part of
+ * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} or as part
+ * of an auto-fill request to generate additional virtual structure under this view.
+ *
+ * <p>The defaullt implementation uses {@link #getAccessibilityNodeProvider()} to try to
+ * generate this from the view's virtual accessibility nodes, if any. You can override this
+ * for a more optimal implementation providing this data.
+ *
+ * <p>The structure must be filled according to the request type, which is set in the
+ * {@code flags} parameter - see the documentation on each flag for more details.
+ *
+ * @param structure Fill in with structured view data.
+ * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and
+ * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info).
+ */
+ public void onProvideVirtualStructure(ViewStructure structure, int flags) {
+ boolean sanitize = (flags & View.ASSIST_FLAG_SANITIZED_TEXT) != 0;
+
+ if (sanitize) {
+ // TODO(b/33197203): change populateVirtualStructure so it sanitizes data in this case.
+ return;
+ }
+
AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
if (provider != null) {
AccessibilityNodeInfo info = createAccessibilityNodeInfo();
structure.setChildCount(1);
ViewStructure root = structure.newChild(0);
- populateVirtualStructure(root, provider, info);
+ populateVirtualStructure(root, provider, info, flags);
info.recycle();
}
}
private void populateVirtualStructure(ViewStructure structure,
- AccessibilityNodeProvider provider, AccessibilityNodeInfo info) {
+ AccessibilityNodeProvider provider, AccessibilityNodeInfo info, int flags) {
structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
null, null, null);
Rect rect = structure.getTempRect();
AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo(
AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
ViewStructure child = structure.newChild(i);
- populateVirtualStructure(child, provider, cinfo);
+ populateVirtualStructure(child, provider, cinfo, flags);
cinfo.recycle();
}
}
* Dispatch creation of {@link ViewStructure} down the hierarchy. The default
* implementation calls {@link #onProvideStructure} and
* {@link #onProvideVirtualStructure}.
+ *
+ * @deprecated As of API O, sub-classes should override
+ * {@link #dispatchProvideStructure(ViewStructure, int)} instead.
*/
+ // TODO(b/33197203): set proper API above
+ @Deprecated
public void dispatchProvideStructure(ViewStructure structure) {
- if (!isAssistBlocked()) {
- onProvideStructure(structure);
- onProvideVirtualStructure(structure);
+ dispatchProvideStructure(structure, 0);
+ }
+
+ /**
+ * Dispatch creation of {@link ViewStructure} down the hierarchy.
+ *
+ * <p>The structure must be filled according to the request type, which is set in the
+ * {@code flags} parameter - see the documentation on each flag for more details.
+ *
+ * <p>The default implementation calls {@link #onProvideStructure(ViewStructure, int)} and
+ * {@link #onProvideVirtualStructure(ViewStructure, int)}.
+ *
+ * @param structure Fill in with structured view data.
+ * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and
+ * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info).
+ */
+ public void dispatchProvideStructure(ViewStructure structure, int flags) {
+ boolean forAutoFill = (flags
+ & (View.ASSIST_FLAG_SANITIZED_TEXT
+ | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+
+ boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked();
+ if (!blocked) {
+ onProvideStructure(structure, flags);
+ onProvideVirtualStructure(structure, flags);
} else {
structure.setClassName(getAccessibilityClassName().toString());
structure.setAssistBlocked(true);
/**
* @hide
+ * Indicates whether this view will participate in data collection through
+ * {@link ViewStructure} for auto-fill purposes.
+ *
+ * <p>If {@code true}, it will not provide any data for itself or its children.
+ * <p>If {@code false}, the normal data collection will be allowed.
+ *
+ * @return Returns {@code false} if assist data collection for auto-fill is not blocked,
+ * else {@code true}.
+ *
+ * TODO(b/33197203): update / remove javadoc tags below
+ * @see #setAssistBlocked(boolean)
+ * @attr ref android.R.styleable#View_assistBlocked
+ */
+ public boolean isAutoFillBlocked() {
+ return false; // TODO(b/33197203): properly implement it
+ }
+
+ /**
+ * @hide
* Controls whether assist data collection from this view and its children is enabled
* (that is, whether {@link #onProvideStructure} and
* {@link #onProvideVirtualStructure} will be called). The default value is false,
}
/**
- * Dispatch creation of {@link ViewStructure} down the hierarchy. This implementation
- * adds in all child views of the view group, in addition to calling the default View
- * implementation.
+ * {@inheritDoc}
+ *
+ * <p>This implementation adds in all child views of the view group, in addition to calling the
+ * default {@link View} implementation.
*/
@Override
- public void dispatchProvideStructure(ViewStructure structure) {
- super.dispatchProvideStructure(structure);
- if (!isAssistBlocked()) {
+ public void dispatchProvideStructure(ViewStructure structure, int flags) {
+ super.dispatchProvideStructure(structure, flags);
+
+ boolean forAutoFill = (flags
+ & (View.ASSIST_FLAG_SANITIZED_TEXT
+ | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0;
+
+ boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked();
+
+ if (!blocked) {
if (structure.getChildCount() == 0) {
final int childrenCount = getChildCount();
if (childrenCount > 0) {
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
final ViewStructure cstructure = structure.newChild(i);
- child.dispatchProvideStructure(cstructure);
+
+ // Must explicitly check which recursive method to call because child might
+ // not be overriding the new, flags-based version
+ if (flags == 0) {
+ child.dispatchProvideStructure(cstructure);
+ } else {
+ child.dispatchProvideStructure(cstructure, flags);
+ }
}
if (preorderedList != null) preorderedList.clear();
}
return mProvider.getViewDelegate().shouldDelayChildPressedState();
}
+ @Override
public CharSequence getAccessibilityClassName() {
return WebView.class.getName();
}
mProvider.getViewDelegate().onProvideVirtualStructure(structure);
}
+ @Override
+ public void onProvideVirtualStructure(ViewStructure structure, int flags) {
+ mProvider.getViewDelegate().onProvideVirtualStructure(structure, flags);
+ }
+
/** @hide */
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
public void onProvideVirtualStructure(android.view.ViewStructure structure);
+ @SuppressWarnings("unused")
+ public default void onProvideVirtualStructure(android.view.ViewStructure structure,
+ int flags) {
+ }
+
public AccessibilityNodeProvider getAccessibilityNodeProvider();
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
@Override
- public void onProvideStructure(ViewStructure structure) {
- super.onProvideStructure(structure);
+ public void onProvideStructure(ViewStructure structure, int flags) {
+ super.onProvideStructure(structure, flags);
+
+ // NOTE: current there is no difference for Assist (flags=0) or AutoFill (flags>0);
CharSequence switchText = isChecked() ? mTextOn : mTextOff;
if (!TextUtils.isEmpty(switchText)) {
CharSequence oldText = structure.getText();
}
@Override
- public void onProvideStructure(ViewStructure structure) {
- super.onProvideStructure(structure);
+ public void onProvideStructure(ViewStructure structure, int flags) {
+ super.onProvideStructure(structure, flags);
+
+ final boolean forAutoFillSave =
+ (flags & ASSIST_FLAG_NON_SANITIZED_TEXT) != 0;
final boolean isPassword = hasPasswordTransformationMethod()
|| isPasswordInputType(getInputType());
- if (!isPassword) {
+ if (!isPassword || forAutoFillSave) {
if (mLayout == null) {
assumeLayout();
}
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
+import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT;
+import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT;
import android.Manifest;
import android.app.AppGlobals;
import android.app.Notification;
+import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
@Override
- public void requestAutoFill(int userId, IBinder activityToken) {
+ public void requestAutoFill(IBinder activityToken, int userId, int flags) {
+ if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
synchronized (mLock) {
final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null) {
- service.requestAutoFill(activityToken);
+ service.requestAutoFill(activityToken, flags);
}
}
}
synchronized (mLock) {
removeCachedServiceForUserLocked(userId);
final ComponentName serviceComponent = getProviderForUser(userId);
- if (serviceComponent== null) {
+ if (serviceComponent == null) {
cancelNotificationLocked(userId);
} else {
showNotification(serviceComponent, userId);
////////////////////////////////////////////////////////////////////////////
// TODO: remove from frameworks/base/core/res/AndroidManifest.xml once it's not used anymore
- private static final String NOTIFICATION_INTENT =
+ private static final String NOTIFICATION_AUTO_FILL_INTENT =
"com.android.internal.autofill.action.REQUEST_AUTOFILL";
private static final String EXTRA_USER_ID = "user_id";
+ private static final String EXTRA_FLAGS = "flags";
private static final int MSG_SHOW_ALL_NOTIFICATIONS = 42;
private static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000;
@Override
public void onReceive(Context context, Intent intent) {
final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
+ final int flags = intent.getIntExtra(EXTRA_FLAGS, 0);
if (DEBUG) Slog.d(TAG, "Requesting autofill by notification for user " + userId);
synchronized (mLock) {
final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
if (service == null) {
Slog.w(TAG, "no auto-fill service for user " + userId);
} else {
- service.requestAutoFill(null);
+ service.requestAutoFill(null, flags);
}
}
}
if (mNotificationReceiver == null) {
mNotificationReceiver = new NotificationReceiver();
mContext.registerReceiver(mNotificationReceiver,
- new IntentFilter(NOTIFICATION_INTENT));
+ new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
}
}
- final Intent intent = new Intent(NOTIFICATION_INTENT);
- intent.putExtra(EXTRA_USER_ID, userId);
- final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ final Intent fillIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
+ fillIntent.putExtra(EXTRA_USER_ID, userId);
+ fillIntent.putExtra(EXTRA_FLAGS, ASSIST_FLAG_SANITIZED_TEXT);
+ final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext,
+ ASSIST_FLAG_SANITIZED_TEXT, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ final Action fillAction = new Action.Builder(null, "FILL", fillPendingIntent).build();
+
+ final Intent saveIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
+ saveIntent.putExtra(EXTRA_USER_ID, userId);
+ saveIntent.putExtra(EXTRA_FLAGS, ASSIST_FLAG_NON_SANITIZED_TEXT);
+ final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext,
+ ASSIST_FLAG_NON_SANITIZED_TEXT, saveIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ final Action saveAction = new Action.Builder(null, "SAVE", savePendingIntent).build();
final String packageName = serviceComponent.getPackageName();
String providerName = null;
} catch (Exception e) {
providerName = packageName;
}
- final String title = "AutoFill by '" + providerName + "'";
- final String subTitle = "Tap notification to auto-fill top activity for user " + userId;
+ final String title = "AutoFill actions";
+ final String subTitle = "Provider: " + providerName + "\n" + "User: " + userId;
final Notification notification = new Notification.Builder(mContext)
.setCategory(Notification.CATEGORY_SYSTEM)
com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)
.setStyle(new Notification.BigTextStyle().bigText(subTitle))
- .setContentIntent(pi)
+ .setActions(fillAction, saveAction)
.build();
NotificationManager.from(mContext).notify(userId, notification);
}
import com.android.server.LocalServices;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
private final AutoFillServiceInfo mInfo;
private final AutoFillManagerService mManagerService;
- // TODO: improve its usage
+ // TODO(b/33197203): improve its usage
// - set maximum number of entries
// - disable on low-memory devices.
private final List<String> mRequestHistory = new LinkedList<>();
@GuardedBy("mLock")
- private final List<IBinder> mQueuedRequests = new LinkedList<>();
+ private final List<QueuedRequest> mQueuedRequests = new LinkedList<>();
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
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 (or remove this receiver)
+ // TODO(b/33197203): close any pending UI like account selection (or remove this
+ // receiver)
}
}
};
if (!mQueuedRequests.isEmpty()) {
if (DEBUG) Log.d(TAG, "queued requests:" + mQueuedRequests.size());
}
- for (IBinder activityToken : mQueuedRequests) {
- requestAutoFillLocked(activityToken, false);
+ for (final QueuedRequest request: mQueuedRequests) {
+ requestAutoFillLocked(request.activityToken, request.flags, false);
}
}
}
if (DEBUG) Slog.d(TAG, "Bound to " + mComponent);
}
- void requestAutoFill(IBinder activityToken) {
+ void requestAutoFill(IBinder activityToken, int flags) {
synchronized (mLock) {
if (!mBound) {
Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
}
}
- // 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 request'
+ // TODO(b/33197203): activityToken should probably not be null, but we need to wait until
+ // the UI is triggering the call (for now it's trough 'adb shell cmd autofill request'
if (activityToken == null) {
// Let's get top activities from all visible stacks.
- // TODO: overload getTopVisibleActivities() to take userId, otherwise it could return
- // activities for different users when a work profile app is displayed in another
- // window (in a multi-window environment).
+ // TODO(b/33197203): overload getTopVisibleActivities() to take userId, otherwise it
+ // could return activities for different users when a work profile app is displayed in
+ // another window (in a multi-window environment).
final List<IBinder> topActivities = LocalServices
.getService(ActivityManagerInternal.class).getTopVisibleActivities();
if (DEBUG)
DateFormat.getDateTimeInstance().format(new Date()) + " - " + activityToken;
synchronized (mLock) {
mRequestHistory.add(historyItem);
- requestAutoFillLocked(activityToken, true);
+ requestAutoFillLocked(activityToken, flags, true);
}
}
- private void requestAutoFillLocked(IBinder activityToken, boolean queueIfNecessary) {
+ private void requestAutoFillLocked(IBinder activityToken, int flags, boolean queueIfNecessary) {
if (mService == null) {
if (!queueIfNecessary) {
Slog.w(TAG, "requestAutoFillLocked(): service is null");
return;
}
if (DEBUG) Slog.d(TAG, "requestAutoFill(): service not set yet, queuing it");
- mQueuedRequests.add(activityToken);
+ mQueuedRequests.add(new QueuedRequest(activityToken, flags));
return;
}
/*
- * TODO: apply security checks below:
+ * TODO(b/33197203): apply security checks below:
* - checks if disabled by secure settings / device policy
* - log operation using noteOp()
* - check flags
* - display disclosure if needed
*/
try {
- // TODO: add MetricsLogger call
- if (!mAm.requestAutoFillData(mService.getAssistReceiver(), null, activityToken)) {
- // TODO: might need a way to warn user (perhaps a new method on AutoFillService).
+ // TODO(b/33197203): add MetricsLogger call
+ if (!mAm.requestAutoFillData(mService.getAssistReceiver(), null, activityToken,
+ flags)) {
+ // TODO(b/33197203): might need a way to warn user (perhaps a new method on
+ // AutoFillService).
Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
}
} catch (RemoteException e) {
return "[AutoFillManagerServiceImpl: userId=" + mUserId + ", uid=" + mUid
+ ", component=" + mComponent.flattenToShortString() + "]";
}
+
+ private static final class QueuedRequest {
+ final IBinder activityToken;
+ final int flags;
+
+ QueuedRequest(IBinder activityToken, int flags) {
+ this.activityToken = activityToken;
+ this.flags = flags;
+ }
+
+ @Override
+ public String toString() {
+ return "flags: " + flags + " token: " + activityToken;
+ }
+ }
}
package com.android.server.autofill;
+import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT;
+import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT;
+
import android.app.ActivityManager;
import android.os.RemoteException;
import android.os.ShellCommand;
final PrintWriter pw = getOutPrintWriter();
try {
switch (cmd) {
- case "request":
- return requestAutoFill();
+ case "fill":
+ return requestAutoFill(ASSIST_FLAG_SANITIZED_TEXT);
+ case "save":
+ return requestAutoFill(ASSIST_FLAG_NON_SANITIZED_TEXT);
default:
return handleDefaultCommands(cmd);
}
pw.println(" help");
pw.println(" Prints this help text.");
pw.println("");
- pw.println(" request [--user USER_ID]");
- pw.println(" Request auto-fill on the top activity. ");
+ pw.println(" fill [--user USER_ID]");
+ pw.println(" Request provider to auto-fill the top activity. ");
+ pw.println(" save [--user USER_ID]");
+ pw.println(" Request provider to save contents of the top activity. ");
pw.println("");
}
}
- private int requestAutoFill() throws RemoteException {
+ private int requestAutoFill(int flags) throws RemoteException {
final int userId = getUserIdFromArgs();
- mService.requestAutoFill(userId, null);
+ mService.requestAutoFill(null, userId, flags);
return 0;
}
import android.os.storage.StorageManager;
import android.provider.Downloads;
import android.provider.Settings;
+import android.service.autofill.AutoFillService;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionSession;
// on getting this result before starting to launch its UI).
static final int PENDING_ASSIST_EXTRAS_LONG_TIMEOUT = 2000;
+ // How long to wait in getAutoFillAssistStructure() for the activity to respond with the result.
+ static final int PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT = 2000;
+
// Maximum number of persisted Uri grants a package is allowed
static final int MAX_PERSISTED_URI_GRANTS = 128;
public AssistStructure structure = null;
public AssistContent content = null;
public Bundle receiverExtras;
+ public int flags;
public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
- String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
+ String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _flags,
+ int _userHandle) {
activity = _activity;
extras = _extras;
intent = _intent;
hint = _hint;
receiver = _receiver;
receiverExtras = _receiverExtras;
+ flags = _flags;
userHandle = _userHandle;
}
@Override
public Bundle getAssistContextExtras(int requestType) {
PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
null, null, true /* focused */, true /* newSessionId */,
- UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT);
+ UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT, 0);
if (pae == null) {
return null;
}
IBinder activityToken, boolean focused, boolean newSessionId) {
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
activityToken, focused, newSessionId,
- UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT)
+ UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0)
!= null;
}
@Override
public boolean requestAutoFillData(IResultReceiver receiver, Bundle receiverExtras,
- IBinder activityToken) {
- return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null, receiver,
+ IBinder activityToken, int flags) {
+ return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_FULL, null, null, receiver,
receiverExtras, activityToken, true, true,
- UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT)
- != null;
+ UserHandle.getCallingUserId(), null, PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT,
+ flags) != 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) {
+ boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
+ int flags) {
enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
"enqueueAssistContext()");
synchronized (this) {
extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName);
extras.putInt(Intent.EXTRA_ASSIST_UID, activity.app.uid);
pae = new PendingAssistExtras(activity, extras, intent, hint, receiver, receiverExtras,
- userHandle);
+ flags, userHandle);
// Increment the sessionId if necessary
if (newSessionId) {
mViSessionId++;
}
try {
activity.app.thread.requestAssistContextExtras(activity.appToken, pae,
- requestType, mViSessionId);
+ requestType, mViSessionId, flags);
mPendingAssistExtras.add(pae);
mUiHandler.postDelayed(pae, timeout);
} catch (RemoteException e) {
sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content);
sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
pae.receiverExtras);
+ if (pae.flags > 0) {
+ sendBundle.putInt(VoiceInteractionSession.KEY_FLAGS, pae.flags);
+ }
IBinder autoFillCallback =
- extras.getBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK);
+ extras.getBinder(AutoFillService.KEY_CALLBACK);
if (autoFillCallback != null) {
- sendBundle.putBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK,
+ sendBundle.putBinder(AutoFillService.KEY_CALLBACK,
autoFillCallback);
}
}
Bundle args) {
return enqueueAssistContext(requestType, intent, hint, null, null, null,
true /* focused */, true /* newSessionId */,
- userHandle, args, PENDING_ASSIST_EXTRAS_TIMEOUT) != null;
+ userHandle, args, PENDING_ASSIST_EXTRAS_TIMEOUT, 0) != null;
}
public void registerProcessObserver(IProcessObserver observer) {