blob: 77e7b317cba4618c1814581309ec95f86f43049c [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.autofill;
import static com.android.server.autofill.Helper.DEBUG;
import static com.android.server.autofill.Helper.bundleToString;
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 android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.assist.AssistStructure;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.service.autofill.AutoFillService;
import android.service.autofill.AutoFillServiceInfo;
import android.service.autofill.IAutoFillAppCallback;
import android.service.autofill.IAutoFillServerCallback;
import android.service.autofill.IAutoFillService;
import android.service.voice.VoiceInteractionSession;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.autofill.AutoFillId;
import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* Bridge between the {@code system_server}'s {@link AutoFillManagerService} and the
* app's {@link IAutoFillService} implementation.
*
*/
final class AutoFillManagerServiceImpl {
private static final String TAG = "AutoFillManagerServiceImpl";
/** Used do assign ids to new ServerCallback instances. */
private static int sSessionIdCounter = 0;
private final int mUserId;
private final int mUid;
private final ComponentName mComponent;
private final Context mContext;
private final IActivityManager mAm;
private final Object mLock;
private final AutoFillServiceInfo mInfo;
private final AutoFillManagerService mManagerService;
private final AutoFillUI mUi;
// 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<QueuedRequest> mQueuedRequests = new LinkedList<>();
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
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(b/33197203): close any pending UI like account selection (or remove this
// receiver)
}
}
};
/**
* Cache of pending {@link Session}s, keyed by {@link Session#mId}.
*
* <p>They're kept until the {@link AutoFillService} finished handling a request, an error
* occurs, or the session times out.
*/
// TODO(b/33197203): need to make sure service is bound while callback is pending and/or
// use WeakReference
@GuardedBy("mLock")
private static final SparseArray<Session> mSessions = new SparseArray<>();
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) Slog.d(TAG, "onServiceConnected():" + name);
synchronized (mLock) {
mService = IAutoFillService.Stub.asInterface(service);
try {
mService.onConnected();
} catch (RemoteException e) {
Slog.w(TAG, "Exception on service.onConnected(): " + e);
return;
}
if (!mQueuedRequests.isEmpty()) {
if (DEBUG) Slog.d(TAG, "queued requests:" + mQueuedRequests.size());
}
for (final QueuedRequest request: mQueuedRequests) {
requestAutoFillLocked(request.activityToken, request.extras, request.flags,
false);
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) Slog.d(TAG, name + " disconnected");
synchronized (mLock) {
mService = null;
mManagerService.removeCachedServiceForUserLocked(mUserId);
}
}
};
/**
* Receiver of assist data from the app's {@link Activity}, uses the {@code resultData} as
* the {@link Session#mId}.
*/
private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
if (DEBUG) Slog.d(TAG, "resultCode on mAssistReceiver: " + resultCode);
final IBinder appBinder = resultData.getBinder(AutoFillService.KEY_CALLBACK);
if (appBinder == null) {
Slog.w(TAG, "no app callback on mAssistReceiver's resultData");
return;
}
final AssistStructure structure = resultData
.getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0);
final Session session;
synchronized (mLock) {
session = mSessions.get(resultCode);
if (session == null) {
Slog.w(TAG, "no server callback for id " + resultCode);
return;
}
session.mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
}
mService.autoFill(structure, session.mServerCallback, session.mExtras, flags);
}
};
@GuardedBy("mLock")
private IAutoFillService mService;
@GuardedBy("mLock")
private boolean mBound;
@GuardedBy("mLock")
private boolean mValid;
// Estimated time when the service will be evicted from the cache.
long mEstimateTimeOfDeath;
AutoFillManagerServiceImpl(AutoFillManagerService managerService, AutoFillUI ui,
Context context, Object lock, Handler handler, int userId, int uid,
ComponentName component, long ttl) {
mManagerService = managerService;
mUi = ui;
mContext = context;
mLock = lock;
mUserId = userId;
mUid = uid;
mComponent = component;
mAm = ActivityManager.getService();
setLifeExpectancy(ttl);
final AutoFillServiceInfo info;
try {
info = new AutoFillServiceInfo(context.getPackageManager(), component, mUserId);
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Auto-fill service not found: " + component, e);
mInfo = null;
mValid = false;
return;
}
mInfo = info;
if (mInfo.getParseError() != null) {
Slog.w(TAG, "Bad auto-fill service: " + mInfo.getParseError());
mValid = false;
return;
}
mValid = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mContext.registerReceiver(mBroadcastReceiver, filter, null, handler);
}
void setLifeExpectancy(long ttl) {
mEstimateTimeOfDeath = SystemClock.uptimeMillis() + ttl;
}
void startLocked() {
if (DEBUG) Slog.d(TAG, "startLocked()");
final Intent intent = new Intent(AutoFillService.SERVICE_INTERFACE);
intent.setComponent(mComponent);
mBound = mContext.bindServiceAsUser(intent, mConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, new UserHandle(mUserId));
if (!mBound) {
Slog.w(TAG, "Failed binding to auto-fill service " + mComponent);
return;
}
if (DEBUG) Slog.d(TAG, "Bound to " + mComponent);
}
/**
* Asks service to auto-fill an activity.
*
* @param activityToken activity token
* @param extras bundle to be passed to the {@link AutoFillService} method.
* @param flags optional flags.
*/
void requestAutoFill(@Nullable IBinder activityToken, @Nullable Bundle extras, int flags) {
synchronized (mLock) {
if (!mBound) {
Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
return;
}
}
// 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(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)
Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
if (topActivities.isEmpty()) {
Slog.w(TAG, "Could not get top activity");
return;
}
activityToken = topActivities.get(0);
}
final String historyItem = TimeUtils.formatForLogging(System.currentTimeMillis())
+ " - " + activityToken;
synchronized (mLock) {
mRequestHistory.add(historyItem);
requestAutoFillLocked(activityToken, extras, flags, true);
}
}
private void requestAutoFillLocked(IBinder activityToken, @Nullable Bundle extras, 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(new QueuedRequest(activityToken, extras, flags));
return;
}
final int sessionId = ++sSessionIdCounter;
final Session session = new Session(sessionId, extras);
mSessions.put(sessionId, session);
/*
* 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): add MetricsLogger call
if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, 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) {
// Should happen, it's a local call.
}
}
void stopLocked() {
if (DEBUG) Slog.d(TAG, "stopLocked()");
// Sanity check.
if (mService == null) {
Slog.w(TAG, "service already null on shutdown");
return;
}
try {
mService.onDisconnected();
} catch (RemoteException e) {
if (! (e instanceof DeadObjectException)) {
Slog.w(TAG, "Exception calling service.onDisconnected(): " + e);
}
} finally {
mService = null;
}
if (mBound) {
mContext.unbindService(mConnection);
mBound = false;
}
if (mValid) {
mContext.unregisterReceiver(mBroadcastReceiver);
}
}
/**
* 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);
// 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: ");
if (mInfo == null) {
pw.println("no info");
} else {
pw.println(mInfo.getParseError());
}
return;
}
final String prefix2 = prefix + " ";
pw.print(prefix); pw.print("mUserId="); pw.println(mUserId);
pw.print(prefix); pw.print("mUid="); pw.println(mUid);
pw.print(prefix); pw.print("mComponent="); pw.println(mComponent.flattenToShortString());
pw.print(prefix); pw.print("mService: "); pw.println(mService);
pw.print(prefix); pw.print("mBound="); pw.println(mBound);
pw.print(prefix); pw.print("mEstimateTimeOfDeath=");
TimeUtils.formatDuration(mEstimateTimeOfDeath, SystemClock.uptimeMillis(), pw);
pw.println();
if (DEBUG) {
// ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
pw.print(prefix); pw.println("ServiceInfo:");
mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
}
if (mRequestHistory.isEmpty()) {
pw.print(prefix); pw.println("No history");
} else {
pw.print(prefix); pw.println("History:");
for (int i = 0; i < mRequestHistory.size(); i++) {
pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mRequestHistory.get(i));
}
}
if (mQueuedRequests.isEmpty()) {
pw.print(prefix); pw.println("No queued requests");
} else {
pw.print(prefix); pw.println("Queued requests:");
for (int i = 0; i < mQueuedRequests.size(); i++) {
pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mQueuedRequests.get(i));
}
}
pw.print(prefix); pw.print("sSessionIdCounter="); pw.println(sSessionIdCounter);
final int size = mSessions.size();
if (size == 0) {
pw.print(prefix); pw.println("No sessions");
} else {
pw.print(prefix); pw.print(size); pw.println(" sessions:");
for (int i = 0; i < size; i++) {
pw.print(prefix2); pw.print(mSessions.keyAt(i));
final Session session = mSessions.valueAt(i);
if (session.mAppCallback == null) {
pw.println("(no appCallback)");
} else {
pw.print(" (app callback: "); pw.print(session.mAppCallback) ; pw.println(")");
}
}
pw.println();
}
}
@Override
public String toString() {
return "AutoFillManagerServiceImpl: [userId=" + mUserId + ", uid=" + mUid
+ ", component=" + mComponent.flattenToShortString() + "]";
}
private static final class QueuedRequest {
final IBinder activityToken;
final Bundle extras;
final int flags;
QueuedRequest(IBinder activityToken, Bundle extras, int flags) {
this.activityToken = activityToken;
this.extras = extras;
this.flags = flags;
}
@Override
public String toString() {
return "flags: " + flags + " token: " + activityToken;
}
}
/**
* A bridge between the {@link AutoFillService} implementation and the activity being
* auto-filled (represented through the {@link IAutoFillAppCallback}).
*
* <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
* example, when service return a {@link FillResponse} that contains all the fields needed
* to fill the activity but it requires authentication first, that response need to be held
* until the user authenticates or it times out.
*/
// TODO(b/33197203): make sure sessions are removed (and tested by CTS):
// - On all authentication scenarios.
// - When user does not interact back after a while.
// - When service is unbound.
private final class Session {
private final int mId;
private final Bundle mExtras;
private IAutoFillAppCallback mAppCallback;
// Token used on fingerprint authentication
private final IBinder mToken = new Binder();
private final IFingerprintService mFingerprintService;
@GuardedBy("mLock")
private FillResponse mResponseRequiringAuth;
@GuardedBy("mLock")
private Dataset mDatasetRequiringAuth;
// Used to auto-fill the activity directly when the FillCallback.onResponse() is called as
// the result of a successful user authentication on service's side.
@GuardedBy("mLock")
private boolean mAutoFillDirectly;
// TODO(b/33197203): use handler to handle results?
// TODO(b/33197203): handle all callback methods and/or cancelation?
private IFingerprintServiceReceiver mServiceReceiver =
new IFingerprintServiceReceiver.Stub() {
@Override
public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
if (DEBUG) Slog.d(TAG, "onEnrollResult()");
}
@Override
public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
if (DEBUG) Slog.d(TAG, "onAcquired()");
}
@Override
public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
if (DEBUG) Slog.d(TAG, "onAuthenticationSucceeded(): " + fp.getGroupId());
// First, check what was authenticated, a response or a dataset.
// Then, decide how to handle it:
// - If service provided data, handle them directly.
// - Otherwise, notify service.
mAutoFillDirectly = true;
if (mDatasetRequiringAuth != null) {
if (mDatasetRequiringAuth.isEmpty()) {
notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(),
FLAG_AUTHENTICATION_SUCCESS);
} else {
autoFillAppLocked(mDatasetRequiringAuth, true);
}
} else if (mResponseRequiringAuth != null) {
final List<Dataset> datasets = mResponseRequiringAuth.getDatasets();
if (datasets.isEmpty()) {
notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(),
FLAG_AUTHENTICATION_SUCCESS);
} else {
showResponseLocked(mResponseRequiringAuth, true);
}
} else {
Slog.w(TAG, "onAuthenticationSucceeded(): no response or dataset");
}
mUi.dismissFingerprintRequest(mUserId, true);
}
@Override
public void onAuthenticationFailed(long deviceId) {
if (DEBUG) Slog.d(TAG, "onAuthenticationFailed()");
// Do nothing - onError() will be called after a few failures...
}
@Override
public void onError(long deviceId, int error, int vendorCode) {
if (DEBUG) Slog.d(TAG, "onError()");
// Notify service so it can fallback to its own authentication
if (mDatasetRequiringAuth != null) {
notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(),
FLAG_AUTHENTICATION_ERROR);
} else if (mResponseRequiringAuth != null) {
notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(),
FLAG_AUTHENTICATION_ERROR);
} else {
Slog.w(TAG, "onError(): no response or dataset");
}
mUi.dismissFingerprintRequest(mUserId, false);
}
@Override
public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
if (DEBUG) Slog.d(TAG, "onRemoved()");
}
@Override
public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) {
if (DEBUG) Slog.d(TAG, "onEnumerated()");
}
};
private IAutoFillServerCallback mServerCallback = new IAutoFillServerCallback.Stub() {
@Override
public void showResponse(FillResponse response) {
// TODO(b/33197203): add MetricsLogger call
if (response == null) {
if (DEBUG) Slog.d(TAG, "showResponse(): null response");
removeSelf();
return;
}
synchronized (mLock) {
showResponseLocked(response, response.isAuthRequired());
}
}
@Override
public void showError(CharSequence message) {
// TODO(b/33197203): add MetricsLogger call
if (DEBUG) Slog.d(TAG, "showError(): " + message);
mUi.showError(message);
removeSelf();
}
@Override
public void highlightSavedFields(AutoFillId[] ids) {
// TODO(b/33197203): add MetricsLogger call
if (DEBUG) Slog.d(TAG, "highlightSavedFields(): " + Arrays.toString(ids));
mUi.highlightSavedFields(ids);
removeSelf();
}
@Override
public void unlockFillResponse(int flags) {
// TODO(b/33197203): add proper MetricsLogger calls?
if (DEBUG) Log.d(TAG, "unlockUser(): flags=" + flags);
synchronized (mLock) {
if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) {
if (mResponseRequiringAuth == null) {
Log.wtf(TAG, "unlockUser(): no mResponseRequiringAuth on flags "
+ flags);
removeSelf();
return;
}
final List<Dataset> datasets = mResponseRequiringAuth.getDatasets();
if (datasets.isEmpty()) {
Log.w(TAG, "unlockUser(): no dataset on previous response: "
+ mResponseRequiringAuth);
removeSelf();
return;
}
mAutoFillDirectly = true;
showResponseLocked(mResponseRequiringAuth, false);
}
// TODO(b/33197203): show UI error on authentication failure?
// Or let service handle it?
}
}
@Override
public void unlockDataset(Dataset dataset, int flags) {
// TODO(b/33197203): add proper MetricsLogger calls?
if (DEBUG) Log.d(TAG, "unlockDataset(): dataset=" + dataset + ", flags=" + flags);
if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) {
autoFillAppLocked(dataset != null ? dataset : mDatasetRequiringAuth, true);
return;
}
removeSelf();
}
};
private Session(int id, Bundle extras) {
this.mId = id;
this.mExtras = extras;
this.mFingerprintService = IFingerprintService.Stub
.asInterface(ServiceManager.getService("fingerprint"));
}
private void showResponseLocked(FillResponse response, boolean authRequired) {
if (DEBUG) Slog.d(TAG, "showResponse(directly=" + mAutoFillDirectly
+ ", authRequired=" + authRequired +"):" + response);
if (mAutoFillDirectly && response != null) {
final List<Dataset> datasets = response.getDatasets();
if (datasets.size() == 1) {
// User authenticated and provider returned just 1 dataset - auto-fill it now!
final Dataset dataset = datasets.get(0);
if (DEBUG) Slog.d(TAG, "auto-filling directly from auth: " + dataset);
autoFillAppLocked(dataset, true);
return;
}
}
if (!authRequired) {
// TODO(b/33197203): add MetricsLogger call
mUi.showOptions(mUserId, mId, response);
return;
}
// Handles response that requires authentication.
// TODO(b/33197203): add MetricsLogger call, including if fingerprint requested
mResponseRequiringAuth = response;
final boolean requiresFingerprint = response.hasCryptoObject();
if (requiresFingerprint) {
// TODO(b/33197203): check if fingerprint is available first and call error callback
// with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
// Start scanning for the fingerprint.
scanFingerprint(response.getCryptoObjectOpId());
}
// Displays the message asking the user to tap (or fingerprint) for AutoFill.
mUi.showFillResponseAuthenticationRequest(mUserId, mId, requiresFingerprint,
response.getExtras(), response.getFlags());
}
void autoFill(Dataset dataset) {
synchronized (mLock) {
// Autofill it directly...
if (!dataset.isAuthRequired()) {
autoFillAppLocked(dataset, true);
return;
}
// ...or handle authentication.
mDatasetRequiringAuth = dataset;
final boolean requiresFingerprint = dataset.hasCryptoObject();
if (requiresFingerprint) {
// TODO(b/33197203): check if fingerprint is available first and call error callback
// with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
// Start scanning for the fingerprint.
scanFingerprint(dataset.getCryptoObjectOpId());
// Displays the message asking the user to tap (or fingerprint) for AutoFill.
mUi.showDatasetFingerprintAuthenticationRequest(dataset);
} else {
try {
mService.authenticateDataset(dataset.getExtras(),
FLAG_AUTHENTICATION_REQUESTED);
} catch (RemoteException e) {
Slog.w(TAG, "Error authenticating dataset: " + e);
}
}
}
}
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, since save is
// not integrated with IME yet.
mUi.showSaveNotification(mUserId, null, dataset);
} catch (RemoteException e) {
Slog.w(TAG, "Error auto-filling activity: " + e);
}
if (removeSelf) {
removeSelf();
}
}
private void scanFingerprint(long opId) {
// TODO(b/33197203): add MetricsLogger call
if (DEBUG) Slog.d(TAG, "Starting fingerprint scan for op id: " + opId);
// TODO(b/33197203): since we're clearing the AutoFillService's identity, make sure
// this method is only called at the proper times, otherwise a malicious provider could
// keep the callback refence to bypass the check
final long token = Binder.clearCallingIdentity();
try {
// TODO(b/33197203): set a timeout?
mFingerprintService.authenticate(mToken, opId, mUserId, mServiceReceiver, 0, null);
} catch (RemoteException e) {
// Local call, shouldn't happen.
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void removeSelf() {
synchronized (mLock) {
removeSessionLocked(mId);
}
}
}
}