Merge "Refactor AutoFill server code."
This commit is contained in:
committed by
Android (Google) Code Review
commit
c93be882cb
@@ -81,7 +81,6 @@ public final class AutoFillManagerService extends SystemService {
|
||||
protected static final int MSG_REQUEST_AUTO_FILL = 3;
|
||||
|
||||
private final AutoFillManagerServiceStub mServiceStub;
|
||||
private final AutoFillUI mUi;
|
||||
private final Context mContext;
|
||||
private final ContentResolver mResolver;
|
||||
|
||||
@@ -146,7 +145,7 @@ public final class AutoFillManagerService extends SystemService {
|
||||
mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true);
|
||||
|
||||
mContext = context;
|
||||
mUi = new AutoFillUI(context, this, mLock);
|
||||
|
||||
mResolver = context.getContentResolver();
|
||||
mServiceStub = new AutoFillManagerServiceStub();
|
||||
}
|
||||
@@ -186,7 +185,7 @@ public final class AutoFillManagerService extends SystemService {
|
||||
if (DEBUG) Slog.d(TAG, "no service info for " + serviceComponent);
|
||||
return null;
|
||||
}
|
||||
return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, mRequestsHistory,
|
||||
return new AutoFillManagerServiceImpl(this, mContext, mLock, mRequestsHistory,
|
||||
FgThread.getHandler(), userId, serviceInfo.applicationInfo.uid, serviceComponent,
|
||||
SERVICE_BINDING_LIFETIME_MS);
|
||||
}
|
||||
@@ -326,7 +325,6 @@ public final class AutoFillManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
}
|
||||
mUi.dump(pw);
|
||||
pw.println("Requests history:");
|
||||
mRequestsHistory.reverseDump(fd, pw, args);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ package com.android.server.autofill;
|
||||
import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_ERROR;
|
||||
import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_REQUESTED;
|
||||
import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_SUCCESS;
|
||||
import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
|
||||
import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
|
||||
import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_SHOW;
|
||||
import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_HIDE;
|
||||
|
||||
import static com.android.server.autofill.Helper.DEBUG;
|
||||
@@ -56,6 +58,7 @@ import android.service.autofill.IAutoFillAppCallback;
|
||||
import android.service.autofill.IAutoFillServerCallback;
|
||||
import android.service.autofill.IAutoFillService;
|
||||
import android.service.voice.VoiceInteractionSession;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Log;
|
||||
import android.util.PrintWriterPrinter;
|
||||
@@ -63,6 +66,7 @@ import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.view.autofill.AutoFillId;
|
||||
import android.view.autofill.AutoFillValue;
|
||||
import android.view.autofill.Dataset;
|
||||
import android.view.autofill.FillResponse;
|
||||
|
||||
@@ -74,6 +78,7 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bridge between the {@code system_server}'s {@link AutoFillManagerService} and the
|
||||
@@ -96,12 +101,14 @@ final class AutoFillManagerServiceImpl {
|
||||
private final Object mLock;
|
||||
private final AutoFillServiceInfo mInfo;
|
||||
private final AutoFillManagerService mManagerService;
|
||||
private final AutoFillUI mUi;
|
||||
|
||||
// Token used for fingerprint authentication
|
||||
// TODO(b/33197203): create on demand?
|
||||
private final IBinder mAuthToken = new Binder();
|
||||
|
||||
private final IFingerprintService mFingerprintService =
|
||||
IFingerprintService.Stub.asInterface(ServiceManager.getService("fingerprint"));
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final List<QueuedRequest> mQueuedRequests = new LinkedList<>();
|
||||
|
||||
@@ -204,11 +211,10 @@ final class AutoFillManagerServiceImpl {
|
||||
// Estimated time when the service will be evicted from the cache.
|
||||
long mEstimateTimeOfDeath;
|
||||
|
||||
AutoFillManagerServiceImpl(AutoFillManagerService managerService, AutoFillUI ui,
|
||||
Context context, Object lock, LocalLog requestsHistory, Handler handler, int userId,
|
||||
int uid, ComponentName component, long ttl) {
|
||||
AutoFillManagerServiceImpl(AutoFillManagerService managerService, Context context, Object lock,
|
||||
LocalLog requestsHistory, Handler handler, int userId, int uid, ComponentName component,
|
||||
long ttl) {
|
||||
mManagerService = managerService;
|
||||
mUi = ui;
|
||||
mContext = context;
|
||||
mLock = lock;
|
||||
mRequestsHistory = requestsHistory;
|
||||
@@ -277,8 +283,8 @@ final class AutoFillManagerServiceImpl {
|
||||
requestAutoFillLocked(activityToken, autoFillId, bounds, flags, true);
|
||||
}
|
||||
|
||||
private void requestAutoFillLocked(IBinder activityToken, AutoFillId autoFillId, Rect bounds,
|
||||
int flags, boolean queueIfNecessary) {
|
||||
private void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId,
|
||||
@Nullable Rect bounds, int flags, boolean queueIfNecessary) {
|
||||
if (mService == null) {
|
||||
if (!queueIfNecessary) {
|
||||
Slog.w(TAG, "requestAutoFillLocked(): service is null");
|
||||
@@ -294,56 +300,35 @@ final class AutoFillManagerServiceImpl {
|
||||
return;
|
||||
}
|
||||
|
||||
final Session session = getSessionByTokenLocked(activityToken);
|
||||
|
||||
if (session != null) {
|
||||
// Session already exist, update UI instead...
|
||||
/*
|
||||
* TODO(b/33197203): currently, it's always reusing the session, regardless of the
|
||||
* requested autoFillId, but it should start a new session for views that
|
||||
* were not part of the initial auto-fill dataset returned by the service. For example:
|
||||
*
|
||||
* 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
|
||||
* 2.User taps `first_name`.
|
||||
* 3.Service returns a dataset with ids for `first_name` and `last_name`.
|
||||
* 4.When user taps `first_name` (again) or `last_name`, session should be reused, but
|
||||
* when user taps `address`, it should start a new session (since that field was
|
||||
* not part of the initial dataset).
|
||||
*
|
||||
* Similarly, once the activity is auto-filled, the flag logic should be reset (so if
|
||||
* the user taps the view again, a new auto-fill request is made)
|
||||
*/
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "requestAutoFillLocked(): reusing session for token "
|
||||
+ activityToken + ", id " + autoFillId + " and flags " + flags);
|
||||
}
|
||||
|
||||
if ((flags & FLAG_UPDATE_UI_HIDE) != 0) {
|
||||
// TODO(b/33197203): handle it?
|
||||
if (DEBUG) Slog.d(TAG, "ignoring FLAG_UPDATE_UI_HIDE request for " + autoFillId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
session.mCurrentAutoFillId = autoFillId;
|
||||
session.mCurrentBounds = bounds;
|
||||
mUi.showResponse(mUserId, session.mId, autoFillId, bounds, session.mCurrentResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
final int sessionId = ++sSessionIdCounter;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "requestAutoFillLocked(): new session (id=" + sessionId + " for token "
|
||||
+ activityToken + " and autoFillId " + autoFillId);
|
||||
}
|
||||
|
||||
final Session newSession = new Session(sessionId, activityToken, autoFillId, bounds);
|
||||
mSessions.put(sessionId, newSession);
|
||||
|
||||
final String historyItem = "s=" + mComponentName + " u=" + mUserId + " f=" + flags
|
||||
+ " a=" + activityToken + " i=" + autoFillId + " b=" + bounds;
|
||||
mRequestsHistory.log(historyItem);
|
||||
|
||||
// TODO(b/33197203): Handle partitioning
|
||||
Session session = getOrCreateSessionByTokenLocked(activityToken);
|
||||
if (DEBUG) Slog.d(TAG, "using Session: " + session.mId);
|
||||
|
||||
session.updateAutoFillInput(flags, autoFillId, null, bounds);
|
||||
}
|
||||
|
||||
private Session getOrCreateSessionByTokenLocked(IBinder activityToken) {
|
||||
final int size = mSessions.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
final Session session = mSessions.valueAt(i);
|
||||
if (activityToken.equals(session.mActivityToken.get())) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
return createSessionByTokenLocked(activityToken);
|
||||
}
|
||||
|
||||
private Session createSessionByTokenLocked(IBinder activityToken) {
|
||||
final int sessionId = ++sSessionIdCounter;
|
||||
if (DEBUG) Slog.d(TAG, "creating Session: " + sessionId);
|
||||
|
||||
final Session newSession = new Session(sessionId, activityToken);
|
||||
mSessions.put(sessionId, newSession);
|
||||
|
||||
/*
|
||||
* TODO(b/33197203): apply security checks below:
|
||||
* - checks if disabled by secure settings / device policy
|
||||
@@ -353,7 +338,8 @@ final class AutoFillManagerServiceImpl {
|
||||
*/
|
||||
try {
|
||||
// TODO(b/33197203): add MetricsLogger call
|
||||
if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken, flags)) {
|
||||
if (!mAm.requestAutoFillData(
|
||||
mAssistReceiver, null, sessionId, activityToken, AUTO_FILL_FLAG_TYPE_FILL)) {
|
||||
// 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);
|
||||
@@ -361,75 +347,7 @@ final class AutoFillManagerServiceImpl {
|
||||
} catch (RemoteException e) {
|
||||
// Should not happen, it's a local call.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by UI to trigger a save request to the service.
|
||||
*/
|
||||
void requestSaveLocked(int sessionId) {
|
||||
// TODO(b/33197203): add MetricsLogger call
|
||||
// TODO(b/33197203): use handler?
|
||||
// TODO(b/33197203): show error on UI on Slog.w situations below???
|
||||
|
||||
if (mService == null) {
|
||||
Slog.w(TAG, "requestSave(): service is null");
|
||||
return;
|
||||
}
|
||||
final Session session = mSessions.get(sessionId);
|
||||
if (session == null) {
|
||||
Slog.w(TAG, "requestSave(): no session with id " + sessionId);
|
||||
return;
|
||||
}
|
||||
final IBinder activityToken = session.mActivityToken.get();
|
||||
if (activityToken == null) {
|
||||
Slog.w(TAG, "activity token for session " + sessionId + " already GCed");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(b/33197203): refactor save logic so it uses a cached AssistStructure, and get
|
||||
the extras to be sent to the service based on the response / dataset in the session.
|
||||
Something like:
|
||||
final Bundle extras = (responseExtras == null && datasetExtras == null)
|
||||
? null : new Bundle();
|
||||
if (responseExtras != null) {
|
||||
if (DEBUG) Slog.d(TAG, "response extras on save notification: " +
|
||||
bundleToString(responseExtras));
|
||||
extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
|
||||
}
|
||||
if (datasetExtras != null) {
|
||||
if (DEBUG) Slog.d(TAG, "dataset extras on save notification: " +
|
||||
bundleToString(datasetExtras));
|
||||
extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken,
|
||||
AUTO_FILL_FLAG_TYPE_SAVE)) {
|
||||
Slog.w(TAG, "failed to save for " + activityToken);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// Should not happen, it's a local call.
|
||||
}
|
||||
}
|
||||
|
||||
private Session getSessionByTokenLocked(IBinder activityToken) {
|
||||
final int size = mSessions.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
final Session session = mSessions.valueAt(i);
|
||||
if (activityToken.equals(session.mActivityToken.get())) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return newSession;
|
||||
}
|
||||
|
||||
void stopLocked() {
|
||||
@@ -459,35 +377,6 @@ final class AutoFillManagerServiceImpl {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link AutoFillUI} to fill an activity after the user selected a dataset.
|
||||
*/
|
||||
void autoFillApp(int sessionId, Dataset dataset) {
|
||||
// TODO(b/33197203): add MetricsLogger call
|
||||
|
||||
if (dataset == null) {
|
||||
Slog.w(TAG, "autoFillApp(): no dataset for callback id " + sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
final Session session;
|
||||
synchronized (mLock) {
|
||||
session = mSessions.get(sessionId);
|
||||
if (session == null) {
|
||||
Slog.w(TAG, "autoFillApp(): no session with id " + sessionId);
|
||||
return;
|
||||
}
|
||||
if (session.mAppCallback == null) {
|
||||
Slog.w(TAG, "autoFillApp(): no app callback for session " + sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/33197203): use a handler?
|
||||
session.autoFill(dataset);
|
||||
}
|
||||
}
|
||||
|
||||
void removeSessionLocked(int id) {
|
||||
if (DEBUG) Slog.d(TAG, "Removing session " + id);
|
||||
mSessions.remove(id);
|
||||
@@ -495,43 +384,6 @@ final class AutoFillManagerServiceImpl {
|
||||
// TODO(b/33197203): notify mService so it can invalidate the FillCallback / SaveCallback?
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the result of a {@link FillResponse} authentication request to the service.
|
||||
*
|
||||
* <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after user
|
||||
* used the fingerprint sensors to authenticate.
|
||||
*/
|
||||
void notifyResponseAuthenticationResult(Bundle extras, int flags) {
|
||||
if (DEBUG) Slog.d(TAG, "notifyResponseAuthenticationResult(): flags=" + flags
|
||||
+ ", extras=" + bundleToString(extras));
|
||||
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
mService.authenticateFillResponse(extras, flags);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Error sending authentication result back to service: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the result of a {@link Dataset} authentication request to the service.
|
||||
*
|
||||
* <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after
|
||||
* it gets the results from a fingerprint authentication.
|
||||
*/
|
||||
void notifyDatasetAuthenticationResult(Bundle extras, int flags) {
|
||||
if (DEBUG) Slog.d(TAG, "notifyDatasetAuthenticationResult(): flags=" + flags
|
||||
+ ", extras=" + bundleToString(extras));
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
mService.authenticateDataset(extras, flags);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Error sending authentication result back to service: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dumpLocked(String prefix, PrintWriter pw) {
|
||||
if (!mValid) {
|
||||
pw.print(" NOT VALID: ");
|
||||
@@ -612,8 +464,78 @@ final class AutoFillManagerServiceImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* A bridge between the {@link AutoFillService} implementation and the activity being
|
||||
* auto-filled (represented through the {@link IAutoFillAppCallback}).
|
||||
* State for a given view with a AutoFillId.
|
||||
*
|
||||
* <p>This class holds state about a view and calls its listener when the fill UI is ready to
|
||||
* be displayed for the view.
|
||||
*/
|
||||
static final class ViewState {
|
||||
interface Listener {
|
||||
/**
|
||||
* Called when the fill UI is ready to be shown for this view.
|
||||
*/
|
||||
void onFillReady(ViewState viewState, FillResponse fillResponse, Rect bounds,
|
||||
@Nullable AutoFillValue value);
|
||||
}
|
||||
|
||||
private final Listener mListener;
|
||||
@Nullable
|
||||
private FillResponse mResponse;
|
||||
private AutoFillValue mAutoFillValue;
|
||||
private Rect mBounds;
|
||||
|
||||
ViewState(Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response should only be set once.
|
||||
*/
|
||||
void setResponse(FillResponse response) {
|
||||
if (mResponse != null) {
|
||||
Slog.e(TAG, "ViewState response set more than once");
|
||||
return;
|
||||
}
|
||||
mResponse = response;
|
||||
|
||||
maybeCallOnFillReady();
|
||||
}
|
||||
|
||||
void update(@Nullable AutoFillValue autoFillValue, @Nullable Rect bounds) {
|
||||
if (autoFillValue != null) {
|
||||
mAutoFillValue = autoFillValue;
|
||||
}
|
||||
if (bounds != null) {
|
||||
mBounds = bounds;
|
||||
}
|
||||
|
||||
maybeCallOnFillReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Listener#onFillReady(ViewState, FillResponse, Rect, AutoFillValue)} if the
|
||||
* fill UI is ready to be displayed (i.e. when response and bounds are set).
|
||||
*/
|
||||
void maybeCallOnFillReady() {
|
||||
if (mResponse != null && mBounds != null) {
|
||||
mListener.onFillReady(this, mResponse, mBounds, mAutoFillValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (!DEBUG) return super.toString();
|
||||
|
||||
return "ViewState: [response=" + mResponse + ", value=" + mAutoFillValue
|
||||
+ ", bounds=" + mBounds + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A session for a given activity.
|
||||
*
|
||||
* <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
|
||||
* of the current view session to display the appropriate UI.
|
||||
*
|
||||
* <p>Although the auto-fill requests and callbacks are stateless from the service's point of
|
||||
* view, we need to keep state in the framework side for cases such as authentication. For
|
||||
@@ -625,23 +547,23 @@ final class AutoFillManagerServiceImpl {
|
||||
// - On all authentication scenarios.
|
||||
// - When user does not interact back after a while.
|
||||
// - When service is unbound.
|
||||
private final class Session {
|
||||
final class Session implements ViewState.Listener {
|
||||
|
||||
private final int mId;
|
||||
private final AutoFillUI mUi;
|
||||
final int mId;
|
||||
private final WeakReference<IBinder> mActivityToken;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final Map<AutoFillId, ViewState> mViewStates = new ArrayMap<>();
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private ViewState mCurrentViewState;
|
||||
|
||||
private IAutoFillAppCallback mAppCallback;
|
||||
|
||||
// Current view where the auto-fill bar is displayed
|
||||
@GuardedBy("mLock")
|
||||
private AutoFillId mCurrentAutoFillId;
|
||||
@GuardedBy("mLock")
|
||||
private Rect mCurrentBounds;
|
||||
// TODO(b/33197203): Get a response per view instead of per activity.
|
||||
@GuardedBy("mLock")
|
||||
private FillResponse mCurrentResponse;
|
||||
|
||||
private final IFingerprintService mFingerprintService;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private FillResponse mResponseRequiringAuth;
|
||||
@GuardedBy("mLock")
|
||||
@@ -683,7 +605,7 @@ final class AutoFillManagerServiceImpl {
|
||||
notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(),
|
||||
FLAG_AUTHENTICATION_SUCCESS);
|
||||
} else {
|
||||
autoFillAppLocked(mDatasetRequiringAuth, true);
|
||||
autoFillApp(mDatasetRequiringAuth);
|
||||
}
|
||||
} else if (mResponseRequiringAuth != null) {
|
||||
final List<Dataset> datasets = mResponseRequiringAuth.getDatasets();
|
||||
@@ -697,7 +619,7 @@ final class AutoFillManagerServiceImpl {
|
||||
Slog.w(TAG, "onAuthenticationSucceeded(): no response or dataset");
|
||||
}
|
||||
|
||||
mUi.dismissFingerprintRequest(mUserId, true);
|
||||
mUi.dismissFingerprintRequest(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -721,7 +643,7 @@ final class AutoFillManagerServiceImpl {
|
||||
Slog.w(TAG, "onError(): no response or dataset");
|
||||
}
|
||||
|
||||
mUi.dismissFingerprintRequest(mUserId, false);
|
||||
mUi.dismissFingerprintRequest(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -741,7 +663,6 @@ final class AutoFillManagerServiceImpl {
|
||||
// TODO(b/33197203): add MetricsLogger call
|
||||
if (response == null) {
|
||||
if (DEBUG) Slog.d(TAG, "showResponse(): null response");
|
||||
|
||||
removeSelf();
|
||||
return;
|
||||
}
|
||||
@@ -805,20 +726,16 @@ final class AutoFillManagerServiceImpl {
|
||||
if (DEBUG) Log.d(TAG, "unlockDataset(): dataset=" + dataset + ", flags=" + flags);
|
||||
|
||||
if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) {
|
||||
autoFillAppLocked(dataset != null ? dataset : mDatasetRequiringAuth, true);
|
||||
autoFillApp(dataset != null ? dataset : mDatasetRequiringAuth);
|
||||
return;
|
||||
}
|
||||
removeSelf();
|
||||
}
|
||||
};
|
||||
|
||||
private Session(int id, IBinder activityToken, AutoFillId autoFillId, Rect bounds) {
|
||||
this.mId = id;
|
||||
this.mActivityToken = new WeakReference<>(activityToken);
|
||||
this.mCurrentAutoFillId = autoFillId;
|
||||
this.mCurrentBounds = bounds;
|
||||
this.mFingerprintService = IFingerprintService.Stub
|
||||
.asInterface(ServiceManager.getService("fingerprint"));
|
||||
private Session(int id, IBinder activityToken) {
|
||||
mUi = new AutoFillUI(mContext, this);
|
||||
mId = id;
|
||||
mActivityToken = new WeakReference<>(activityToken);
|
||||
}
|
||||
|
||||
void setAppCallback(IBinder appBinder) {
|
||||
@@ -834,6 +751,54 @@ final class AutoFillManagerServiceImpl {
|
||||
mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
|
||||
}
|
||||
|
||||
void updateAutoFillInput(int flags, AutoFillId autoFillId,
|
||||
@Nullable AutoFillValue autoFillValue, @Nullable Rect bounds) {
|
||||
synchronized (mLock) {
|
||||
ViewState viewState = mViewStates.get(autoFillId);
|
||||
if (viewState == null) {
|
||||
viewState = new ViewState(this);
|
||||
mViewStates.put(autoFillId, viewState);
|
||||
}
|
||||
|
||||
if ((flags & FLAG_UPDATE_UI_SHOW) != 0) {
|
||||
// Remove the UI if the ViewState has changed.
|
||||
if (mCurrentViewState != viewState) {
|
||||
mUi.hideFillUi();
|
||||
mCurrentViewState = viewState;
|
||||
}
|
||||
|
||||
// If the ViewState is ready to be displayed, onReady() will be called.
|
||||
viewState.update(autoFillValue, bounds);
|
||||
|
||||
// TODO(b/33197203): Remove when there is a response per activity.
|
||||
if (mCurrentResponse != null) {
|
||||
viewState.setResponse(mCurrentResponse);
|
||||
}
|
||||
} else if ((flags & FLAG_UPDATE_UI_HIDE) != 0) {
|
||||
if (mCurrentViewState == viewState) {
|
||||
mUi.hideFillUi();
|
||||
mCurrentViewState = null;
|
||||
}
|
||||
} else {
|
||||
Slog.w(TAG, "unknown flags " + flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFillReady(ViewState viewState, FillResponse response, Rect bounds,
|
||||
@Nullable AutoFillValue value) {
|
||||
String filterText = "";
|
||||
if (value != null) {
|
||||
// TODO(b/33197203): Handle other AutoFillValue types
|
||||
final CharSequence text = value.getTextValue();
|
||||
if (text != null) {
|
||||
filterText = text.toString();
|
||||
}
|
||||
}
|
||||
mUi.showFillUi(viewState, response.getDatasets(), bounds, filterText);
|
||||
}
|
||||
|
||||
private void showResponseLocked(FillResponse response, boolean authRequired) {
|
||||
if (DEBUG) Slog.d(TAG, "showResponse(directly=" + mAutoFillDirectly
|
||||
+ ", authRequired=" + authRequired +"):" + response);
|
||||
@@ -845,7 +810,7 @@ final class AutoFillManagerServiceImpl {
|
||||
final Dataset dataset = datasets.get(0);
|
||||
if (DEBUG) Slog.d(TAG, "auto-filling directly from auth: " + dataset);
|
||||
|
||||
autoFillAppLocked(dataset, true);
|
||||
autoFillApp(dataset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -853,7 +818,10 @@ final class AutoFillManagerServiceImpl {
|
||||
if (!authRequired) {
|
||||
// TODO(b/33197203): add MetricsLogger call
|
||||
mCurrentResponse = response;
|
||||
mUi.showResponse(mUserId, mId, mCurrentAutoFillId, mCurrentBounds, mCurrentResponse);
|
||||
// TODO(b/33197203): Consider using mCurrentResponse, depends on partitioning design
|
||||
if (mCurrentViewState != null) {
|
||||
mCurrentViewState.setResponse(mCurrentResponse);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -869,7 +837,7 @@ final class AutoFillManagerServiceImpl {
|
||||
scanFingerprint(response.getCryptoObjectOpId());
|
||||
}
|
||||
// Displays the message asking the user to tap (or fingerprint) for AutoFill.
|
||||
mUi.showFillResponseAuthenticationRequest(mUserId, mId, requiresFingerprint,
|
||||
mUi.showFillResponseAuthenticationRequest(requiresFingerprint,
|
||||
response.getExtras(), response.getFlags());
|
||||
}
|
||||
|
||||
@@ -877,7 +845,7 @@ final class AutoFillManagerServiceImpl {
|
||||
synchronized (mLock) {
|
||||
// Autofill it directly...
|
||||
if (!dataset.isAuthRequired()) {
|
||||
autoFillAppLocked(dataset, true);
|
||||
autoFillApp(dataset);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -906,30 +874,114 @@ final class AutoFillManagerServiceImpl {
|
||||
void dumpLocked(String prefix, PrintWriter pw) {
|
||||
pw.print(prefix); pw.print("mId: "); pw.println(mId);
|
||||
pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken.get());
|
||||
pw.print(prefix); pw.print("mCurrentAutoFillId: "); pw.println(mCurrentAutoFillId);
|
||||
pw.print(prefix); pw.print("mCurrentBounds: "); pw.println(mCurrentBounds);
|
||||
pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
|
||||
pw.print(prefix);
|
||||
pw.print("mResponseRequiringAuth: "); pw.println(mResponseRequiringAuth);
|
||||
pw.print(prefix);
|
||||
pw.print("mDatasetRequiringAuth: "); pw.println(mDatasetRequiringAuth);
|
||||
pw.print(prefix); pw.print("mAutoFillDirectly: "); pw.println(mAutoFillDirectly);
|
||||
pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState);
|
||||
pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size());
|
||||
final String prefix2 = prefix + " ";
|
||||
for (Map.Entry<AutoFillId, ViewState> entry : mViewStates.entrySet()) {
|
||||
pw.print(prefix2);
|
||||
pw.print(entry.getKey()); pw.print(": " ); pw.println(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void autoFillAppLocked(Dataset dataset, boolean removeSelf) {
|
||||
try {
|
||||
if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
|
||||
mAppCallback.autoFill(dataset);
|
||||
|
||||
// TODO(b/33197203): temporarily hack: show the save notification after autofilled,
|
||||
// since save is not automatically detected yet.
|
||||
mUi.showSaveNotification(mUserId, mId); removeSelf = false;
|
||||
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Error auto-filling activity: " + e);
|
||||
/**
|
||||
* Notifies the result of a {@link FillResponse} authentication request to the service.
|
||||
*
|
||||
* <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after user
|
||||
* used the fingerprint sensors to authenticate.
|
||||
*/
|
||||
void notifyResponseAuthenticationResult(Bundle extras, int flags) {
|
||||
if (DEBUG) Slog.d(TAG, "notifyResponseAuthenticationResult(): flags=" + flags
|
||||
+ ", extras=" + bundleToString(extras));
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
mService.authenticateFillResponse(extras, flags);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Error sending authentication result back to service: " + e);
|
||||
}
|
||||
}
|
||||
if (removeSelf) {
|
||||
removeSelf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the result of a {@link Dataset} authentication request to the service.
|
||||
*
|
||||
* <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after
|
||||
* it gets the results from a fingerprint authentication.
|
||||
*/
|
||||
void notifyDatasetAuthenticationResult(Bundle extras, int flags) {
|
||||
if (DEBUG) Slog.d(TAG, "notifyDatasetAuthenticationResult(): flags=" + flags
|
||||
+ ", extras=" + bundleToString(extras));
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
mService.authenticateDataset(extras, flags);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Error sending authentication result back to service: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void autoFillApp(Dataset dataset) {
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
|
||||
mAppCallback.autoFill(dataset);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Error auto-filling activity: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void requestSave() {
|
||||
synchronized (mLock) {
|
||||
requestSaveLocked(mId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by UI to trigger a save request to the service.
|
||||
*/
|
||||
void requestSaveLocked(int sessionId) {
|
||||
// TODO(b/33197203): add MetricsLogger call
|
||||
// TODO(b/33197203): use handler?
|
||||
// TODO(b/33197203): show error on UI on Slog.w situations below???
|
||||
|
||||
if (mService == null) {
|
||||
Slog.w(TAG, "requestSave(): service is null");
|
||||
return;
|
||||
}
|
||||
final Session session = mSessions.get(sessionId);
|
||||
if (session == null) {
|
||||
Slog.w(TAG, "requestSave(): no session with id " + sessionId);
|
||||
return;
|
||||
}
|
||||
final IBinder activityToken = session.mActivityToken.get();
|
||||
if (activityToken == null) {
|
||||
Slog.w(TAG, "activity token for session " + sessionId + " already GCed");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(b/33197203): refactor save logic so it uses a cached AssistStructure, and
|
||||
get the extras to be sent to the service based on the response / dataset in the
|
||||
session. */
|
||||
if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken,
|
||||
AUTO_FILL_FLAG_TYPE_SAVE)) {
|
||||
Slog.w(TAG, "failed to save for " + activityToken);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// Should not happen, it's a local call.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ import android.widget.Toast;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.server.UiThread;
|
||||
import com.android.server.autofill.AutoFillManagerServiceImpl.Session;
|
||||
import com.android.server.autofill.AutoFillManagerServiceImpl.ViewState;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
@@ -59,24 +61,25 @@ final class AutoFillUI {
|
||||
private static final String TAG = "AutoFillUI";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final Session mSession;
|
||||
private final WindowManager mWm;
|
||||
|
||||
@Nullable
|
||||
// Fill UI variables
|
||||
private AnchoredWindow mFillWindow;
|
||||
private DatasetPicker mFillView;
|
||||
private ViewState mViewState;
|
||||
private Rect mBounds;
|
||||
private String mFilterText;
|
||||
|
||||
/**
|
||||
* Custom snackbar UI used for saving autofill or other informational messages.
|
||||
*/
|
||||
private View mSnackbar;
|
||||
|
||||
AutoFillUI(Context context, AutoFillManagerService service, Object lock) {
|
||||
AutoFillUI(Context context, Session session) {
|
||||
mContext = context;
|
||||
mSession = session;
|
||||
mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
mService = service;
|
||||
mLock = lock;
|
||||
|
||||
setNotificationListener();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,29 +104,63 @@ final class AutoFillUI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the options from a {@link FillResponse} so the user can pick up the proper
|
||||
* {@link Dataset} (when the response has one) for a given view (identified by
|
||||
* {@code autoFillId}).
|
||||
* Hides the fill UI.
|
||||
*/
|
||||
void showResponse(int userId, int sessionId, AutoFillId autoFillId, Rect bounds,
|
||||
FillResponse response) {
|
||||
if (DEBUG) Slog.d(TAG, "showResponse: id=" + autoFillId + ", bounds=" + bounds);
|
||||
|
||||
void hideFillUi() {
|
||||
UiThread.getHandler().runWithScissors(() -> {
|
||||
if (mFillWindow != null) {
|
||||
if (DEBUG) Slog.d(TAG, "remove FillUi remove " + mFillWindow);
|
||||
mFillWindow.hide();
|
||||
}
|
||||
|
||||
final DatasetPicker fillView = new DatasetPicker(mContext, response.getDatasets(),
|
||||
(dataset) -> {
|
||||
mFillWindow.hide();
|
||||
onDatasetPicked(userId, dataset, sessionId);
|
||||
});
|
||||
mViewState = null;
|
||||
mBounds = null;
|
||||
mFilterText = null;
|
||||
mFillView = null;
|
||||
mFillWindow = null;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// TODO(b/33197203): request width/height properly.
|
||||
mFillWindow = new AnchoredWindow(mWm, fillView, 800,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mFillWindow.show(bounds != null ? bounds : new Rect());
|
||||
/**
|
||||
* Shows the fill UI, removing the previous fill UI if the has changed.
|
||||
*
|
||||
* @param viewState the view state, compared by reference to know if new UI should be shown
|
||||
* @param response the response to show, not used if viewState is the same
|
||||
* @param bounds bounds of the view to be filled, used if changed
|
||||
* @param filterText text of the view to be filled, used if changed
|
||||
*/
|
||||
void showFillUi(ViewState viewState, List<Dataset> datasets, Rect bounds,
|
||||
String filterText) {
|
||||
UiThread.getHandler().runWithScissors(() -> {
|
||||
if (mViewState != viewState) {
|
||||
// new
|
||||
hideFillUi();
|
||||
|
||||
mViewState = viewState;
|
||||
|
||||
mFillView = new DatasetPicker(mContext, datasets,
|
||||
(dataset) -> {
|
||||
mSession.autoFillApp(dataset);
|
||||
hideFillUi();
|
||||
showSaveUi();
|
||||
});
|
||||
mFillWindow = new AnchoredWindow(
|
||||
mWm, mFillView, 800, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
if (DEBUG) Slog.d(TAG, "show FillUi");
|
||||
}
|
||||
|
||||
if (!bounds.equals(mBounds)) {
|
||||
if (DEBUG) Slog.d(TAG, "update FillUi bounds: " + mBounds);
|
||||
mBounds = bounds;
|
||||
mFillWindow.show(mBounds);
|
||||
}
|
||||
|
||||
if (!filterText.equals(mFilterText)) {
|
||||
if (DEBUG) Slog.d(TAG, "update FillUi filter text: " + mFilterText);
|
||||
mFilterText = filterText;
|
||||
mFillView.update(mFilterText);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -134,10 +171,10 @@ final class AutoFillUI {
|
||||
* <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint or tap to
|
||||
* autofill" or "Tap to autofill", depending on the value of {@code usesFingerprint}.
|
||||
*/
|
||||
void showFillResponseAuthenticationRequest(int userId, int sessionId, boolean usesFingerprint,
|
||||
void showFillResponseAuthenticationRequest(boolean usesFingerprint,
|
||||
Bundle extras, int flags) {
|
||||
// TODO(b/33197203): proper implementation
|
||||
showAuthNotification(userId, sessionId, usesFingerprint, extras, flags);
|
||||
showAuthNotification(usesFingerprint, extras, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,15 +198,13 @@ final class AutoFillUI {
|
||||
/**
|
||||
* Shows the UI asking the user to save for auto-fill.
|
||||
*/
|
||||
void showSaveUI(int userId, int sessionId) {
|
||||
void showSaveUi() {
|
||||
showSnackbar(new SavePrompt(mContext, new SavePrompt.OnSaveListener() {
|
||||
@Override
|
||||
public void onSaveClick() {
|
||||
hideSnackbar();
|
||||
synchronized (mLock) {
|
||||
final AutoFillManagerServiceImpl service = getServiceLocked(userId);
|
||||
service.requestSaveLocked(sessionId);
|
||||
}
|
||||
|
||||
mSession.requestSave();
|
||||
}
|
||||
@Override
|
||||
public void onCancelClick() {
|
||||
@@ -181,10 +216,10 @@ final class AutoFillUI {
|
||||
/**
|
||||
* Called by service after the user user the fingerprint sensors to authenticate.
|
||||
*/
|
||||
void dismissFingerprintRequest(int userId, boolean success) {
|
||||
void dismissFingerprintRequest(boolean success) {
|
||||
if (DEBUG) Slog.d(TAG, "dismissFingerprintRequest(): ok=" + success);
|
||||
|
||||
dismissAuthNotification(userId);
|
||||
dismissAuthNotification();
|
||||
|
||||
if (!success) {
|
||||
// TODO(b/33197203): proper implementation (snack bar / i18n string)
|
||||
@@ -198,48 +233,11 @@ final class AutoFillUI {
|
||||
pw.println("AufoFill UI");
|
||||
final String prefix = " ";
|
||||
pw.print(prefix); pw.print("sResultCode: "); pw.println(sResultCode);
|
||||
pw.print(prefix); pw.print("mSessionId: "); pw.println(mSession.mId);
|
||||
pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar);
|
||||
mFillWindow.dump(pw);
|
||||
}
|
||||
|
||||
private AutoFillManagerServiceImpl getServiceLocked(int userId) {
|
||||
final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
|
||||
if (service == null) {
|
||||
Slog.w(TAG, "no auto-fill service for user " + userId);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
private void onSaveRequested(int userId, int sessionId) {
|
||||
// TODO(b/33197203): displays the snack bar, until save notification is refactored
|
||||
showSaveUI(userId, sessionId);
|
||||
}
|
||||
|
||||
private void onDatasetPicked(int userId, Dataset dataset, int sessionId) {
|
||||
synchronized (mLock) {
|
||||
final AutoFillManagerServiceImpl service = getServiceLocked(userId);
|
||||
if (service == null) return;
|
||||
|
||||
service.autoFillApp(sessionId, dataset);
|
||||
}
|
||||
}
|
||||
|
||||
private void onSessionDone(int userId, int sessionId) {
|
||||
synchronized (mLock) {
|
||||
final AutoFillManagerServiceImpl service = getServiceLocked(userId);
|
||||
if (service == null) return;
|
||||
|
||||
service.removeSessionLocked(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void onResponseAuthenticationRequested(int userId, Bundle extras, int flags) {
|
||||
synchronized (mLock) {
|
||||
final AutoFillManagerServiceImpl service = getServiceLocked(userId);
|
||||
if (service == null) return;
|
||||
|
||||
service.notifyResponseAuthenticationResult(extras, flags);
|
||||
}
|
||||
pw.print(prefix); pw.print("mViewState: "); pw.println(mViewState);
|
||||
pw.print(prefix); pw.print("mBounds: "); pw.println(mBounds);
|
||||
pw.print(prefix); pw.print("mFilterText: "); pw.println(mFilterText);
|
||||
}
|
||||
|
||||
//similar to a snackbar, but can be a bit custom since it is more than just text. This will
|
||||
@@ -289,16 +287,10 @@ final class AutoFillUI {
|
||||
private static final String EXTRA_FLAGS = "flags";
|
||||
|
||||
private static final String TYPE_OPTIONS = "options";
|
||||
private static final String TYPE_FINISH_SESSION = "finish_session";
|
||||
private static final String TYPE_PICK_DATASET = "pick_dataset";
|
||||
private static final String TYPE_SAVE = "save";
|
||||
private static final String TYPE_AUTH_RESPONSE = "auth_response";
|
||||
|
||||
@GuardedBy("mServiceLock")
|
||||
private BroadcastReceiver mNotificationReceiver;
|
||||
@GuardedBy("mServiceLock")
|
||||
private final AutoFillManagerService mService;
|
||||
private final Object mLock;
|
||||
private final Object mLock = new Object();
|
||||
|
||||
// Hack used to generate unique pending intents
|
||||
static int sResultCode = 0;
|
||||
@@ -316,8 +308,6 @@ final class AutoFillUI {
|
||||
final class NotificationReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
|
||||
final int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
|
||||
final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
|
||||
if (type == null) {
|
||||
Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
|
||||
@@ -326,26 +316,12 @@ final class AutoFillUI {
|
||||
final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
|
||||
final int flags = intent.getIntExtra(EXTRA_FLAGS, 0);
|
||||
|
||||
if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
|
||||
+ ", sessionId=" + sessionId);
|
||||
if (DEBUG) Slog.d(TAG, "Notification received: type=" + type
|
||||
+ ", sessionId=" + mSession.mId);
|
||||
synchronized (mLock) {
|
||||
switch (type) {
|
||||
case TYPE_SAVE:
|
||||
onSaveRequested(userId, sessionId);
|
||||
break;
|
||||
case TYPE_FINISH_SESSION:
|
||||
onSessionDone(userId, sessionId);
|
||||
break;
|
||||
case TYPE_PICK_DATASET:
|
||||
onDatasetPicked(userId, dataset, sessionId);
|
||||
|
||||
// Must cancel notification because it might be comming from action
|
||||
if (DEBUG) Slog.d(TAG, "Cancelling notification");
|
||||
NotificationManager.from(mContext).cancel(TYPE_OPTIONS, userId);
|
||||
|
||||
break;
|
||||
case TYPE_AUTH_RESPONSE:
|
||||
onResponseAuthenticationRequested(userId,
|
||||
mSession.notifyResponseAuthenticationResult(
|
||||
intent.getBundleExtra(EXTRA_AUTH_REQUIRED_EXTRAS), flags);
|
||||
break;
|
||||
default: {
|
||||
@@ -357,161 +333,28 @@ final class AutoFillUI {
|
||||
}
|
||||
}
|
||||
|
||||
private static Intent newNotificationIntent(int userId, String type) {
|
||||
private static Intent newNotificationIntent(String type) {
|
||||
final Intent intent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
|
||||
intent.putExtra(EXTRA_USER_ID, userId);
|
||||
intent.putExtra(EXTRA_NOTIFICATION_TYPE, type);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private PendingIntent newPickDatasetPI(int userId, int sessionId, FillResponse response,
|
||||
Dataset dataset) {
|
||||
final int resultCode = ++ sResultCode;
|
||||
if (DEBUG) Slog.d(TAG, "newPickDatasetPI: userId=" + userId + ", sessionId=" + sessionId
|
||||
+ ", resultCode=" + resultCode);
|
||||
|
||||
final Intent intent = newNotificationIntent(userId, TYPE_PICK_DATASET);
|
||||
intent.putExtra(EXTRA_SESSION_ID, sessionId);
|
||||
intent.putExtra(EXTRA_FILL_RESPONSE, response);
|
||||
intent.putExtra(EXTRA_DATASET, dataset);
|
||||
return PendingIntent.getBroadcast(mContext, resultCode, intent,
|
||||
PendingIntent.FLAG_ONE_SHOT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notification with the results of an auto-fill request, using notications actions
|
||||
* to emulate the auto-fill bar buttons displaying the dataset names.
|
||||
*/
|
||||
private void showOptionsNotification(int userId, int callbackId, AutoFillId autoFillId,
|
||||
FillResponse response) {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
showOptionsNotificationAsSystem(userId, callbackId, autoFillId, response);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
private void showOptionsNotificationAsSystem(int userId, int sessionId,
|
||||
AutoFillId autoFillId, FillResponse response) {
|
||||
// Make sure server callback is removed from cache if user cancels the notification.
|
||||
final Intent deleteIntent = newNotificationIntent(userId, TYPE_FINISH_SESSION)
|
||||
.putExtra(EXTRA_SESSION_ID, sessionId);
|
||||
final PendingIntent deletePendingIntent = PendingIntent.getBroadcast(mContext,
|
||||
++sResultCode, deleteIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
final String title = "AutoFill Options";
|
||||
|
||||
final Notification.Builder notification = newNotificationBuilder()
|
||||
.setOngoing(false)
|
||||
.setDeleteIntent(deletePendingIntent)
|
||||
.setContentTitle(title);
|
||||
|
||||
boolean autoCancel = true;
|
||||
final String subTitle;
|
||||
final List<Dataset> datasets;
|
||||
final AutoFillId[] savableIds;
|
||||
if (response != null) {
|
||||
datasets = response.getDatasets();
|
||||
savableIds = response.getSavableIds();
|
||||
} else {
|
||||
datasets = null;
|
||||
savableIds = null;
|
||||
}
|
||||
boolean showSave = false;
|
||||
if (datasets == null ) {
|
||||
subTitle = "No options to auto-fill " + autoFillId;
|
||||
} else if (datasets.isEmpty()) {
|
||||
if (savableIds.length == 0) {
|
||||
subTitle = "No options to auto-fill " + autoFillId;
|
||||
} else {
|
||||
subTitle = "No options to auto-fill " + autoFillId
|
||||
+ ", but provider can save ids:\n" + Arrays.toString(savableIds);
|
||||
showSave = true;
|
||||
}
|
||||
} else {
|
||||
final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
|
||||
if (service == null) {
|
||||
subTitle = "No auto-fill service for user " + userId;
|
||||
Slog.w(TAG, subTitle);
|
||||
} else {
|
||||
autoCancel = false;
|
||||
final int size = datasets.size();
|
||||
subTitle = "There are " + size + " option(s) to fill " + autoFillId + ".\n"
|
||||
+ "Use the notification action(s) to select the proper one."
|
||||
+ "Actions with (F) require fingerprint unlock, and with (P) require"
|
||||
+ "provider authentication to unlock";
|
||||
for (Dataset dataset : datasets) {
|
||||
final StringBuilder name = new StringBuilder(dataset.getName());
|
||||
if (dataset.isAuthRequired()) {
|
||||
if (dataset.hasCryptoObject()) {
|
||||
name.append("(F)");
|
||||
} else {
|
||||
name.append("(P)");
|
||||
}
|
||||
}
|
||||
final PendingIntent pi = newPickDatasetPI(userId, sessionId, response, dataset);
|
||||
notification.addAction(new Action.Builder(null, name, pi).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notification.setAutoCancel(autoCancel);
|
||||
notification.setStyle(new Notification.BigTextStyle().bigText(subTitle));
|
||||
|
||||
NotificationManager.from(mContext).notify(TYPE_OPTIONS, userId, notification.build());
|
||||
|
||||
if (showSave) {
|
||||
showSaveNotification(userId, sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
void showSaveNotification(int userId, int sessionId) {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
showSaveNotificationAsSystem(userId, sessionId);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
private void showSaveNotificationAsSystem(int userId, int sessionId) {
|
||||
final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE)
|
||||
.putExtra(EXTRA_SESSION_ID, sessionId);
|
||||
|
||||
final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext,
|
||||
++sResultCode, saveIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
final String title = "AutoFill Save Emulation";
|
||||
final String subTitle = "Tap notification to launch the save snackbar.";
|
||||
|
||||
final Notification notification = newNotificationBuilder()
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(false)
|
||||
.setContentTitle(title)
|
||||
.setContentIntent(savePendingIntent)
|
||||
.setStyle(new Notification.BigTextStyle().bigText(subTitle))
|
||||
.build();
|
||||
NotificationManager.from(mContext).notify(TYPE_SAVE, userId, notification);
|
||||
}
|
||||
|
||||
private void showAuthNotification(int userId, int sessionId, boolean usesFingerprint,
|
||||
private void showAuthNotification(boolean usesFingerprint,
|
||||
Bundle extras, int flags) {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
showAuthNotificationAsSystem(userId, sessionId, usesFingerprint, extras, flags);
|
||||
showAuthNotificationAsSystem(usesFingerprint, extras, flags);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
private void showAuthNotificationAsSystem(int userId, int sessionId,
|
||||
private void showAuthNotificationAsSystem(
|
||||
boolean usesFingerprint, Bundle extras, int flags) {
|
||||
final String title = "AutoFill Authentication";
|
||||
final StringBuilder subTitle = new StringBuilder("Provider require user authentication.\n");
|
||||
|
||||
final Intent authIntent = newNotificationIntent(userId, TYPE_AUTH_RESPONSE)
|
||||
.putExtra(EXTRA_SESSION_ID, sessionId);
|
||||
final Intent authIntent = newNotificationIntent(TYPE_AUTH_RESPONSE);
|
||||
if (extras != null) {
|
||||
authIntent.putExtra(EXTRA_AUTH_REQUIRED_EXTRAS, extras);
|
||||
}
|
||||
@@ -537,11 +380,11 @@ final class AutoFillUI {
|
||||
if (authPendingIntent != null) {
|
||||
notification.setContentIntent(authPendingIntent);
|
||||
}
|
||||
NotificationManager.from(mContext).notify(TYPE_AUTH_RESPONSE, userId, notification.build());
|
||||
NotificationManager.from(mContext).notify(mSession.mId, notification.build());
|
||||
}
|
||||
|
||||
private void dismissAuthNotification(int userId) {
|
||||
NotificationManager.from(mContext).cancel(TYPE_AUTH_RESPONSE, userId);
|
||||
private void dismissAuthNotification() {
|
||||
NotificationManager.from(mContext).cancel(mSession.mId);
|
||||
}
|
||||
|
||||
private Notification.Builder newNotificationBuilder() {
|
||||
|
||||
Reference in New Issue
Block a user