"core/java/android/hardware/display/IDisplayManagerCallback.aidl",
"core/java/android/hardware/display/IVirtualDisplayCallback.aidl",
"core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl",
+ "core/java/android/hardware/face/IFaceService.aidl",
+ "core/java/android/hardware/face/IFaceServiceLockoutResetCallback.aidl",
+ "core/java/android/hardware/face/IFaceServiceReceiver.aidl",
"core/java/android/hardware/fingerprint/IFingerprintService.aidl",
"core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl",
"core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl",
public static final int OP_START_FOREGROUND = 76;
/** @hide */
public static final int OP_BLUETOOTH_SCAN = 77;
+ /** @hide Use the face authentication API. */
+ public static final int OP_USE_FACE = 78;
/** @hide */
- public static final int _NUM_OP = 78;
+ public static final int _NUM_OP = 79;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
/** @hide */
public static final String OPSTR_BLUETOOTH_SCAN = "android:bluetooth_scan";
+ /** @hide Use the face authentication API. */
+ public static final String OPSTR_USE_FACE = "android:use_FACE";
+
+
+
// Warning: If an permission is added here it also has to be added to
// com.android.packageinstaller.permission.utils.EventLogger
private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
OP_MANAGE_IPSEC_TUNNELS, // MANAGE_IPSEC_HANDOVERS
OP_START_FOREGROUND, // START_FOREGROUND
OP_COARSE_LOCATION, // BLUETOOTH_SCAN
+ OP_USE_FACE, // FACE
};
/**
OPSTR_MANAGE_IPSEC_TUNNELS,
OPSTR_START_FOREGROUND,
OPSTR_BLUETOOTH_SCAN,
+ OPSTR_USE_FACE,
};
/**
"MANAGE_IPSEC_TUNNELS",
"START_FOREGROUND",
"BLUETOOTH_SCAN",
+ "USE_FACE",
};
/**
null, // no permission for OP_MANAGE_IPSEC_TUNNELS
Manifest.permission.FOREGROUND_SERVICE,
null, // no permission for OP_BLUETOOTH_SCAN
+ Manifest.permission.USE_BIOMETRIC,
};
/**
null, // MANAGE_IPSEC_TUNNELS
null, // START_FOREGROUND
null, // maybe should be UserManager.DISALLOW_SHARE_LOCATION, //BLUETOOTH_SCAN
+ null, // USE_FACE
};
/**
false, // MANAGE_IPSEC_HANDOVERS
false, // START_FOREGROUND
true, // BLUETOOTH_SCAN
+ false, // USE_FACE
};
/**
AppOpsManager.MODE_ERRORED, // MANAGE_IPSEC_TUNNELS
AppOpsManager.MODE_ALLOWED, // OP_START_FOREGROUND
AppOpsManager.MODE_ALLOWED, // OP_BLUETOOTH_SCAN
+ AppOpsManager.MODE_ALLOWED, // USE_FACE
};
/**
false, // MANAGE_IPSEC_TUNNELS
false, // START_FOREGROUND
false, // BLUETOOTH_SCAN
+ false, // USE_FACE
};
/**
import android.hardware.SystemSensorManager;
import android.hardware.camera2.CameraManager;
import android.hardware.display.DisplayManager;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceService;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.hdmi.HdmiControlManager;
return new FingerprintManager(ctx.getOuterContext(), service);
}});
+ registerService(Context.FACE_SERVICE, FaceManager.class,
+ new CachedServiceFetcher<FaceManager>() {
+ @Override
+ public FaceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ final IBinder binder;
+ if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
+ binder = ServiceManager.getServiceOrThrow(Context.FACE_SERVICE);
+ } else {
+ binder = ServiceManager.getService(Context.FACE_SERVICE);
+ }
+ IFaceService service = IFaceService.Stub.asInterface(binder);
+ return new FaceManager(ctx.getOuterContext(), service);
+ }
+ });
+
registerService(Context.TV_INPUT_SERVICE, TvInputManager.class,
new CachedServiceFetcher<TvInputManager>() {
@Override
/**
* Called by a device/profile owner to set the timeout after which unlocking with secondary, non
- * strong auth (e.g. fingerprint, trust agents) times out, i.e. the user has to use a strong
- * authentication method like password, pin or pattern.
+ * strong auth (e.g. fingerprint, face, trust agents) times out, i.e. the user has to use a
+ * strong authentication method like password, pin or pattern.
*
* <p>This timeout is used internally to reset the timer to require strong auth again after
* specified timeout each time it has been successfully used.
| DevicePolicyManager.KEYGUARD_DISABLE_IRIS
| DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
-
/**
* Disable all current and future keyguard customizations.
*/
/**
* @hide
*/
- public void reportFailedFingerprintAttempt(int userHandle) {
+ public void reportFailedBiometricAttempt(int userHandle) {
if (mService != null) {
try {
- mService.reportFailedFingerprintAttempt(userHandle);
+ mService.reportFailedBiometricAttempt(userHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
/**
* @hide
*/
- public void reportSuccessfulFingerprintAttempt(int userHandle) {
+ public void reportSuccessfulBiometricAttempt(int userHandle) {
if (mService != null) {
try {
- mService.reportSuccessfulFingerprintAttempt(userHandle);
+ mService.reportSuccessfulBiometricAttempt(userHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
void reportPasswordChanged(int userId);
void reportFailedPasswordAttempt(int userHandle);
void reportSuccessfulPasswordAttempt(int userHandle);
- void reportFailedFingerprintAttempt(int userHandle);
- void reportSuccessfulFingerprintAttempt(int userHandle);
+ void reportFailedBiometricAttempt(int userHandle);
+ void reportSuccessfulBiometricAttempt(int userHandle);
void reportKeyguardDismissed(int userHandle);
void reportKeyguardSecured(int userHandle);
package android.app.trust;
import android.app.trust.ITrustListener;
+import android.hardware.biometrics.BiometricSourceType;
/**
* System private API to comunicate with trust service.
boolean isDeviceLocked(int userId);
boolean isDeviceSecure(int userId);
boolean isTrustUsuallyManaged(int userId);
- void unlockedByFingerprintForUser(int userId);
- void clearAllFingerprints();
+ void unlockedByBiometricForUser(int userId, in BiometricSourceType source);
+ void clearAllBiometricRecognized(in BiometricSourceType target);
}
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
}
/**
- * Updates the trust state for the user due to the user unlocking via fingerprint.
- * Should only be called if user authenticated via fingerprint and bouncer can be skipped.
+ * Updates the trust state for the user due to the user unlocking via a biometric sensor.
+ * Should only be called if user authenticated via fingerprint, face, or iris and bouncer
+ * can be skipped.
+ *
* @param userId
*/
@RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
- public void unlockedByFingerprintForUser(int userId) {
+ public void unlockedByBiometricForUser(int userId, BiometricSourceType source) {
try {
- mService.unlockedByFingerprintForUser(userId);
+ mService.unlockedByBiometricForUser(userId, source);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Clears authenticated fingerprints for all users.
+ * Clears authentication by the specified biometric type for all users.
*/
@RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
- public void clearAllFingerprints() {
+ public void clearAllBiometricRecognized(BiometricSourceType source) {
try {
- mService.clearAllFingerprints();
+ mService.clearAllBiometricRecognized(source);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
NSD_SERVICE,
AUDIO_SERVICE,
FINGERPRINT_SERVICE,
+ //@hide: FACE_SERVICE,
MEDIA_ROUTER_SERVICE,
TELEPHONY_SERVICE,
TELEPHONY_SUBSCRIPTION_SERVICE,
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.face.FaceManager} for handling management
+ * of face authentication.
+ *
+ * @hide
+ * @see #getSystemService
+ * @see android.hardware.face.FaceManager
+ */
+ public static final String FACE_SERVICE = "face";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.media.MediaRouter} for controlling and managing
* routing of media.
*
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has biometric hardware to detect a fingerprint.
- */
+ */
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_FINGERPRINT = "android.hardware.fingerprint";
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has biometric hardware to perform face authentication.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FACE = "android.hardware.face";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports portrait orientation
* screens. For backwards compatibility, you can assume that if neither
* this nor {@link #FEATURE_SCREEN_LANDSCAPE} is set then the device supports
--- /dev/null
+/*
+ * Copyright (C) 2018 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.hardware.biometrics;
+
+/**
+ * @hide
+ */
+parcelable BiometricSourceType;
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2018 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.hardware.biometrics;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public enum BiometricSourceType implements Parcelable {
+ FINGERPRINT,
+ FACE,
+ IRIS;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(name());
+ }
+
+ public static final Creator<BiometricSourceType> CREATOR = new Creator<BiometricSourceType>() {
+ @Override
+ public BiometricSourceType createFromParcel(final Parcel source) {
+ return BiometricSourceType.valueOf(source.readString());
+ }
+
+ @Override
+ public BiometricSourceType[] newArray(final int size) {
+ return new BiometricSourceType[size];
+ }
+ };
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 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.hardware.face;
+
+/**
+ * @hide
+ */
+parcelable Face;
--- /dev/null
+/*
+ * Copyright (C) 2018 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.hardware.face;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Container for face metadata.
+ *
+ * @hide
+ */
+public final class Face extends BiometricAuthenticator.BiometricIdentifier {
+ private CharSequence mName;
+ private int mFaceId;
+ private long mDeviceId; // physical device this face is associated with
+
+ public Face(CharSequence name, int faceId, long deviceId) {
+ mName = name;
+ mFaceId = faceId;
+ mDeviceId = deviceId;
+ }
+
+ private Face(Parcel in) {
+ mName = in.readString();
+ mFaceId = in.readInt();
+ mDeviceId = in.readLong();
+ }
+
+ /**
+ * Gets the human-readable name for the given fingerprint.
+ * @return name given to finger
+ */
+ public CharSequence getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the device-specific finger id. Used by Settings to map a name to a specific
+ * fingerprint template.
+ * @return device-specific id for this finger
+ * @hide
+ */
+ public int getFaceId() {
+ return mFaceId;
+ }
+
+ /**
+ * Device this face belongs to.
+ *
+ * @hide
+ */
+ public long getDeviceId() {
+ return mDeviceId;
+ }
+
+ /**
+ * Describes the contents.
+ * @return
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Writes to a parcel.
+ * @param out
+ * @param flags Additional flags about how the object should be written.
+ */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mName.toString());
+ out.writeInt(mFaceId);
+ out.writeLong(mDeviceId);
+ }
+
+ public static final Parcelable.Creator<Face> CREATOR = new Parcelable.Creator<Face>() {
+ public Face createFromParcel(Parcel in) {
+ return new Face(in);
+ }
+
+ public Face[] newArray(int size) {
+ return new Face[size];
+ }
+ };
+}
--- /dev/null
+/**
+ * Copyright (C) 2018 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.hardware.face;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_FACE;
+import static android.Manifest.permission.USE_BIOMETRIC;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.CancellationSignal.OnCancelListener;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.security.keystore.AndroidKeyStoreProvider;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+import java.security.Signature;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
+/**
+ * A class that coordinates access to the face authentication hardware.
+ * @hide
+ */
+@SystemService(Context.FACE_SERVICE)
+public class FaceManager {
+ /**
+ * The hardware is unavailable. Try again later.
+ */
+ public static final int FACE_ERROR_HW_UNAVAILABLE = 1;
+ /**
+ * Error state returned when the sensor was unable to process the current image.
+ */
+ public static final int FACE_ERROR_UNABLE_TO_PROCESS = 2;
+ /**
+ * Error state returned when the current request has been running too long. This is intended to
+ * prevent programs from waiting for the face authentication sensor indefinitely. The timeout is
+ * platform and sensor-specific, but is generally on the order of 30 seconds.
+ */
+ public static final int FACE_ERROR_TIMEOUT = 3;
+ /**
+ * Error state returned for operations like enrollment; the operation cannot be completed
+ * because there's not enough storage remaining to complete the operation.
+ */
+ public static final int FACE_ERROR_NO_SPACE = 4;
+ /**
+ * The operation was canceled because the face authentication sensor is unavailable. For
+ * example, this may happen when the user is switched, the device is locked or another pending
+ * operation prevents or disables it.
+ */
+ public static final int FACE_ERROR_CANCELED = 5;
+ /**
+ * The {@link FaceManager#remove} call failed. Typically this will happen when the
+ * provided face id was incorrect.
+ *
+ * @hide
+ */
+ public static final int FACE_ERROR_UNABLE_TO_REMOVE = 6;
+ /**
+ * The operation was canceled because the API is locked out due to too many attempts.
+ * This occurs after 5 failed attempts, and lasts for 30 seconds.
+ */
+ public static final int FACE_ERROR_LOCKOUT = 7;
+ /**
+ * Hardware vendors may extend this list if there are conditions that do not fall under one of
+ * the above categories. Vendors are responsible for providing error strings for these errors.
+ * These messages are typically reserved for internal operations such as enrollment, but may be
+ * used to express vendor errors not covered by the ones in HAL h file. Applications are
+ * expected to show the error message string if they happen, but are advised not to rely on the
+ * message id since they will be device and vendor-specific
+ */
+ public static final int FACE_ERROR_VENDOR = 8;
+ //
+ // Error messages from face authentication hardware during initialization, enrollment,
+ // authentication or removal. Must agree with the list in HAL h file
+ //
+ /**
+ * The operation was canceled because FACE_ERROR_LOCKOUT occurred too many times.
+ * Face authentication is disabled until the user unlocks with strong authentication
+ * (PIN/Pattern/Password)
+ */
+ public static final int FACE_ERROR_LOCKOUT_PERMANENT = 9;
+ /**
+ * The user canceled the operation. Upon receiving this, applications should use alternate
+ * authentication (e.g. a password). The application should also provide the means to return
+ * to face authentication, such as a "use face authentication" button.
+ */
+ public static final int FACE_ERROR_USER_CANCELED = 10;
+ /**
+ * The user does not have a face enrolled.
+ */
+ public static final int FACE_ERROR_NOT_ENROLLED = 11;
+ /**
+ * The device does not have a face sensor. This message will propagate if the calling app
+ * ignores the result from PackageManager.hasFeature(FEATURE_FACE) and calls
+ * this API anyway. Apps should always check for the feature before calling this API.
+ */
+ public static final int FACE_ERROR_HW_NOT_PRESENT = 12;
+ /**
+ * @hide
+ */
+ public static final int FACE_ERROR_VENDOR_BASE = 1000;
+ /**
+ * The image acquired was good.
+ */
+ public static final int FACE_ACQUIRED_GOOD = 0;
+ /**
+ * The face image was not good enough to process due to a detected condition.
+ * (See {@link #FACE_ACQUIRED_TOO_BRIGHT or @link #FACE_ACQUIRED_TOO_DARK}).
+ */
+ public static final int FACE_ACQUIRED_INSUFFICIENT = 1;
+ /**
+ * The face image was too bright due to too much ambient light.
+ * For example, it's reasonable to return this after multiple
+ * {@link #FACE_ACQUIRED_INSUFFICIENT}
+ * The user is expected to take action to retry in better lighting conditions
+ * when this is returned.
+ */
+ public static final int FACE_ACQUIRED_TOO_BRIGHT = 2;
+ /**
+ * The face image was too dark due to illumination light obscured.
+ * For example, it's reasonable to return this after multiple
+ * {@link #FACE_ACQUIRED_INSUFFICIENT}
+ * The user is expected to take action to retry in better lighting conditions
+ * when this is returned.
+ */
+ public static final int FACE_ACQUIRED_TOO_DARK = 3;
+ /**
+ * The detected face is too close to the sensor, and the image can't be processed.
+ * The user should be informed to move farther from the sensor when this is returned.
+ */
+ public static final int FACE_ACQUIRED_TOO_CLOSE = 4;
+ /**
+ * The detected face is too small, as the user might be too far from the sensor.
+ * The user should be informed to move closer to the sensor when this is returned.
+ */
+ public static final int FACE_ACQUIRED_TOO_FAR = 5;
+ /**
+ * Only the upper part of the face was detected. The sensor field of view is too high.
+ * The user should be informed to move up with respect to the sensor when this is returned.
+ */
+ public static final int FACE_ACQUIRED_TOO_HIGH = 6;
+ /**
+ * Only the lower part of the face was detected. The sensor field of view is too low.
+ * The user should be informed to move down with respect to the sensor when this is returned.
+ */
+ public static final int FACE_ACQUIRED_TOO_LOW = 7;
+
+ //
+ // Image acquisition messages. Must agree with those in HAL h file
+ //
+ /**
+ * Only the right part of the face was detected. The sensor field of view is too far right.
+ * The user should be informed to move to the right with respect to the sensor
+ * when this is returned.
+ */
+ public static final int FACE_ACQUIRED_TOO_RIGHT = 8;
+ /**
+ * Only the left part of the face was detected. The sensor field of view is too far left.
+ * The user should be informed to move to the left with respect to the sensor
+ * when this is returned.
+ */
+ public static final int FACE_ACQUIRED_TOO_LEFT = 9;
+ /**
+ * User's gaze strayed too far from the sensor causing significant parts of the user's face
+ * to be hidden.
+ * The user should be informed to turn the face front to the sensor.
+ */
+ public static final int FACE_ACQUIRED_POOR_GAZE = 10;
+ /**
+ * No face was detected in front of the sensor.
+ * The user should be informed to point the sensor to a face when this is returned.
+ */
+ public static final int FACE_ACQUIRED_NOT_DETECTED = 11;
+ /**
+ * Hardware vendors may extend this list if there are conditions that do not fall under one of
+ * the above categories. Vendors are responsible for providing error strings for these errors.
+ *
+ * @hide
+ */
+ public static final int FACE_ACQUIRED_VENDOR = 12;
+ /**
+ * @hide
+ */
+ public static final int FACE_ACQUIRED_VENDOR_BASE = 1000;
+ private static final String TAG = "FaceManager";
+ private static final boolean DEBUG = true;
+ private static final int MSG_ENROLL_RESULT = 100;
+ private static final int MSG_ACQUIRED = 101;
+ private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
+ private static final int MSG_AUTHENTICATION_FAILED = 103;
+ private static final int MSG_ERROR = 104;
+ private static final int MSG_REMOVED = 105;
+ private final Context mContext;
+ private IFaceService mService;
+ private IBinder mToken = new Binder();
+ private AuthenticationCallback mAuthenticationCallback;
+ private EnrollmentCallback mEnrollmentCallback;
+ private RemovalCallback mRemovalCallback;
+ private CryptoObject mCryptoObject;
+ private Face mRemovalFace;
+ private Handler mHandler;
+ private IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
+
+ @Override // binder call
+ public void onEnrollResult(long deviceId, int faceId, int remaining) {
+ mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,
+ new Face(null, faceId, deviceId)).sendToTarget();
+ }
+
+ @Override // binder call
+ public void onAcquired(long deviceId, int acquireInfo, int vendorCode) {
+ mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, deviceId).sendToTarget();
+ }
+
+ @Override // binder call
+ public void onAuthenticationSucceeded(long deviceId, Face face) {
+ mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, face).sendToTarget();
+ }
+
+ @Override // binder call
+ public void onAuthenticationFailed(long deviceId) {
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+ }
+
+ @Override // binder call
+ public void onError(long deviceId, int error, int vendorCode) {
+ mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();
+ }
+
+ @Override // binder call
+ public void onRemoved(long deviceId, int faceId, int remaining) {
+ mHandler.obtainMessage(MSG_REMOVED, remaining, 0,
+ new Face(null, faceId, deviceId)).sendToTarget();
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public FaceManager(Context context, IFaceService service) {
+ mContext = context;
+ mService = service;
+ if (mService == null) {
+ Slog.v(TAG, "FaceAuthenticationManagerService was null");
+ }
+ mHandler = new MyHandler(context);
+ }
+
+ /**
+ * Request authentication of a crypto object. This call operates the face recognition hardware
+ * and starts capturing images. It terminates when
+ * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
+ * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
+ * which point the object is no longer valid. The operation can be canceled by using the
+ * provided cancel object.
+ *
+ * @param crypto object associated with the call or null if none required.
+ * @param cancel an object that can be used to cancel authentication
+ * @param flags optional flags; should be 0
+ * @param callback an object to receive authentication events
+ * @param handler an optional handler to handle callback events
+ * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
+ * by
+ * <a href="{@docRoot}training/articles/keystore.html">Android
+ * Keystore facility</a>.
+ * @throws IllegalStateException if the crypto primitive is not initialized.
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
+ int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) {
+ authenticate(crypto, cancel, flags, callback, handler, UserHandle.myUserId());
+ }
+
+ /**
+ * Use the provided handler thread for events.
+ */
+ private void useHandler(Handler handler) {
+ if (handler != null) {
+ mHandler = new MyHandler(handler.getLooper());
+ } else if (mHandler.getLooper() != mContext.getMainLooper()) {
+ mHandler = new MyHandler(mContext.getMainLooper());
+ }
+ }
+
+ /**
+ * Per-user version
+ *
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
+ int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) {
+ if (callback == null) {
+ throw new IllegalArgumentException("Must supply an authentication callback");
+ }
+
+ if (cancel != null) {
+ if (cancel.isCanceled()) {
+ Log.w(TAG, "authentication already canceled");
+ return;
+ } else {
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
+ }
+ }
+
+ if (mService != null) {
+ try {
+ useHandler(handler);
+ mAuthenticationCallback = callback;
+ mCryptoObject = crypto;
+ long sessionId = crypto != null ? crypto.getOpId() : 0;
+ mService.authenticate(mToken, sessionId, mServiceReceiver, flags,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Remote exception while authenticating: ", e);
+ if (callback != null) {
+ // Though this may not be a hardware issue, it will cause apps to give up or try
+ // again later.
+ callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
+ getErrorString(FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
+ }
+ }
+ }
+ }
+
+ /**
+ * Request face authentication enrollment. This call operates the face authentication hardware
+ * and starts capturing images. Progress will be indicated by callbacks to the
+ * {@link EnrollmentCallback} object. It terminates when
+ * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or
+ * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at
+ * which point the object is no longer valid. The operation can be canceled by using the
+ * provided cancel object.
+ *
+ * @param token a unique token provided by a recent creation or verification of device
+ * credentials (e.g. pin, pattern or password).
+ * @param cancel an object that can be used to cancel enrollment
+ * @param flags optional flags
+ * @param userId the user to whom this face will belong to
+ * @param callback an object to receive enrollment events
+ * @hide
+ */
+ @RequiresPermission(MANAGE_FACE)
+ public void enroll(byte[] token, CancellationSignal cancel, int flags,
+ int userId, EnrollmentCallback callback) {
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = getCurrentUserId();
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Must supply an enrollment callback");
+ }
+
+ if (cancel != null) {
+ if (cancel.isCanceled()) {
+ Log.w(TAG, "enrollment already canceled");
+ return;
+ } else {
+ cancel.setOnCancelListener(new OnEnrollCancelListener());
+ }
+ }
+
+ if (mService != null) {
+ try {
+ mEnrollmentCallback = callback;
+ mService.enroll(mToken, token, userId, mServiceReceiver, flags,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Remote exception in enroll: ", e);
+ if (callback != null) {
+ // Though this may not be a hardware issue, it will cause apps to give up or try
+ // again later.
+ callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
+ getErrorString(FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
+ }
+ }
+ }
+ }
+
+ /**
+ * Requests a pre-enrollment auth token to tie enrollment to the confirmation of
+ * existing device credentials (e.g. pin/pattern/password).
+ *
+ * @hide
+ */
+ @RequiresPermission(MANAGE_FACE)
+ public long preEnroll() {
+ long result = 0;
+ if (mService != null) {
+ try {
+ result = mService.preEnroll(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Finishes enrollment and cancels the current auth token.
+ *
+ * @hide
+ */
+ @RequiresPermission(MANAGE_FACE)
+ public int postEnroll() {
+ int result = 0;
+ if (mService != null) {
+ try {
+ result = mService.postEnroll(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Sets the active user. This is meant to be used to select the current profile for enrollment
+ * to allow separate enrolled faces for a work profile
+ *
+ * @hide
+ */
+ @RequiresPermission(MANAGE_FACE)
+ public void setActiveUser(int userId) {
+ if (mService != null) {
+ try {
+ mService.setActiveUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Remove given face template from face hardware and/or protected storage.
+ *
+ * @param face the face item to remove
+ * @param userId the user who this face belongs to
+ * @param callback an optional callback to verify that face templates have been
+ * successfully removed. May be null if no callback is required.
+ * @hide
+ */
+ @RequiresPermission(MANAGE_FACE)
+ public void remove(Face face, int userId, RemovalCallback callback) {
+ if (mService != null) {
+ try {
+ mRemovalCallback = callback;
+ mRemovalFace = face;
+ mService.remove(mToken, userId, mServiceReceiver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Remote exception in remove: ", e);
+ if (callback != null) {
+ callback.onRemovalError(face, FACE_ERROR_HW_UNAVAILABLE,
+ getErrorString(FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
+ }
+ }
+ }
+ }
+
+ /**
+ * Obtain the enrolled face template.
+ *
+ * @return the current face item
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ public Face getEnrolledFace(int userId) {
+ if (mService != null) {
+ try {
+ return mService.getEnrolledFace(userId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Obtain the enrolled face template.
+ *
+ * @return the current face item
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ public Face getEnrolledFace() {
+ return getEnrolledFace(UserHandle.myUserId());
+ }
+
+ /**
+ * Determine if there is a face enrolled.
+ *
+ * @return true if a face is enrolled, false otherwise
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ public boolean hasEnrolledFace() {
+ if (mService != null) {
+ try {
+ return mService.hasEnrolledFace(
+ UserHandle.myUserId(), mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ USE_BIOMETRIC,
+ INTERACT_ACROSS_USERS})
+ public boolean hasEnrolledFace(int userId) {
+ if (mService != null) {
+ try {
+ return mService.hasEnrolledFace(userId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine if face authentication sensor hardware is present and functional.
+ *
+ * @return true if hardware is present and functional, false otherwise.
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ public boolean isHardwareDetected() {
+ if (mService != null) {
+ try {
+ long deviceId = 0; /* TODO: plumb hardware id to FPMS */
+ return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Log.w(TAG, "isFaceHardwareDetected(): Service not connected!");
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves the authenticator token for binding keys to the lifecycle
+ * of the calling user's face. Used only by internal clients.
+ *
+ * @hide
+ */
+ public long getAuthenticatorId() {
+ if (mService != null) {
+ try {
+ return mService.getAuthenticatorId(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Log.w(TAG, "getAuthenticatorId(): Service not connected!");
+ }
+ return 0;
+ }
+
+ /**
+ * Reset the lockout timer when asked to do so by keyguard.
+ *
+ * @param token an opaque token returned by password confirmation.
+ * @hide
+ */
+ public void resetTimeout(byte[] token) {
+ if (mService != null) {
+ try {
+ mService.resetTimeout(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Log.w(TAG, "resetTimeout(): Service not connected!");
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void addLockoutResetCallback(final LockoutResetCallback callback) {
+ if (mService != null) {
+ try {
+ final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+ mService.addLockoutResetCallback(
+ new IFaceServiceLockoutResetCallback.Stub() {
+
+ @Override
+ public void onLockoutReset(long deviceId,
+ IRemoteCallback serverCallback)
+ throws RemoteException {
+ try {
+ final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK,
+ "faceLockoutResetCallback");
+ wakeLock.acquire();
+ mHandler.post(() -> {
+ try {
+ callback.onLockoutReset();
+ } finally {
+ wakeLock.release();
+ }
+ });
+ } finally {
+ serverCallback.sendResult(null /* data */);
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Log.w(TAG, "addLockoutResetCallback(): Service not connected!");
+ }
+ }
+
+ private int getCurrentUserId() {
+ try {
+ return ActivityManager.getService().getCurrentUser().id;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void cancelEnrollment() {
+ if (mService != null) {
+ try {
+ mService.cancelEnrollment(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private void cancelAuthentication(CryptoObject cryptoObject) {
+ if (mService != null) {
+ try {
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private String getErrorString(int errMsg, int vendorCode) {
+ switch (errMsg) {
+ case FACE_ERROR_HW_UNAVAILABLE:
+ return mContext.getString(
+ com.android.internal.R.string.face_error_hw_not_available);
+ case FACE_ERROR_UNABLE_TO_PROCESS:
+ return mContext.getString(
+ com.android.internal.R.string.face_error_unable_to_process);
+ case FACE_ERROR_TIMEOUT:
+ return mContext.getString(com.android.internal.R.string.face_error_timeout);
+ case FACE_ERROR_NO_SPACE:
+ return mContext.getString(com.android.internal.R.string.face_error_no_space);
+ case FACE_ERROR_CANCELED:
+ return mContext.getString(com.android.internal.R.string.face_error_canceled);
+ case FACE_ERROR_LOCKOUT:
+ return mContext.getString(com.android.internal.R.string.face_error_lockout);
+ case FACE_ERROR_LOCKOUT_PERMANENT:
+ return mContext.getString(
+ com.android.internal.R.string.face_error_lockout_permanent);
+ case FACE_ERROR_NOT_ENROLLED:
+ return mContext.getString(com.android.internal.R.string.face_error_not_enrolled);
+ case FACE_ERROR_HW_NOT_PRESENT:
+ return mContext.getString(com.android.internal.R.string.face_error_hw_not_present);
+ case FACE_ERROR_VENDOR: {
+ String[] msgArray = mContext.getResources().getStringArray(
+ com.android.internal.R.array.face_error_vendor);
+ if (vendorCode < msgArray.length) {
+ return msgArray[vendorCode];
+ }
+ }
+ }
+ Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
+ return null;
+ }
+
+ private String getAcquiredString(int acquireInfo, int vendorCode) {
+ switch (acquireInfo) {
+ case FACE_ACQUIRED_GOOD:
+ return null;
+ case FACE_ACQUIRED_INSUFFICIENT:
+ return mContext.getString(R.string.face_acquired_insufficient);
+ case FACE_ACQUIRED_TOO_BRIGHT:
+ return mContext.getString(R.string.face_acquired_too_bright);
+ case FACE_ACQUIRED_TOO_DARK:
+ return mContext.getString(R.string.face_acquired_too_dark);
+ case FACE_ACQUIRED_TOO_CLOSE:
+ return mContext.getString(R.string.face_acquired_too_close);
+ case FACE_ACQUIRED_TOO_FAR:
+ return mContext.getString(R.string.face_acquired_too_far);
+ case FACE_ACQUIRED_TOO_HIGH:
+ return mContext.getString(R.string.face_acquired_too_high);
+ case FACE_ACQUIRED_TOO_LOW:
+ return mContext.getString(R.string.face_acquired_too_low);
+ case FACE_ACQUIRED_TOO_RIGHT:
+ return mContext.getString(R.string.face_acquired_too_right);
+ case FACE_ACQUIRED_TOO_LEFT:
+ return mContext.getString(R.string.face_acquired_too_left);
+ case FACE_ACQUIRED_POOR_GAZE:
+ return mContext.getString(R.string.face_acquired_poor_gaze);
+ case FACE_ACQUIRED_NOT_DETECTED:
+ return mContext.getString(R.string.face_acquired_not_detected);
+ case FACE_ACQUIRED_VENDOR: {
+ String[] msgArray = mContext.getResources().getStringArray(
+ R.array.face_acquired_vendor);
+ if (vendorCode < msgArray.length) {
+ return msgArray[vendorCode];
+ }
+ }
+ }
+ Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
+ return null;
+ }
+
+ /**
+ * A wrapper class for the crypto objects supported by FaceAuthenticationManager.
+ */
+ public static final class CryptoObject {
+
+ private final Object mCrypto;
+
+ public CryptoObject(@NonNull Signature signature) {
+ mCrypto = signature;
+ }
+
+ public CryptoObject(@NonNull Cipher cipher) {
+ mCrypto = cipher;
+ }
+
+ public CryptoObject(@NonNull Mac mac) {
+ mCrypto = mac;
+ }
+
+ /**
+ * Get {@link Signature} object.
+ *
+ * @return {@link Signature} object or null if this doesn't contain one.
+ */
+ public Signature getSignature() {
+ return mCrypto instanceof Signature ? (Signature) mCrypto : null;
+ }
+
+ /**
+ * Get {@link Cipher} object.
+ *
+ * @return {@link Cipher} object or null if this doesn't contain one.
+ */
+ public Cipher getCipher() {
+ return mCrypto instanceof Cipher ? (Cipher) mCrypto : null;
+ }
+
+ /**
+ * Get {@link Mac} object.
+ *
+ * @return {@link Mac} object or null if this doesn't contain one.
+ */
+ public Mac getMac() {
+ return mCrypto instanceof Mac ? (Mac) mCrypto : null;
+ }
+
+ /**
+ * @return the opId associated with this object or 0 if none
+ * @hide
+ */
+ public long getOpId() {
+ return mCrypto != null
+ ? AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0;
+ }
+ }
+
+ /**
+ * Container for callback data from {@link FaceManager#authenticate(CryptoObject,
+ * CancellationSignal, int, AuthenticationCallback, Handler)}.
+ */
+ public static class AuthenticationResult {
+ private Face mFace;
+ private CryptoObject mCryptoObject;
+ private int mUserId;
+
+ /**
+ * Authentication result
+ *
+ * @param crypto the crypto object
+ * @param face the recognized face data, if allowed.
+ * @hide
+ */
+ public AuthenticationResult(CryptoObject crypto, Face face, int userId) {
+ mCryptoObject = crypto;
+ mFace = face;
+ mUserId = userId;
+ }
+
+ /**
+ * Obtain the crypto object associated with this transaction
+ *
+ * @return crypto object provided to {@link FaceManager#authenticate
+ * (CryptoObject,
+ * CancellationSignal, int, AuthenticationCallback, Handler)}.
+ */
+ public CryptoObject getCryptoObject() {
+ return mCryptoObject;
+ }
+
+ /**
+ * Obtain the Face associated with this operation. Applications are strongly
+ * discouraged from associating specific faces with specific applications or operations.
+ *
+ * @hide
+ */
+ public Face getFace() {
+ return mFace;
+ }
+
+ /**
+ * Obtain the userId for which this face was authenticated.
+ *
+ * @hide
+ */
+ public int getUserId() {
+ return mUserId;
+ }
+ }
+
+ /**
+ * Callback structure provided to {@link FaceManager#authenticate(CryptoObject,
+ * CancellationSignal, int, AuthenticationCallback, Handler)}. Users of {@link
+ * FaceManager#authenticate(CryptoObject, CancellationSignal,
+ * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening
+ * to face events.
+ */
+ public abstract static class AuthenticationCallback {
+
+ /**
+ * Called when an unrecoverable error has been encountered and the operation is complete.
+ * No further callbacks will be made on this object.
+ *
+ * @param errorCode An integer identifying the error message
+ * @param errString A human-readable error string that can be shown in UI
+ */
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ }
+
+ /**
+ * Called when a recoverable error has been encountered during authentication. The help
+ * string is provided to give the user guidance for what went wrong, such as
+ * "Sensor dirty, please clean it."
+ *
+ * @param helpCode An integer identifying the error message
+ * @param helpString A human-readable string that can be shown in UI
+ */
+ public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
+ }
+
+ /**
+ * Called when a face is recognized.
+ *
+ * @param result An object containing authentication-related data
+ */
+ public void onAuthenticationSucceeded(AuthenticationResult result) {
+ }
+
+ /**
+ * Called when a face is detected but not recognized.
+ */
+ public void onAuthenticationFailed() {
+ }
+
+ /**
+ * Called when a face image has been acquired, but wasn't processed yet.
+ *
+ * @param acquireInfo one of FACE_ACQUIRED_* constants
+ * @hide
+ */
+ public void onAuthenticationAcquired(int acquireInfo) {
+ }
+ }
+
+ /**
+ * Callback structure provided to {@link FaceManager#enroll(long,
+ * EnrollmentCallback, CancellationSignal, int). Users of {@link #FaceAuthenticationManager()}
+ * must provide an implementation of this to {@link FaceManager#enroll(long,
+ * CancellationSignal, int, EnrollmentCallback) for listening to face enrollment events.
+ *
+ * @hide
+ */
+ public abstract static class EnrollmentCallback {
+
+ /**
+ * Called when an unrecoverable error has been encountered and the operation is complete.
+ * No further callbacks will be made on this object.
+ *
+ * @param errMsgId An integer identifying the error message
+ * @param errString A human-readable error string that can be shown in UI
+ */
+ public void onEnrollmentError(int errMsgId, CharSequence errString) {
+ }
+
+ /**
+ * Called when a recoverable error has been encountered during enrollment. The help
+ * string is provided to give the user guidance for what went wrong, such as
+ * "Image too dark, uncover light source" or what they need to do next, such as
+ * "Rotate face up / down."
+ *
+ * @param helpMsgId An integer identifying the error message
+ * @param helpString A human-readable string that can be shown in UI
+ */
+ public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
+ }
+
+ /**
+ * Called as each enrollment step progresses. Enrollment is considered complete when
+ * remaining reaches 0. This function will not be called if enrollment fails. See
+ * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
+ *
+ * @param remaining The number of remaining steps
+ * @param vendorMsg Vendor feedback about the current enroll attempt. Use it to customize
+ * the GUI according to vendor's requirements.
+ */
+ public void onEnrollmentProgress(int remaining, long vendorMsg) {
+ }
+ }
+
+ /**
+ * Callback structure provided to {@link #remove}. Users of {@link FaceManager}
+ * may
+ * optionally provide an implementation of this to
+ * {@link #remove(Face, int, RemovalCallback)} for listening to face template
+ * removal events.
+ *
+ * @hide
+ */
+ public abstract static class RemovalCallback {
+
+ /**
+ * Called when the given face can't be removed.
+ *
+ * @param face The face that the call attempted to remove
+ * @param errMsgId An associated error message id
+ * @param errString An error message indicating why the face id can't be removed
+ */
+ public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
+ }
+
+ /**
+ * Called when a given face is successfully removed.
+ *
+ * @param face The face template that was removed.
+ */
+ public void onRemovalSucceeded(Face face) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public abstract static class LockoutResetCallback {
+
+ /**
+ * Called when lockout period expired and clients are allowed to listen for face
+ * authentication
+ * again.
+ */
+ public void onLockoutReset() {
+ }
+ }
+
+ private class OnEnrollCancelListener implements OnCancelListener {
+ @Override
+ public void onCancel() {
+ cancelEnrollment();
+ }
+ }
+
+ private class OnAuthenticationCancelListener implements OnCancelListener {
+ private CryptoObject mCrypto;
+
+ OnAuthenticationCancelListener(CryptoObject crypto) {
+ mCrypto = crypto;
+ }
+
+ @Override
+ public void onCancel() {
+ cancelAuthentication(mCrypto);
+ }
+ }
+
+ private class MyHandler extends Handler {
+ private MyHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ private MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(android.os.Message msg) {
+ switch (msg.what) {
+ case MSG_ENROLL_RESULT:
+ sendEnrollResult((EnrollResultMsg) msg.obj);
+ break;
+ case MSG_ACQUIRED:
+ sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
+ msg.arg2 /* vendorCode */);
+ break;
+ case MSG_AUTHENTICATION_SUCCEEDED:
+ sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);
+ break;
+ case MSG_AUTHENTICATION_FAILED:
+ sendAuthenticatedFailed();
+ break;
+ case MSG_ERROR:
+ sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */,
+ msg.arg2 /* vendorCode */);
+ break;
+ case MSG_REMOVED:
+ sendRemovedResult((Face) msg.obj);
+ break;
+ }
+ }
+
+ private void sendRemovedResult(Face face) {
+ if (mRemovalCallback == null) {
+ return;
+ }
+ if (face == null) {
+ Log.e(TAG, "Received MSG_REMOVED, but face is null");
+ return;
+ }
+
+
+ mRemovalCallback.onRemovalSucceeded(face);
+ }
+
+ private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) {
+ // emulate HAL 2.1 behavior and send real errMsgId
+ final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
+ ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+ getErrorString(errMsgId, vendorCode));
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+ getErrorString(errMsgId, vendorCode));
+ } else if (mRemovalCallback != null) {
+ mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
+ getErrorString(errMsgId, vendorCode));
+ }
+ }
+
+ private void sendEnrollResult(EnrollResultMsg faceWrapper) {
+ if (mEnrollmentCallback != null) {
+ int remaining = faceWrapper.getRemaining();
+ long vendorMsg = faceWrapper.getVendorMsg();
+ mEnrollmentCallback.onEnrollmentProgress(remaining, vendorMsg);
+ }
+ }
+
+ private void sendAuthenticatedSucceeded(Face face, int userId) {
+ if (mAuthenticationCallback != null) {
+ final AuthenticationResult result =
+ new AuthenticationResult(mCryptoObject, face, userId);
+ mAuthenticationCallback.onAuthenticationSucceeded(result);
+ }
+ }
+
+ private void sendAuthenticatedFailed() {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationFailed();
+ }
+ }
+
+ private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+ }
+ final String msg = getAcquiredString(acquireInfo, vendorCode);
+ if (msg == null) {
+ return;
+ }
+ final int clientInfo = acquireInfo == FACE_ACQUIRED_VENDOR
+ ? (vendorCode + FACE_ACQUIRED_VENDOR_BASE) : acquireInfo;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+ }
+ }
+ }
+
+ private class EnrollResultMsg {
+ private final Face mFace;
+ private final int mRemaining;
+ private final long mVendorMsg;
+
+ EnrollResultMsg(Face face, int remaining, long vendorMsg) {
+ mFace = face;
+ mRemaining = remaining;
+ mVendorMsg = vendorMsg;
+ }
+
+ Face getFace() {
+ return mFace;
+ }
+
+ long getVendorMsg() {
+ return mVendorMsg;
+ }
+
+ int getRemaining() {
+ return mRemaining;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 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.hardware.face;
+
+import android.os.Bundle;
+import android.hardware.face.IFaceServiceReceiver;
+import android.hardware.face.IFaceServiceLockoutResetCallback;
+import android.hardware.face.Face;
+
+/**
+ * Communication channel from client to the face service.
+ * @hide
+ */
+interface IFaceService {
+ // Authenticate the given sessionId with a face
+ void authenticate(IBinder token, long sessionId,
+ IFaceServiceReceiver receiver, int flags, String opPackageName);
+
+ // Cancel authentication for the given sessionId
+ void cancelAuthentication(IBinder token, String opPackageName);
+
+ // Start face enrollment
+ void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver,
+ int flags, String opPackageName);
+
+ // Cancel enrollment in progress
+ void cancelEnrollment(IBinder token);
+
+ // Any errors resulting from this call will be returned to the listener
+ void remove(IBinder token, int userId, IFaceServiceReceiver receiver);
+
+ // Get the enrolled face for user.
+ Face getEnrolledFace(int userId, String opPackageName);
+
+ // Determine if HAL is loaded and ready
+ boolean isHardwareDetected(long deviceId, String opPackageName);
+
+ // Get a pre-enrollment authentication token
+ long preEnroll(IBinder token);
+
+ // Finish an enrollment sequence and invalidate the authentication token
+ int postEnroll(IBinder token);
+
+ // Determine if a user has enrolled a face
+ boolean hasEnrolledFace(int userId, String opPackageName);
+
+ // Gets the number of hardware devices
+ // int getHardwareDeviceCount();
+
+ // Gets the unique device id for hardware enumerated at i
+ // long getHardwareDevice(int i);
+
+ // Gets the authenticator ID for face
+ long getAuthenticatorId(String opPackageName);
+
+ // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password)
+ void resetTimeout(in byte [] cryptoToken);
+
+ // Add a callback which gets notified when the face lockout period expired.
+ void addLockoutResetCallback(IFaceServiceLockoutResetCallback callback);
+
+ // Explicitly set the active user (for enrolling work profile)
+ void setActiveUser(int uid);
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 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.hardware.face;
+
+import android.hardware.face.Face;
+import android.os.Bundle;
+import android.os.IRemoteCallback;
+import android.os.UserHandle;
+
+/**
+ * Callback when lockout period expired and clients are allowed to authenticate again.
+ * @hide
+ */
+oneway interface IFaceServiceLockoutResetCallback {
+
+ /**
+ * A wakelock will be held until the reciever calls back into {@param callback}
+ */
+ void onLockoutReset(long deviceId, IRemoteCallback callback);
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 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.hardware.face;
+
+import android.hardware.face.Face;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+/**
+ * Communication channel from the FaceService back to FaceAuthenticationManager.
+ * @hide
+ */
+oneway interface IFaceServiceReceiver {
+ void onEnrollResult(long deviceId, int faceId, int remaining);
+ void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
+ void onAuthenticationSucceeded(long deviceId, in Face face);
+ void onAuthenticationFailed(long deviceId);
+ void onError(long deviceId, int error, int vendorCode);
+ void onRemoved(long deviceId, int faceId, int remaining);
+}
*/
public static final int ACTION_ROTATE_SCREEN = 6;
+ /*
+ * Time between we get a face acquired signal until we start with the unlock animation
+ */
+ public static final int ACTION_FACE_WAKE_AND_UNLOCK = 6;
+
private static final String[] NAMES = new String[] {
"expand panel",
"toggle recents",
"check credential",
"check credential unlocked",
"turn on screen",
- "rotate the screen"};
+ "rotate the screen",
+ "face wake-and-unlock" };
private static LatencyTracker sLatencyTracker;
* @see StrongAuthTracker#isFingerprintAllowedForUser
*/
public boolean isFingerprintAllowedForUser(int userId) {
- return (getStrongAuthForUser(userId) & ~StrongAuthTracker.ALLOWING_FINGERPRINT) == 0;
+ return (getStrongAuthForUser(userId) & ~StrongAuthTracker.ALLOWING_BIOMETRIC) == 0;
}
public boolean isUserInLockdown(int userId) {
public static final int STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN = 0x20;
/**
- * Strong auth flags that do not prevent fingerprint from being accepted as auth.
- *
- * If any other flags are set, fingerprint is disabled.
+ * Strong auth flags that do not prevent biometric methods from being accepted as auth.
+ * If any other flags are set, biometric authentication is disabled.
*/
- private static final int ALLOWING_FINGERPRINT = STRONG_AUTH_NOT_REQUIRED
+ private static final int ALLOWING_BIOMETRIC = STRONG_AUTH_NOT_REQUIRED
| SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
private final SparseIntArray mStrongAuthRequiredForUser = new SparseIntArray();
}
/**
- * @return true if unlocking with fingerprint alone is allowed for {@param userId} by the
- * current strong authentication requirements.
+ * @return true if unlocking with a biometric method alone is allowed for {@param userId}
+ * by the current strong authentication requirements.
*/
- public boolean isFingerprintAllowedForUser(int userId) {
- return (getStrongAuthForUser(userId) & ~ALLOWING_FINGERPRINT) == 0;
+ public boolean isBiometricAllowedForUser(int userId) {
+ return (getStrongAuthForUser(userId) & ~ALLOWING_BIOMETRIC) == 0;
}
/**
--- /dev/null
+/*
+ * Copyright (C) 2018 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.
+ */
+
+syntax = "proto3";
+
+package android.service.face;
+
+option java_multiple_files = true;
+option java_outer_classname = "FaceServiceProto";
+
+message FaceServiceDumpProto {
+ // Each log may include multiple user_id for different users.
+ repeated FaceUserStatsProto users = 1;
+}
+
+message FaceUserStatsProto {
+ // Refer to the UserHandle documentation.
+ int32 user_id = 1;
+
+ // Normal face authentications stats (e.g. lockscreen).
+ FaceActionStatsProto normal = 2;
+
+ // Crypto authentications (e.g. to unlock password storage, make secure
+ // purchases, etc).
+ FaceActionStatsProto crypto = 3;
+}
+
+message FaceActionStatsProto {
+ // Number of accepted faces.
+ int32 accept = 1;
+
+ // Number of rejected faces.
+ int32 reject = 2;
+
+ // Total number of acquisitions. Should be >= accept+reject due to poor
+ // image acquisition in some cases (too high, too low, poor gaze, etc.)
+ int32 acquire = 3;
+
+ // Total number of lockouts.
+ int32 lockout = 4;
+
+ // Total number of permanent lockouts.
+ int32 lockout_permanent = 5;
+}
<protected-broadcast android:name="android.intent.action.TWILIGHT_CHANGED" />
<protected-broadcast android:name="com.android.server.fingerprint.ACTION_LOCKOUT_RESET" />
+ <protected-broadcast android:name="com.android.server.face.ACTION_LOCKOUT_RESET" />
<protected-broadcast android:name="android.net.wifi.PASSPOINT_ICON_RECEIVED" />
<protected-broadcast android:name="com.android.server.notification.CountdownConditionProvider" />
<permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT"
android:protectionLevel="signature" />
+ <!-- Allows managing (adding, removing) facial templates. Reserved for the system. @hide -->
+ <permission android:name="android.permission.MANAGE_FACE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an app to reset face authentication attempt counter. Reserved for the system. @hide -->
+ <permission android:name="android.permission.RESET_FACE_LOCKOUT"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to control keyguard. Only allowed for system processes.
@hide -->
<permission android:name="android.permission.CONTROL_KEYGUARD"
<!-- Content description which should be used for the fingerprint icon. -->
<string name="fingerprint_icon_content_description">Fingerprint icon</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50] -->
+ <string name="permlab_manageFace">manage face authentication hardware</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=90] -->
+ <string name="permdesc_manageFace">Allows the app to invoke methods to add and delete facial templates for use.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50] -->
+ <string name="permlab_useFaceAuthentication">use face authentication hardware</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=90] -->
+ <string name="permdesc_useFaceAuthentication">Allows the app to use face authentication hardware for authentication</string>
+
+ <!-- Message shown during face acquisition when the face cannot be recognized [CHAR LIMIT=50] -->
+ <string name="face_acquired_insufficient">Couldn\u2019t process face. Please try again.</string>
+ <!-- Message shown during face acquisition when the image is too bright [CHAR LIMIT=50] -->
+ <string name="face_acquired_too_bright">Face is too bright. Please try in lower light.</string>
+ <!-- Message shown during face acquisition when the image is too dark [CHAR LIMIT=50] -->
+ <string name="face_acquired_too_dark">Face is too dark. Please uncover light source.</string>
+ <!-- Message shown during face acquisition when the user is too close to sensor [CHAR LIMIT=50] -->
+ <string name="face_acquired_too_close">Please move sensor farther from face.</string>
+ <!-- Message shown during face acquisition when the user is too far from sensor [CHAR LIMIT=50] -->
+ <string name="face_acquired_too_far">Please bring sensor closer to face.</string>
+ <!-- Message shown during face acquisition when the user is too high relatively to sensor [CHAR LIMIT=50] -->
+ <string name="face_acquired_too_high">Please move sensor higher.</string>
+ <!-- Message shown during face acquisition when the user is too low relatively to sensor [CHAR LIMIT=50] -->
+ <string name="face_acquired_too_low">Please move sensor lower.</string>
+ <!-- Message shown during face acquisition when the user is too right relatively to sensor [CHAR LIMIT=50] -->
+ <string name="face_acquired_too_right">Please move sensor to the right.</string>
+ <!-- Message shown during face acquisition when the user is too left relatively to sensor [CHAR LIMIT=50] -->
+ <string name="face_acquired_too_left">Please move sensor to the left.</string>
+ <!-- Message shown during face acquisition when the user is not front facing the sensor [CHAR LIMIT=50] -->
+ <string name="face_acquired_poor_gaze">Please look at the sensor.</string>
+ <!-- Message shown during face acquisition when the user is not detected [CHAR LIMIT=50] -->
+ <string name="face_acquired_not_detected">No face detected.</string>
+
+
+ <!-- Message shown during face acquisition when the face is not kept steady infront of device [CHAR LIMIT=50] -->
+ <string name="face_acquired_not_steady">Keep face steady infront of device.</string>
+ <!-- Array containing custom messages shown during face acquisition from vendor. Vendor is expected to add and translate these strings -->
+ <string-array name="face_acquired_vendor">
+ </string-array>
+
+ <!-- Error message shown when the face hardware can't be accessed. [CHAR LIMIT=50] -->
+ <string name="face_error_hw_not_available">Face hardware not available.</string>
+ <!-- Error message shown when the face hardware timer has expired and the user needs to restart the operation. [CHAR LIMIT=50] -->
+ <string name="face_error_timeout">Face time out reached. Try again.</string>
+ <!-- Error message shown when the face hardware has run out of room for storing faces. [CHAR LIMIT=50] -->
+ <string name="face_error_no_space">Face can\u2019t be stored.</string>
+ <!-- Generic error message shown when the face operation (e.g. enrollment or authentication) is canceled. Generally not shown to the user. [CHAR LIMIT=50] -->
+ <string name="face_error_canceled">Face operation canceled.</string>
+ <!-- Generic error message shown when the face operation fails because too many attempts have been made. [CHAR LIMIT=50] -->
+ <string name="face_error_lockout">Too many attempts. Try again later.</string>
+ <!-- Generic error message shown when the face operation fails because strong authentication is required. [CHAR LIMIT=50] -->
+ <string name="face_error_lockout_permanent">Too many attempts. Facial authentication disabled.</string>
+ <!-- Generic error message shown when the face hardware can't recognize the face. [CHAR LIMIT=50] -->
+ <string name="face_error_unable_to_process">Try again.</string>
+ <!-- Generic error message shown when the user has no enrolled face. [CHAR LIMIT=50] -->
+ <string name="face_error_not_enrolled">No face enrolled.</string>
+ <!-- Generic error message shown when the app requests face authentication on a device without a sensor. [CHAR LIMIT=60] -->
+ <string name="face_error_hw_not_present">This device does not have a face authentication sensor</string>
+
+ <!-- Template to be used to name enrolled faces by default. [CHAR LIMIT=10] -->
+ <string name="face_name_template">Face <xliff:g id="faceId" example="1">%d</xliff:g></string>
+
+ <!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings -->
+ <string-array name="face_error_vendor">
+ </string-array>
+
+ <!-- Content description which should be used for the face icon. [CHAR LIMIT=10] -->
+ <string name="face_icon_content_description">Face icon</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readSyncSettings">read sync settings</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/>
<java-symbol type="bool" name="config_fingerprintSupportsGestures"/>
+ <!-- Face authentication messages -->
+ <java-symbol type="string" name="face_error_unable_to_process" />
+ <java-symbol type="string" name="face_error_hw_not_available" />
+ <java-symbol type="string" name="face_error_no_space" />
+ <java-symbol type="string" name="face_error_timeout" />
+ <java-symbol type="array" name="face_error_vendor" />
+ <java-symbol type="string" name="face_error_canceled" />
+ <java-symbol type="string" name="face_error_lockout" />
+ <java-symbol type="string" name="face_error_lockout_permanent" />
+ <java-symbol type="string" name="face_error_not_enrolled" />
+ <java-symbol type="string" name="face_error_hw_not_present" />
+ <java-symbol type="string" name="face_acquired_insufficient" />
+ <java-symbol type="string" name="face_acquired_too_bright" />
+ <java-symbol type="string" name="face_acquired_too_dark" />
+ <java-symbol type="string" name="face_acquired_too_close" />
+ <java-symbol type="string" name="face_acquired_too_far" />
+ <java-symbol type="string" name="face_acquired_too_high" />
+ <java-symbol type="string" name="face_acquired_too_low" />
+ <java-symbol type="string" name="face_acquired_too_right" />
+ <java-symbol type="string" name="face_acquired_too_left" />
+ <java-symbol type="string" name="face_acquired_poor_gaze" />
+ <java-symbol type="string" name="face_acquired_not_detected" />
+ <java-symbol type="array" name="face_acquired_vendor" />
+
<!-- From various Material changes -->
<java-symbol type="attr" name="titleTextAppearance" />
<java-symbol type="attr" name="subtitleTextAppearance" />
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<uses-permission android:name="android.permission.TRUST_LISTENER" />
+ <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT" />
<uses-permission android:name="android.permission.MANAGE_SLICE_PERMISSIONS" />
</plurals>
<!-- Fingerprint hint message when finger was not recognized.-->
- <string name="fingerprint_not_recognized">Not recognized</string>
+ <string name="kg_fingerprint_not_recognized">Not recognized</string>
+
+ <!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] -->
+ <string name="kg_face_not_recognized">Not recognized</string>
<!-- Instructions telling the user remaining times when enter SIM PIN view. -->
<plurals name="kg_password_default_pin_message">
*/
public static final boolean DEBUG = false;
public static final boolean DEBUG_SIM_STATES = true;
- public static final boolean DEBUG_FP_WAKELOCK = true;
+ public static final boolean DEBUG_BIOMETRIC_WAKELOCK = true;
}
import android.app.UserSwitchObserver;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
+import android.hardware.biometrics.BiometricSourceType;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
+import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
private static final int MSG_DREAMING_STATE_CHANGED = 333;
private static final int MSG_USER_UNLOCKED = 334;
private static final int MSG_ASSISTANT_STACK_CHANGED = 335;
- private static final int MSG_FINGERPRINT_AUTHENTICATION_CONTINUE = 336;
+ private static final int MSG_BIOMETRIC_AUTHENTICATION_CONTINUE = 336;
private static final int MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED = 337;
- /** Fingerprint state: Not listening to fingerprint. */
- private static final int FINGERPRINT_STATE_STOPPED = 0;
+ /** Biometric authentication state: Not listening. */
+ private static final int BIOMETRIC_STATE_STOPPED = 0;
- /** Fingerprint state: Listening. */
- private static final int FINGERPRINT_STATE_RUNNING = 1;
+ /** Biometric authentication state: Listening. */
+ private static final int BIOMETRIC_STATE_RUNNING = 1;
/**
- * Fingerprint state: Cancelling and waiting for the confirmation from FingerprintService to
+ * Biometric authentication: Cancelling and waiting for the relevant biometric service to
* send us the confirmation that cancellation has happened.
*/
- private static final int FINGERPRINT_STATE_CANCELLING = 2;
+ private static final int BIOMETRIC_STATE_CANCELLING = 2;
/**
- * Fingerprint state: During cancelling we got another request to start listening, so when we
+ * Biometric state: During cancelling we got another request to start listening, so when we
* receive the cancellation done signal, we should start listening again.
*/
- private static final int FINGERPRINT_STATE_CANCELLING_RESTARTING = 3;
+ private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
private List<SubscriptionInfo> mSubscriptionInfo;
private TrustManager mTrustManager;
private UserManager mUserManager;
- private int mFingerprintRunningState = FINGERPRINT_STATE_STOPPED;
+ private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
private LockPatternUtils mLockPatternUtils;
private final IDreamManager mDreamManager;
private boolean mIsDreaming;
private boolean mLogoutEnabled;
/**
- * Short delay before restarting fingerprint authentication after a successful try
- * This should be slightly longer than the time between onFingerprintAuthenticated and
- * setKeyguardGoingAway(true).
+ * Short delay before restarting biometric authentication after a successful try
+ * This should be slightly longer than the time between on<biometric>Authenticated
+ * (e.g. onFingerprintAuthenticated) and setKeyguardGoingAway(true).
*/
- private static final int FINGERPRINT_CONTINUE_DELAY_MS = 500;
+ private static final int BIOMETRIC_CONTINUE_DELAY_MS = 500;
// If FP daemon dies, keyguard should retry after a short delay
- private int mHardwareUnavailableRetryCount = 0;
+ private int mHardwareFingerprintUnavailableRetryCount = 0;
+ private int mHardwareFaceUnavailableRetryCount = 0;
private static final int HW_UNAVAILABLE_TIMEOUT = 3000; // ms
private static final int HW_UNAVAILABLE_RETRY_MAX = 3;
break;
case MSG_ASSISTANT_STACK_CHANGED:
mAssistantVisible = (boolean)msg.obj;
- updateFingerprintListeningState();
+ updateBiometricListeningState();
break;
- case MSG_FINGERPRINT_AUTHENTICATION_CONTINUE:
- updateFingerprintListeningState();
+ case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
+ updateBiometricListeningState();
break;
case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
updateLogoutEnabled();
private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
private SparseBooleanArray mUserFingerprintAuthenticated = new SparseBooleanArray();
+ private SparseBooleanArray mUserFaceAuthenticated = new SparseBooleanArray();
private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray();
private static int sCurrentUser;
- private Runnable mUpdateFingerprintListeningState = this::updateFingerprintListeningState;
+ private Runnable mUpdateBiometricListeningState = this::updateBiometricListeningState;
private static boolean sDisableHandlerCheckForTesting;
public synchronized static void setCurrentUser(int currentUser) {
*/
public void setKeyguardOccluded(boolean occluded) {
mKeyguardOccluded = occluded;
- updateFingerprintListeningState();
+ updateBiometricListeningState();
}
/**
mUserFingerprintAuthenticated.put(userId, true);
// Update/refresh trust state only if user can skip bouncer
if (getUserCanSkipBouncer(userId)) {
- mTrustManager.unlockedByFingerprintForUser(userId);
+ mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
}
// Don't send cancel if authentication succeeds
mFingerprintCancelSignal = null;
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onFingerprintAuthenticated(userId);
+ cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT);
}
}
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATION_CONTINUE),
- FINGERPRINT_CONTINUE_DELAY_MS);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE),
+ BIOMETRIC_CONTINUE_DELAY_MS);
// Only authenticate fingerprint once when assistant is visible
mAssistantVisible = false;
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onFingerprintAuthFailed();
+ cb.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
}
}
- handleFingerprintHelp(-1, mContext.getString(R.string.fingerprint_not_recognized));
+ handleFingerprintHelp(-1, mContext.getString(R.string.kg_fingerprint_not_recognized));
}
private void handleFingerprintAcquired(int acquireInfo) {
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onFingerprintAcquired();
+ cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
}
}
}
}
onFingerprintAuthenticated(userId);
} finally {
- setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
+ setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
}
Trace.endSection();
}
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onFingerprintHelp(msgId, helpString);
+ cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FINGERPRINT);
}
}
}
@Override
public void run() {
Log.w(TAG, "Retrying fingerprint after HW unavailable, attempt " +
- mHardwareUnavailableRetryCount);
+ mHardwareFingerprintUnavailableRetryCount);
updateFingerprintListeningState();
}
};
private void handleFingerprintError(int msgId, String errString) {
if (msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
- && mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING_RESTARTING) {
- setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
+ && mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
+ setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
startListeningForFingerprint();
} else {
- setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
+ setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
- if (mHardwareUnavailableRetryCount < HW_UNAVAILABLE_RETRY_MAX) {
- mHardwareUnavailableRetryCount++;
+ if (mHardwareFingerprintUnavailableRetryCount < HW_UNAVAILABLE_RETRY_MAX) {
+ mHardwareFingerprintUnavailableRetryCount++;
mHandler.removeCallbacks(mRetryFingerprintAuthentication);
mHandler.postDelayed(mRetryFingerprintAuthentication, HW_UNAVAILABLE_TIMEOUT);
}
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onFingerprintError(msgId, errString);
+ cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
}
}
}
}
private void setFingerprintRunningState(int fingerprintRunningState) {
- boolean wasRunning = mFingerprintRunningState == FINGERPRINT_STATE_RUNNING;
- boolean isRunning = fingerprintRunningState == FINGERPRINT_STATE_RUNNING;
+ boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
+ boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
mFingerprintRunningState = fingerprintRunningState;
// Clients of KeyguardUpdateMonitor don't care about the internal state about the
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onFingerprintRunningStateChanged(isFingerprintDetectionRunning());
+ cb.onBiometricRunningStateChanged(isFingerprintDetectionRunning(),
+ BiometricSourceType.FINGERPRINT);
+ }
+ }
+ }
+
+ private void onFaceAuthenticated(int userId) {
+ Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
+ mUserFaceAuthenticated.put(userId, true);
+ // Update/refresh trust state only if user can skip bouncer
+ if (getUserCanSkipBouncer(userId)) {
+ mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
+ }
+ // Don't send cancel if authentication succeeds
+ mFaceCancelSignal = null;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onBiometricAuthenticated(userId,
+ BiometricSourceType.FACE);
+ }
+ }
+
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE),
+ BIOMETRIC_CONTINUE_DELAY_MS);
+
+ // Only authenticate face once when assistant is visible
+ mAssistantVisible = false;
+
+ Trace.endSection();
+ }
+
+ private void handleFaceAuthFailed() {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onBiometricAuthFailed(BiometricSourceType.FACE);
+ }
+ }
+ handleFaceHelp(-1, mContext.getString(R.string.kg_face_not_recognized));
+ }
+
+ private void handleFaceAcquired(int acquireInfo) {
+ if (acquireInfo != FaceManager.FACE_ACQUIRED_GOOD) {
+ return;
+ }
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onBiometricAcquired(BiometricSourceType.FACE);
+ }
+ }
+ }
+
+ private void handleFaceAuthenticated(int authUserId) {
+ Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
+ try {
+ final int userId;
+ try {
+ userId = ActivityManager.getService().getCurrentUser().id;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get current user id: ", e);
+ return;
+ }
+ if (userId != authUserId) {
+ Log.d(TAG, "Face authenticated for wrong user: " + authUserId);
+ return;
+ }
+ if (isFaceDisabled(userId)) {
+ Log.d(TAG, "Face authentication disabled by DPM for userId: " + userId);
+ return;
+ }
+ onFaceAuthenticated(userId);
+ } finally {
+ setFaceRunningState(BIOMETRIC_STATE_STOPPED);
+ }
+ Trace.endSection();
+ }
+
+ private void handleFaceHelp(int msgId, String helpString) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FACE);
+ }
+ }
+ }
+
+ private Runnable mRetryFaceAuthentication = new Runnable() {
+ @Override
+ public void run() {
+ Log.w(TAG, "Retrying face after HW unavailable, attempt " +
+ mHardwareFaceUnavailableRetryCount);
+ updateFaceListeningState();
+ }
+ };
+
+ private void handleFaceError(int msgId, String errString) {
+ if (msgId == FaceManager.FACE_ERROR_CANCELED
+ && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
+ setFaceRunningState(BIOMETRIC_STATE_STOPPED);
+ startListeningForFace();
+ } else {
+ setFaceRunningState(BIOMETRIC_STATE_STOPPED);
+ }
+
+ if (msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE) {
+ if (mHardwareFaceUnavailableRetryCount < HW_UNAVAILABLE_RETRY_MAX) {
+ mHardwareFaceUnavailableRetryCount++;
+ mHandler.removeCallbacks(mRetryFaceAuthentication);
+ mHandler.postDelayed(mRetryFaceAuthentication, HW_UNAVAILABLE_TIMEOUT);
+ }
+ }
+
+ if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
+ mLockPatternUtils.requireStrongAuth(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
+ getCurrentUser());
+ }
+
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onBiometricError(msgId, errString,
+ BiometricSourceType.FACE);
+ }
+ }
+ }
+
+ private void handleFaceLockoutReset() {
+ updateFaceListeningState();
+ }
+
+ private void setFaceRunningState(int faceRunningState) {
+ boolean wasRunning = mFaceRunningState == BIOMETRIC_STATE_RUNNING;
+ boolean isRunning = faceRunningState == BIOMETRIC_STATE_RUNNING;
+ mFaceRunningState = faceRunningState;
+
+ // Clients of KeyguardUpdateMonitor don't care about the internal state or about the
+ // asynchronousness of the cancel cycle. So only notify them if the actualy running state
+ // has changed.
+ if (wasRunning != isRunning) {
+ notifyFaceRunningStateChanged();
+ }
+ }
+
+ private void notifyFaceRunningStateChanged() {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onBiometricRunningStateChanged(isFaceDetectionRunning(),
+ BiometricSourceType.FACE);
}
}
}
+
private void handleFaceUnlockStateChanged(boolean running, int userId) {
checkIsHandlerThread();
mUserFaceUnlockRunning.put(userId, running);
}
public boolean isFingerprintDetectionRunning() {
- return mFingerprintRunningState == FINGERPRINT_STATE_RUNNING;
+ return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
+ }
+
+ public boolean isFaceDetectionRunning() {
+ return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
}
private boolean isTrustDisabled(int userId) {
|| isSimPinSecure();
}
+ private boolean isFaceDisabled(int userId) {
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return dpm != null && (dpm.getKeyguardDisabledFeatures(null, userId)
+ & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0
+ || isSimPinSecure();
+ }
+
+
public boolean getUserCanSkipBouncer(int userId) {
return getUserHasTrust(userId) || (mUserFingerprintAuthenticated.get(userId)
- && isUnlockingWithFingerprintAllowed());
+ && isUnlockingWithBiometricAllowed());
}
public boolean getUserHasTrust(int userId) {
return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
}
- public boolean isUnlockingWithFingerprintAllowed() {
- return mStrongAuthTracker.isUnlockingWithFingerprintAllowed();
+ public boolean isUnlockingWithBiometricAllowed() {
+ return mStrongAuthTracker.isUnlockingWithBiometricAllowed();
}
public boolean isUserInLockdown(int userId) {
}
};
- private final FingerprintManager.LockoutResetCallback mLockoutResetCallback
+ private final FingerprintManager.LockoutResetCallback mFingerprintLockoutResetCallback
= new FingerprintManager.LockoutResetCallback() {
@Override
public void onLockoutReset() {
}
};
- private FingerprintManager.AuthenticationCallback mAuthenticationCallback
+ private final FaceManager.LockoutResetCallback mFaceLockoutResetCallback
+ = new FaceManager.LockoutResetCallback() {
+ @Override
+ public void onLockoutReset() {
+ handleFaceLockoutReset();
+ }
+ };
+
+ private FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
= new AuthenticationCallback() {
@Override
public void onAuthenticationFailed() {
handleFingerprintAuthFailed();
- };
+ }
@Override
public void onAuthenticationSucceeded(AuthenticationResult result) {
handleFingerprintAcquired(acquireInfo);
}
};
+
+ private FaceManager.AuthenticationCallback mFaceAuthenticationCallback
+ = new FaceManager.AuthenticationCallback() {
+
+ @Override
+ public void onAuthenticationFailed() {
+ handleFaceAuthFailed();
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
+ Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
+ handleFaceAuthenticated(result.getUserId());
+ Trace.endSection();
+ }
+
+ @Override
+ public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+ handleFaceHelp(helpMsgId, helpString.toString());
+ }
+
+ @Override
+ public void onAuthenticationError(int errMsgId, CharSequence errString) {
+ handleFaceError(errMsgId, errString.toString());
+ }
+
+ @Override
+ public void onAuthenticationAcquired(int acquireInfo) {
+ handleFaceAcquired(acquireInfo);
+ }
+ };
+
private CancellationSignal mFingerprintCancelSignal;
+ private CancellationSignal mFaceCancelSignal;
private FingerprintManager mFpm;
+ private FaceManager mFaceAuthenticationManager;
/**
* When we receive a
super(context);
}
- public boolean isUnlockingWithFingerprintAllowed() {
+ public boolean isUnlockingWithBiometricAllowed() {
int userId = getCurrentUser();
- return isFingerprintAllowedForUser(userId);
+ return isBiometricAllowedForUser(userId);
}
public boolean hasUserAuthenticatedSinceBoot() {
protected void handleStartedWakingUp() {
Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
- updateFingerprintListeningState();
+ updateBiometricListeningState();
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
}
protected void handleStartedGoingToSleep(int arg1) {
- clearFingerprintRecognized();
+ clearBiometricRecognized();
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
}
}
mGoingToSleep = true;
- updateFingerprintListeningState();
+ updateBiometricListeningState();
}
protected void handleFinishedGoingToSleep(int arg1) {
cb.onFinishedGoingToSleep(arg1);
}
}
- updateFingerprintListeningState();
+ updateBiometricListeningState();
}
private void handleScreenTurnedOn() {
}
private void handleScreenTurnedOff() {
- mHardwareUnavailableRetryCount = 0;
+ mHardwareFingerprintUnavailableRetryCount = 0;
+ mHardwareFaceUnavailableRetryCount = 0;
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
cb.onDreamingStateChanged(mIsDreaming);
}
}
- updateFingerprintListeningState();
+ updateBiometricListeningState();
}
private void handleUserInfoChanged(int userId) {
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
}
- updateFingerprintListeningState();
+
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
+ mFaceAuthenticationManager =
+ (FaceManager) context.getSystemService(Context.FACE_SERVICE);
+ }
+ updateBiometricListeningState();
if (mFpm != null) {
- mFpm.addLockoutResetCallback(mLockoutResetCallback);
+ mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
+ }
+ if (mFaceAuthenticationManager != null) {
+ mFaceAuthenticationManager.addLockoutResetCallback(mFaceLockoutResetCallback);
}
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
}
+ private void updateBiometricListeningState() {
+ updateFingerprintListeningState();
+ updateFaceListeningState();
+ }
+
private void updateFingerprintListeningState() {
// If this message exists, we should not authenticate again until this message is
// consumed by the handler
- if (mHandler.hasMessages(MSG_FINGERPRINT_AUTHENTICATION_CONTINUE)) {
+ if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
return;
}
mHandler.removeCallbacks(mRetryFingerprintAuthentication);
boolean shouldListenForFingerprint = shouldListenForFingerprint();
- if (mFingerprintRunningState == FINGERPRINT_STATE_RUNNING && !shouldListenForFingerprint) {
+ if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFingerprint) {
stopListeningForFingerprint();
- } else if (mFingerprintRunningState != FINGERPRINT_STATE_RUNNING
+ } else if (mFingerprintRunningState != BIOMETRIC_STATE_RUNNING
&& shouldListenForFingerprint) {
startListeningForFingerprint();
}
}
+ private void updateFaceListeningState() {
+ // If this message exists, we should not authenticate again until this message is
+ // consumed by the handler
+ if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
+ return;
+ }
+ mHandler.removeCallbacks(mRetryFaceAuthentication);
+ boolean shouldListenForFace = shouldListenForFace();
+ if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {
+ stopListeningForFace();
+ } else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING
+ && shouldListenForFace) {
+ startListeningForFace();
+ }
+ }
+
private boolean shouldListenForFingerprintAssistant() {
return mAssistantVisible && mKeyguardOccluded
&& !mUserFingerprintAuthenticated.get(getCurrentUser(), false)
&& !mUserHasTrust.get(getCurrentUser(), false);
}
+ private boolean shouldListenForFaceAssistant() {
+ return mAssistantVisible && mKeyguardOccluded
+ && !mUserFaceAuthenticated.get(getCurrentUser(), false)
+ && !mUserHasTrust.get(getCurrentUser(), false);
+ }
+
private boolean shouldListenForFingerprint() {
return (mKeyguardIsVisible || !mDeviceInteractive ||
(mBouncer && !mKeyguardGoingAway) || mGoingToSleep ||
&& !mKeyguardGoingAway;
}
+ private boolean shouldListenForFace() {
+ return (mKeyguardIsVisible || !mDeviceInteractive ||
+ (mBouncer && !mKeyguardGoingAway) || mGoingToSleep ||
+ shouldListenForFaceAssistant() || (mKeyguardOccluded && mIsDreaming))
+ && !mSwitchingUser && !isFaceDisabled(getCurrentUser())
+ && !mKeyguardGoingAway;
+ }
+
+
private void startListeningForFingerprint() {
- if (mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING) {
- setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING_RESTARTING);
+ if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING) {
+ setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
return;
}
if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
mFingerprintCancelSignal.cancel();
}
mFingerprintCancelSignal = new CancellationSignal();
- mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null,
- userId);
- setFingerprintRunningState(FINGERPRINT_STATE_RUNNING);
+ mFpm.authenticate(null, mFingerprintCancelSignal, 0, mFingerprintAuthenticationCallback,
+ null, userId);
+ setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
+ }
+ }
+
+ private void startListeningForFace() {
+ if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
+ setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
+ return;
+ }
+ if (DEBUG) Log.v(TAG, "startListeningForFace()");
+ int userId = ActivityManager.getCurrentUser();
+ if (isUnlockWithFacePossible(userId)) {
+ if (mFaceCancelSignal != null) {
+ mFaceCancelSignal.cancel();
+ }
+ mFaceCancelSignal = new CancellationSignal();
+ mFaceAuthenticationManager.authenticate(null, mFaceCancelSignal, 0,
+ mFaceAuthenticationCallback, null, userId);
+ setFaceRunningState(BIOMETRIC_STATE_RUNNING);
}
}
&& mFpm.getEnrolledFingerprints(userId).size() > 0;
}
+ public boolean isUnlockWithFacePossible(int userId) {
+ return mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected()
+ && !isFaceDisabled(userId)
+ && mFaceAuthenticationManager.hasEnrolledFace(userId);
+ }
+
private void stopListeningForFingerprint() {
if (DEBUG) Log.v(TAG, "stopListeningForFingerprint()");
- if (mFingerprintRunningState == FINGERPRINT_STATE_RUNNING) {
+ if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) {
if (mFingerprintCancelSignal != null) {
mFingerprintCancelSignal.cancel();
mFingerprintCancelSignal = null;
}
- setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING);
+ setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING);
}
- if (mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING_RESTARTING) {
- setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING);
+ if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
+ setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING);
+ }
+ }
+
+ private void stopListeningForFace() {
+ if (DEBUG) Log.v(TAG, "stopListeningForFace()");
+ if (mFaceRunningState == BIOMETRIC_STATE_RUNNING) {
+ if (mFaceCancelSignal != null) {
+ mFaceCancelSignal.cancel();
+ mFaceCancelSignal = null;
+ }
+ setFaceRunningState(BIOMETRIC_STATE_CANCELLING);
+ }
+ if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
+ setFaceRunningState(BIOMETRIC_STATE_CANCELLING);
}
}
cb.onKeyguardVisibilityChangedRaw(showing);
}
}
- updateFingerprintListeningState();
+ updateBiometricListeningState();
}
/**
*/
private void handleKeyguardReset() {
if (DEBUG) Log.d(TAG, "handleKeyguardReset");
- updateFingerprintListeningState();
+ updateBiometricListeningState();
mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
}
cb.onKeyguardBouncerChanged(isBouncer);
}
}
- updateFingerprintListeningState();
+ updateBiometricListeningState();
}
/**
public void setSwitchingUser(boolean switching) {
mSwitchingUser = switching;
// Since this comes in on a binder thread, we need to post if first
- mHandler.post(mUpdateFingerprintListeningState);
+ mHandler.post(mUpdateBiometricListeningState);
}
private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
mFailedAttempts.put(userId, getFailedUnlockAttempts(userId) + 1);
}
- public void clearFingerprintRecognized() {
+ public void clearBiometricRecognized() {
mUserFingerprintAuthenticated.clear();
- mTrustManager.clearAllFingerprints();
+ mUserFaceAuthenticated.clear();
+ mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT);
+ mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE);
}
public boolean isSimPinVoiceSecure() {
final int userId = ActivityManager.getCurrentUser();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
pw.println(" Fingerprint state (user=" + userId + ")");
- pw.println(" allowed=" + isUnlockingWithFingerprintAllowed());
+ pw.println(" allowed=" + isUnlockingWithBiometricAllowed());
pw.println(" auth'd=" + mUserFingerprintAuthenticated.get(userId));
pw.println(" authSinceBoot="
+ getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
}
+ if (mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected()) {
+ final int userId = ActivityManager.getCurrentUser();
+ final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+ pw.println(" Face authentication state (user=" + userId + ")");
+ pw.println(" allowed=" + isUnlockingWithBiometricAllowed());
+ pw.println(" auth'd=" + mUserFaceAuthenticated.get(userId));
+ pw.println(" authSinceBoot="
+ + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
+ pw.println(" disabled(DPM)=" + isFaceDisabled(userId));
+ pw.println(" possible=" + isUnlockWithFacePossible(userId));
+ pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
+ pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
+ }
}
}
import android.app.admin.DevicePolicyManager;
import android.graphics.Bitmap;
-import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.biometrics.BiometricSourceType;
import android.media.AudioManager;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
public void onTrustGrantedWithFlags(int flags, int userId) { }
/**
- * Called when a finger has been acquired.
+ * Called when a biometric has been acquired.
* <p>
- * It is guaranteed that either {@link #onFingerprintAuthenticated} or
- * {@link #onFingerprintAuthFailed()} is called after this method eventually.
+ * It is guaranteed that either {@link #onBiometricAuthenticated} or
+ * {@link #onBiometricAuthFailed(BiometricSourceType)} is called after this method eventually.
+ * @param biometricSourceType
*/
- public void onFingerprintAcquired() { }
+ public void onBiometricAcquired(BiometricSourceType biometricSourceType) { }
/**
- * Called when a fingerprint couldn't be authenticated.
+ * Called when a biometric couldn't be authenticated.
+ * @param biometricSourceType
*/
- public void onFingerprintAuthFailed() { }
+ public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { }
/**
- * Called when a fingerprint is recognized.
- * @param userId the user id for which the fingerprint was authenticated
+ * Called when a biometric is recognized.
+ * @param userId the user id for which the biometric sample was authenticated
+ * @param biometricSourceType
*/
- public void onFingerprintAuthenticated(int userId) { }
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) { }
/**
- * Called when fingerprint provides help string (e.g. "Try again")
+ * Called when biometric authentication provides help string (e.g. "Try again")
* @param msgId
* @param helpString
+ * @param biometricSourceType
*/
- public void onFingerprintHelp(int msgId, String helpString) { }
+ public void onBiometricHelp(int msgId, String helpString,
+ BiometricSourceType biometricSourceType) { }
/**
- * Called when fingerprint provides an semi-permanent error message
- * (e.g. "Hardware not available").
- * @param msgId one of the error messages listed in {@link FingerprintManager}
+ * Called when biometric authentication method provides a semi-permanent
+ * error message (e.g. "Hardware not available").
+ * @param msgId one of the error messages listed in
+ * {@link android.hardware.biometrics.BiometricConstants}
* @param errString
+ * @param biometricSourceType
*/
- public void onFingerprintError(int msgId, String errString) { }
+ public void onBiometricError(int msgId, String errString,
+ BiometricSourceType biometricSourceType) { }
/**
* Called when the state of face unlock changed.
public void onFaceUnlockStateChanged(boolean running, int userId) { }
/**
- * Called when the fingerprint running state changed.
+ * Called when biometric running state changed.
*/
- public void onFingerprintRunningStateChanged(boolean running) { }
+ public void onBiometricRunningStateChanged(boolean running,
+ BiometricSourceType biometricSourceType) { }
/**
* Called when the state that the user hasn't used strong authentication since quite some time
package com.android.systemui;
+import android.hardware.biometrics.BiometricSourceType;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.internal.util.LatencyTracker;
-import com.android.systemui.statusbar.phone.FingerprintUnlockController;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.StatusBar;
/**
}
private void fakeWakeAndUnlock() {
- FingerprintUnlockController fingerprintUnlockController = getComponent(StatusBar.class)
- .getFingerprintUnlockController();
- fingerprintUnlockController.onFingerprintAcquired();
- fingerprintUnlockController.onFingerprintAuthenticated(
- KeyguardUpdateMonitor.getCurrentUser());
+ BiometricUnlockController biometricUnlockController = getComponent(StatusBar.class)
+ .getBiometricUnlockController();
+ biometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
+ biometricUnlockController.onBiometricAuthenticated(
+ KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT);
}
}
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.hardware.biometrics.BiometricSourceType;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.statusbar.phone.FingerprintUnlockController;
import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
}
@Override
- public void onFingerprintAuthFailed() {
+ public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
if (mLockPatternUtils.isSecure(currentUser)) {
- mLockPatternUtils.getDevicePolicyManager().reportFailedFingerprintAttempt(
+ mLockPatternUtils.getDevicePolicyManager().reportFailedBiometricAttempt(
currentUser);
}
}
@Override
- public void onFingerprintAuthenticated(int userId) {
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
if (mLockPatternUtils.isSecure(userId)) {
- mLockPatternUtils.getDevicePolicyManager().reportSuccessfulFingerprintAttempt(
+ mLockPatternUtils.getDevicePolicyManager().reportSuccessfulBiometricAttempt(
userId);
}
}
}
mUpdateMonitor.clearFailedUnlockAttempts();
- mUpdateMonitor.clearFingerprintRecognized();
+ mUpdateMonitor.clearBiometricRecognized();
if (mGoingToSleep) {
Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
ViewGroup container, NotificationPanelView panelView,
- FingerprintUnlockController fingerprintUnlockController) {
+ BiometricUnlockController biometricUnlockController) {
mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, panelView,
- fingerprintUnlockController, mDismissCallbackRegistry);
+ biometricUnlockController, mDismissCallbackRegistry);
return mStatusBarKeyguardViewManager;
}
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.admin.DevicePolicyManager;
+import android.hardware.biometrics.BiometricSourceType;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
private static final boolean DEBUG_CHARGING_SPEED = false;
private static final int MSG_HIDE_TRANSIENT = 1;
- private static final int MSG_CLEAR_FP_MSG = 2;
- private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300;
+ private static final int MSG_CLEAR_BIOMETRIC_MSG = 2;
+ private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
private final Context mContext;
private ViewGroup mIndicationArea;
public void handleMessage(Message msg) {
if (msg.what == MSG_HIDE_TRANSIENT) {
hideTransientIndication();
- } else if (msg.what == MSG_CLEAR_FP_MSG) {
+ } else if (msg.what == MSG_CLEAR_BIOMETRIC_MSG) {
mLockIcon.setTransientFpError(false);
}
}
}
@Override
- public void onFingerprintHelp(int msgId, String helpString) {
+ public void onBiometricHelp(int msgId, String helpString,
+ BiometricSourceType biometricSourceType) {
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
- if (!updateMonitor.isUnlockingWithFingerprintAllowed()) {
+ if (!updateMonitor.isUnlockingWithBiometricAllowed()) {
return;
}
ColorStateList errorColorState = Utils.getColorError(mContext);
} else if (updateMonitor.isScreenOn()) {
mLockIcon.setTransientFpError(true);
showTransientIndication(helpString, errorColorState);
- hideTransientIndicationDelayed(TRANSIENT_FP_ERROR_TIMEOUT);
- mHandler.removeMessages(MSG_CLEAR_FP_MSG);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_FP_MSG),
- TRANSIENT_FP_ERROR_TIMEOUT);
+ hideTransientIndicationDelayed(TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
+ mHandler.removeMessages(MSG_CLEAR_BIOMETRIC_MSG);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_BIOMETRIC_MSG),
+ TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
}
// Help messages indicate that there was actually a try since the last error, so those
// are not two successive error messages anymore.
}
@Override
- public void onFingerprintError(int msgId, String errString) {
+ public void onBiometricError(int msgId, String errString,
+ BiometricSourceType biometricSourceType) {
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
- if ((!updateMonitor.isUnlockingWithFingerprintAllowed()
- && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
- || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
+ if (shouldSuppressBiometricError(msgId, biometricSourceType, updateMonitor)) {
return;
}
ColorStateList errorColorState = Utils.getColorError(mContext);
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
- // When swiping up right after receiving a fingerprint error, the bouncer calls
+ // When swiping up right after receiving a biometric error, the bouncer calls
// authenticate leading to the same message being shown again on the bouncer.
// We want to avoid this, as it may confuse the user when the message is too
// generic.
mLastSuccessiveErrorMessage = msgId;
}
+ private boolean shouldSuppressBiometricError(int msgId,
+ BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) {
+ if (biometricSourceType == BiometricSourceType.FINGERPRINT)
+ return shouldSuppressFingerprintError(msgId, updateMonitor);
+ if (biometricSourceType == BiometricSourceType.FACE)
+ return shouldSuppressFaceError(msgId, updateMonitor);
+ return false;
+ }
+
+ private boolean shouldSuppressFingerprintError(int msgId,
+ KeyguardUpdateMonitor updateMonitor) {
+ return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+ && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+ || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED);
+ }
+
+ private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
+ return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+ && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
+ || msgId == FaceManager.FACE_ERROR_CANCELED);
+ }
+
@Override
public void onTrustAgentErrorMessage(CharSequence message) {
showTransientIndication(message, Utils.getColorError(mContext));
}
@Override
- public void onFingerprintRunningStateChanged(boolean running) {
+ public void onBiometricRunningStateChanged(boolean running,
+ BiometricSourceType biometricSourceType) {
if (running) {
mMessageToShowOnScreenOn = null;
}
}
@Override
- public void onFingerprintAuthenticated(int userId) {
- super.onFingerprintAuthenticated(userId);
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+ super.onBiometricAuthenticated(userId, biometricSourceType);
mLastSuccessiveErrorMessage = -1;
}
@Override
- public void onFingerprintAuthFailed() {
- super.onFingerprintAuthFailed();
+ public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
+ super.onBiometricAuthFailed(biometricSourceType);
mLastSuccessiveErrorMessage = -1;
}
package com.android.systemui.statusbar.phone;
+import android.hardware.biometrics.BiometricSourceType;
import android.content.Context;
import android.os.Handler;
import android.os.PowerManager;
import java.io.PrintWriter;
/**
- * Controller which coordinates all the fingerprint unlocking actions with the UI.
+ * Controller which coordinates all the biometric unlocking actions with the UI.
*/
-public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback {
+public class BiometricUnlockController extends KeyguardUpdateMonitorCallback {
- private static final String TAG = "FingerprintController";
- private static final boolean DEBUG_FP_WAKELOCK = KeyguardConstants.DEBUG_FP_WAKELOCK;
- private static final long FINGERPRINT_WAKELOCK_TIMEOUT_MS = 15 * 1000;
- private static final String FINGERPRINT_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
+ private static final String TAG = "BiometricUnlockController";
+ private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
+ private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
+ private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
/**
* Mode in which we don't need to wake up the device when we get a fingerprint.
public static final int MODE_WAKE_AND_UNLOCK_FROM_DREAM = 7;
/**
- * How much faster we collapse the lockscreen when authenticating with fingerprint.
+ * How much faster we collapse the lockscreen when authenticating with biometric.
*/
- private static final float FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR = 1.1f;
+ private static final float BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR = 1.1f;
private PowerManager mPowerManager;
private Handler mHandler = new Handler();
private final UnlockMethodCache mUnlockMethodCache;
private final Context mContext;
private int mPendingAuthenticatedUserId = -1;
+ private BiometricSourceType mPendingAuthenticatedBioSourceType = null;
private boolean mPendingShowBouncer;
private boolean mHasScreenTurnedOnSinceAuthenticating;
- public FingerprintUnlockController(Context context,
- DozeScrimController dozeScrimController,
- KeyguardViewMediator keyguardViewMediator,
- ScrimController scrimController,
- StatusBar statusBar,
- UnlockMethodCache unlockMethodCache) {
+ public BiometricUnlockController(Context context,
+ DozeScrimController dozeScrimController,
+ KeyguardViewMediator keyguardViewMediator,
+ ScrimController scrimController,
+ StatusBar statusBar,
+ UnlockMethodCache unlockMethodCache) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
}
- private final Runnable mReleaseFingerprintWakeLockRunnable = new Runnable() {
+ private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@Override
public void run() {
- if (DEBUG_FP_WAKELOCK) {
- Log.i(TAG, "fp wakelock: TIMEOUT!!");
+ if (DEBUG_BIO_WAKELOCK) {
+ Log.i(TAG, "biometric wakelock: TIMEOUT!!");
}
- releaseFingerprintWakeLock();
+ releaseBiometricWakeLock();
}
};
- private void releaseFingerprintWakeLock() {
+ private void releaseBiometricWakeLock() {
if (mWakeLock != null) {
- mHandler.removeCallbacks(mReleaseFingerprintWakeLockRunnable);
- if (DEBUG_FP_WAKELOCK) {
- Log.i(TAG, "releasing fp wakelock");
+ mHandler.removeCallbacks(mReleaseBiometricWakeLockRunnable);
+ if (DEBUG_BIO_WAKELOCK) {
+ Log.i(TAG, "releasing biometric wakelock");
}
mWakeLock.release();
mWakeLock = null;
}
@Override
- public void onFingerprintAcquired() {
- Trace.beginSection("FingerprintUnlockController#onFingerprintAcquired");
- releaseFingerprintWakeLock();
+ public void onBiometricAcquired(BiometricSourceType biometricSourceType) {
+ Trace.beginSection("BiometricUnlockController#onBiometricAcquired");
+ releaseBiometricWakeLock();
if (!mUpdateMonitor.isDeviceInteractive()) {
if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionStart(
- LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
+ int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK;
+ }
+ LatencyTracker.getInstance(mContext).onActionStart(action);
}
mWakeLock = mPowerManager.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK, FINGERPRINT_WAKE_LOCK_NAME);
+ PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME);
Trace.beginSection("acquiring wake-and-unlock");
mWakeLock.acquire();
Trace.endSection();
- if (DEBUG_FP_WAKELOCK) {
- Log.i(TAG, "fingerprint acquired, grabbing fp wakelock");
+ if (DEBUG_BIO_WAKELOCK) {
+ Log.i(TAG, "biometric acquired, grabbing biometric wakelock");
}
- mHandler.postDelayed(mReleaseFingerprintWakeLockRunnable,
- FINGERPRINT_WAKELOCK_TIMEOUT_MS);
+ mHandler.postDelayed(mReleaseBiometricWakeLockRunnable,
+ BIOMETRIC_WAKELOCK_TIMEOUT_MS);
}
Trace.endSection();
}
}
@Override
- public void onFingerprintAuthenticated(int userId) {
- Trace.beginSection("FingerprintUnlockController#onFingerprintAuthenticated");
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+ Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
if (mUpdateMonitor.isGoingToSleep()) {
mPendingAuthenticatedUserId = userId;
+ mPendingAuthenticatedBioSourceType = biometricSourceType;
Trace.endSection();
return;
}
mStatusBarWindowManager.setForceDozeBrightness(true);
}
if (!wasDeviceInteractive) {
- if (DEBUG_FP_WAKELOCK) {
- Log.i(TAG, "fp wakelock: Authenticated, waking up...");
+ if (DEBUG_BIO_WAKELOCK) {
+ Log.i(TAG, "bio wakelock: Authenticated, waking up...");
}
- mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:FINGERPRINT");
+ mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:BIOMETRIC");
}
Trace.beginSection("release wake-and-unlock");
- releaseFingerprintWakeLock();
+ releaseBiometricWakeLock();
Trace.endSection();
switch (mMode) {
case MODE_DISMISS_BOUNCER:
case MODE_NONE:
break;
}
- mStatusBar.notifyFpAuthModeChanged();
+ mStatusBar.notifyBiometricAuthModeChanged();
Trace.endSection();
}
mStatusBarKeyguardViewManager.showBouncer(false);
}
mStatusBarKeyguardViewManager.animateCollapsePanels(
- FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
+ BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
mPendingShowBouncer = false;
}
public void onStartedGoingToSleep(int why) {
resetMode();
mPendingAuthenticatedUserId = -1;
+ mPendingAuthenticatedBioSourceType = null;
}
@Override
public void onFinishedGoingToSleep(int why) {
- Trace.beginSection("FingerprintUnlockController#onFinishedGoingToSleep");
+ Trace.beginSection("BiometricUnlockController#onFinishedGoingToSleep");
if (mPendingAuthenticatedUserId != -1) {
// Post this to make sure it's executed after the device is fully locked.
mHandler.post(new Runnable() {
@Override
public void run() {
- onFingerprintAuthenticated(mPendingAuthenticatedUserId);
+ onBiometricAuthenticated(mPendingAuthenticatedUserId,
+ mPendingAuthenticatedBioSourceType);
}
});
}
mPendingAuthenticatedUserId = -1;
+ mPendingAuthenticatedBioSourceType = null;
Trace.endSection();
}
public boolean hasPendingAuthentication() {
return mPendingAuthenticatedUserId != -1
- && mUpdateMonitor.isUnlockingWithFingerprintAllowed()
+ && mUpdateMonitor.isUnlockingWithBiometricAllowed()
&& mPendingAuthenticatedUserId == KeyguardUpdateMonitor.getCurrentUser();
}
}
private int calculateMode() {
- boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithFingerprintAllowed();
+ boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed();
boolean deviceDreaming = mUpdateMonitor.isDreaming();
if (!mUpdateMonitor.isDeviceInteractive()) {
}
@Override
- public void onFingerprintAuthFailed() {
+ public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
cleanup();
}
@Override
- public void onFingerprintError(int msgId, String errString) {
+ public void onBiometricError(int msgId, String errString,
+ BiometricSourceType biometricSourceType) {
cleanup();
}
private void cleanup() {
- releaseFingerprintWakeLock();
+ releaseBiometricWakeLock();
}
public void startKeyguardFadingAway() {
if (mStatusBar.getNavigationBarView() != null) {
mStatusBar.getNavigationBarView().setWakeAndUnlocking(false);
}
- mStatusBar.notifyFpAuthModeChanged();
+ mStatusBar.notifyBiometricAuthModeChanged();
}
private final WakefulnessLifecycle.Observer mWakefulnessObserver =
@Override
public void onFinishedWakingUp() {
if (mPendingShowBouncer) {
- FingerprintUnlockController.this.showBouncer();
+ BiometricUnlockController.this.showBouncer();
}
}
};
}
public void dump(PrintWriter pw) {
- pw.println(" FingerprintUnlockController:");
+ pw.println(" BiometricUnlockController:");
pw.print(" mMode="); pw.println(mMode);
pw.print(" mWakeLock="); pw.println(mWakeLock);
}
/**
* Successful authentication with fingerprint when the screen was either on or off.
*/
- public boolean isFingerprintUnlock() {
+ public boolean isBiometricUnlock() {
return isWakeAndUnlock() || mMode == MODE_UNLOCK;
}
}
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.admin.DevicePolicyManager;
+import android.hardware.biometrics.BiometricSourceType;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
}
@Override
- public void onFingerprintRunningStateChanged(boolean running) {
+ public void onBiometricRunningStateChanged(boolean running,
+ BiometricSourceType biometricSourceType) {
mLockIcon.update();
}
private final DarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
- private FingerprintUnlockController mFingerprintUnlockController;
+ private BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
private int mSystemUiVisibility;
updateNavigation();
}
- public void setFingerprintUnlockController(
- FingerprintUnlockController fingerprintUnlockController) {
- mFingerprintUnlockController = fingerprintUnlockController;
+ public void setBiometricUnlockController(
+ BiometricUnlockController biometricUnlockController) {
+ mBiometricUnlockController = biometricUnlockController;
}
public void onSystemUiVisibilityChanged(int fullscreenStackVis, int dockedStackVis,
}
private boolean animateChange() {
- if (mFingerprintUnlockController == null) {
+ if (mBiometricUnlockController == null) {
return false;
}
- int unlockMode = mFingerprintUnlockController.getMode();
- return unlockMode != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
- && unlockMode != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
+ int unlockMode = mBiometricUnlockController.getMode();
+ return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+ && unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
}
private void updateStatus(Rect fullscreenStackBounds, Rect dockedStackBounds) {
private int getState() {
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning();
- boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed();
+ boolean unlockingAllowed = updateMonitor.isUnlockingWithBiometricAllowed();
if (mTransientFpError) {
return STATE_FINGERPRINT_ERROR;
} else if (mUnlockMethodCache.canSkipBouncer()) {
private VolumeComponent mVolumeComponent;
private BrightnessMirrorController mBrightnessMirrorController;
private boolean mBrightnessMirrorVisible;
- protected FingerprintUnlockController mFingerprintUnlockController;
+ protected BiometricUnlockController mBiometricUnlockController;
private LightBarController mLightBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
protected void startKeyguard() {
Trace.beginSection("StatusBar#startKeyguard");
KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
- mFingerprintUnlockController = new FingerprintUnlockController(mContext,
+ mBiometricUnlockController = new BiometricUnlockController(mContext,
mDozeScrimController, keyguardViewMediator,
mScrimController, this, UnlockMethodCache.getInstance(mContext));
mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
- getBouncerContainer(), mNotificationPanel, mFingerprintUnlockController);
+ getBouncerContainer(), mNotificationPanel, mBiometricUnlockController);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
- mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+ mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mRemoteInputManager.getController().addCallback(mStatusBarKeyguardViewManager);
mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback();
- mLightBarController.setFingerprintUnlockController(mFingerprintUnlockController);
+ mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
Dependency.get(KeyguardDismissUtil.class).setDismissHandler(this::executeWhenUnlocked);
Trace.endSection();
}
return; // called too early
}
- boolean wakeAndUnlock = mFingerprintUnlockController != null
- && mFingerprintUnlockController.isWakeAndUnlock();
+ boolean wakeAndUnlock = mBiometricUnlockController != null
+ && mBiometricUnlockController.isWakeAndUnlock();
if (mLaunchTransitionFadingAway || wakeAndUnlock) {
mBackdrop.setVisibility(View.INVISIBLE);
Trace.endSection();
if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) && !mDozing
&& (mState != StatusBarState.SHADE || allowWhenShade)
- && mFingerprintUnlockController.getMode()
- != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+ && mBiometricUnlockController.getMode()
+ != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
&& !hideBecauseOccluded) {
// time to show some art!
if (mBackdrop.getVisibility() != View.VISIBLE) {
}
mColorExtractor.setMediaBackdropVisible(false);
boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange();
- if (mFingerprintUnlockController.getMode()
- == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+ if (mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
|| hideBecauseOccluded || cannotAnimateDoze) {
// We are unlocking directly - no animation!
return mGestureRec;
}
- public FingerprintUnlockController getFingerprintUnlockController() {
- return mFingerprintUnlockController;
+ public BiometricUnlockController getBiometricUnlockController() {
+ return mBiometricUnlockController;
}
@Override // CommandQueue
DozeLog.dump(pw);
- if (mFingerprintUnlockController != null) {
- mFingerprintUnlockController.dump(pw);
+ if (mBiometricUnlockController != null) {
+ mBiometricUnlockController.dump(pw);
}
if (mKeyguardIndicationController != null) {
&& mUnlockMethodCache.canSkipBouncer()
&& !mLeaveOpenOnKeyguardHide
&& isPulsing()) {
- // Reuse the fingerprint wake-and-unlock transition if we dismiss keyguard from a pulse.
- // TODO: Factor this transition out of FingerprintUnlockController.
- mFingerprintUnlockController.startWakeAndUnlock(
- FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
+ // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a pulse.
+ // TODO: Factor this transition out of BiometricUnlockController.
+ mBiometricUnlockController.startWakeAndUnlock(
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
}
if (mStatusBarKeyguardViewManager.isShowing()) {
mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
}
private boolean updateIsKeyguard() {
- boolean wakeAndUnlocking = mFingerprintUnlockController.getMode()
- == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
+ boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
// For dozing, keyguard needs to be shown whenever the device is non-interactive. Otherwise
// there's no surface we can show to the user. Note that the device goes fully interactive
}
private void updatePanelExpansionForKeyguard() {
- if (mState == StatusBarState.KEYGUARD && mFingerprintUnlockController.getMode()
- != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
+ if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
+ != BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
instantExpandNotificationsPanel();
} else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
instantCollapseNotificationPanel();
|| mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
}
- public void notifyFpAuthModeChanged() {
+ public void notifyBiometricAuthModeChanged() {
updateDozing();
updateScrimController();
}
Trace.beginSection("StatusBar#updateDozing");
// When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
- || mFingerprintUnlockController.getMode()
- == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+ || mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
final boolean alwaysOn = DozeParameters.getInstance(mContext).getAlwaysOn();
// When in wake-and-unlock we may not have received a change to mState
// but we still should not be dozing, manually set to false.
- if (mFingerprintUnlockController.getMode() ==
- FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
+ if (mBiometricUnlockController.getMode() ==
+ mBiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
dozing = false;
}
if (mDozing != dozing) {
// We don't want to end up in KEYGUARD state when we're unlocking with
// fingerprint from doze. We should cross fade directly from black.
- boolean wakeAndUnlocking = mFingerprintUnlockController.isWakeAndUnlock();
+ boolean wakeAndUnlocking = mBiometricUnlockController.isWakeAndUnlock();
// Do not animate the scrim expansion when triggered by the fingerprint sensor.
mScrimController.setExpansionAffectsAlpha(
- !mFingerprintUnlockController.isFingerprintUnlock());
+ !mBiometricUnlockController.isBiometricUnlock());
if (mBouncerShowing) {
// Bouncer needs the front scrim when it's on top of an activity,
@Override
public boolean isPulsingBlocked() {
- return mFingerprintUnlockController.getMode()
- == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
+ return mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
}
@Override
@Override
public boolean isBlockingDoze() {
- if (mFingerprintUnlockController.hasPendingAuthentication()) {
+ if (mBiometricUnlockController.hasPendingAuthentication()) {
Log.i(TAG, "Blocking AOD because fingerprint has authenticated");
return true;
}
package com.android.systemui.statusbar.phone;
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
-import static com.android.systemui.statusbar.phone.FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
-import static com.android.systemui.statusbar.phone.FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
import android.content.ComponentCallbacks2;
import android.content.Context;
protected ViewMediatorCallback mViewMediatorCallback;
protected StatusBar mStatusBar;
private NotificationPanelView mNotificationPanelView;
- private FingerprintUnlockController mFingerprintUnlockController;
+ private BiometricUnlockController mBiometricUnlockController;
private ViewGroup mContainer;
private boolean mLastBouncerDismissible;
protected boolean mLastRemoteInputActive;
private boolean mLastDozing;
- private int mLastFpMode;
+ private int mLastBiometricMode;
private boolean mGoingToSleepVisibleNotOccluded;
private OnDismissAction mAfterKeyguardGoneAction;
public void registerStatusBar(StatusBar statusBar,
ViewGroup container,
NotificationPanelView notificationPanelView,
- FingerprintUnlockController fingerprintUnlockController,
+ BiometricUnlockController biometricUnlockController,
DismissCallbackRegistry dismissCallbackRegistry) {
mStatusBar = statusBar;
mContainer = container;
- mFingerprintUnlockController = fingerprintUnlockController;
+ mBiometricUnlockController = biometricUnlockController;
mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
mExpansionCallback);
}
private boolean isWakeAndUnlocking() {
- int mode = mFingerprintUnlockController.getMode();
+ int mode = mBiometricUnlockController.getMode();
return mode == MODE_WAKE_AND_UNLOCK || mode == MODE_WAKE_AND_UNLOCK_PULSING;
}
} else {
executeAfterKeyguardGoneAction();
boolean wakeUnlockPulsing =
- mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
+ mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
if (wakeUnlockPulsing) {
delay = 0;
fadeoutDuration = 240;
}
mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
- mFingerprintUnlockController.startKeyguardFadingAway();
+ mBiometricUnlockController.startKeyguardFadingAway();
hideBouncer(true /* destroyView */);
if (wakeUnlockPulsing) {
mStatusBar.fadeKeyguardWhilePulsing();
wakeAndUnlockDejank();
} else {
mStatusBar.finishKeyguardFadingAway();
- mFingerprintUnlockController.finishKeyguardFadingAway();
+ mBiometricUnlockController.finishKeyguardFadingAway();
}
}
updateStates();
mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
100);
mStatusBar.finishKeyguardFadingAway();
- mFingerprintUnlockController.finishKeyguardFadingAway();
+ mBiometricUnlockController.finishKeyguardFadingAway();
WindowManagerGlobal.getInstance().trimMemory(
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
}
private void wakeAndUnlockDejank() {
- if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
+ if (mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
&& LatencyTracker.isEnabled(mContext)) {
DejankUtils.postAfterTraversal(() ->
LatencyTracker.getInstance(mContext).onActionEnd(
mLastBouncerDismissible = bouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
mLastDozing = mDozing;
- mLastFpMode = mFingerprintUnlockController.getMode();
+ mLastBiometricMode = mBiometricUnlockController.getMode();
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
* @return Whether the navigation bar should be made visible based on the current state.
*/
protected boolean isNavBarVisible() {
- int fpMode = mFingerprintUnlockController.getMode();
+ int biometricMode = mBiometricUnlockController.getMode();
boolean keyguardShowing = mShowing && !mOccluded;
- boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
+ boolean hideWhileDozing = mDozing && biometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
|| mRemoteInputActive);
}
*/
protected boolean getLastNavBarVisible() {
boolean keyguardShowing = mLastShowing && !mLastOccluded;
- boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
+ boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
|| mLastRemoteInputActive);
}
package com.android.systemui.statusbar.phone;
+import android.hardware.biometrics.BiometricSourceType;
import android.content.Context;
import android.os.Trace;
}
@Override
- public void onFingerprintAuthenticated(int userId) {
- Trace.beginSection("KeyguardUpdateMonitorCallback#onFingerprintAuthenticated");
- if (!mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()) {
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+ Trace.beginSection("KeyguardUpdateMonitorCallback#onBiometricAuthenticated");
+ if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
Trace.endSection();
return;
}
@Mock
private NotificationPanelView mNotificationPanelView;
@Mock
- private FingerprintUnlockController mFingerprintUnlockController;
+ private BiometricUnlockController mBiometrucUnlockController;
@Mock
private DismissCallbackRegistry mDismissCallbackRegistry;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
mStatusBarKeyguardViewManager = new TestableStatusBarKeyguardViewManager(getContext(),
mViewMediatorCallback, mLockPatternUtils);
mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
- mNotificationPanelView, mFingerprintUnlockController, mDismissCallbackRegistry);
+ mNotificationPanelView, mBiometrucUnlockController, mDismissCallbackRegistry);
mStatusBarKeyguardViewManager.show(null);
}
@Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
- when(mFingerprintUnlockController.getMode())
- .thenReturn(FingerprintUnlockController.MODE_WAKE_AND_UNLOCK);
+ when(mBiometrucUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(KeyguardBouncer.EXPANSION_VISIBLE,
false /* tracking */);
verify(mBouncer, never()).setExpansion(anyFloat());
@Override
public void registerStatusBar(StatusBar statusBar, ViewGroup container,
NotificationPanelView notificationPanelView,
- FingerprintUnlockController fingerprintUnlockController,
+ BiometricUnlockController fingerprintUnlockController,
DismissCallbackRegistry dismissCallbackRegistry) {
super.registerStatusBar(statusBar, container, notificationPanelView,
fingerprintUnlockController, dismissCallbackRegistry);
@Mock private IStatusBarService mBarService;
@Mock private ScrimController mScrimController;
@Mock private ArrayList<Entry> mNotificationList;
- @Mock private FingerprintUnlockController mFingerprintUnlockController;
+ @Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private NotificationData mNotificationData;
// Mock dependencies:
mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
mPowerManager, mNotificationPanelView, mBarService, mNotificationListener,
mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager,
- mEntryManager, mScrimController, mFingerprintUnlockController,
+ mEntryManager, mScrimController, mBiometricUnlockController,
mock(ActivityLaunchAnimator.class), mKeyguardViewMediator,
mRemoteInputManager, mock(NotificationGroupManager.class),
mock(FalsingManager.class), mock(StatusBarWindowManager.class),
@Test
public void testFingerprintNotification_UpdatesScrims() {
- mStatusBar.notifyFpAuthModeChanged();
+ mStatusBar.notifyBiometricAuthModeChanged();
verify(mScrimController).transitionTo(any(), any());
}
@Test
public void testFingerprintUnlock_UpdatesScrims() {
// Simulate unlocking from AoD with fingerprint.
- when(mFingerprintUnlockController.getMode())
- .thenReturn(FingerprintUnlockController.MODE_WAKE_AND_UNLOCK);
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
mStatusBar.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
}
VisualStabilityManager visualStabilityManager,
NotificationViewHierarchyManager viewHierarchyManager,
TestableNotificationEntryManager entryManager, ScrimController scrimController,
- FingerprintUnlockController fingerprintUnlockController,
+ BiometricUnlockController biometricUnlockController,
ActivityLaunchAnimator launchAnimator, KeyguardViewMediator keyguardViewMediator,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationGroupManager notificationGroupManager,
mViewHierarchyManager = viewHierarchyManager;
mEntryManager = entryManager;
mScrimController = scrimController;
- mFingerprintUnlockController = fingerprintUnlockController;
+ mBiometricUnlockController = biometricUnlockController;
mActivityLaunchAnimator = launchAnimator;
mKeyguardViewMediator = keyguardViewMediator;
mClearAllEnabled = true;
--- /dev/null
+/**
+ * Copyright (C) 2018 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.face;
+
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import android.content.Context;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * A class to keep track of the authentication state for a given client.
+ */
+public abstract class AuthenticationClient extends ClientMonitor {
+ private long mOpId;
+
+ public abstract int handleFailedAttempt();
+ public abstract void resetFailedAttempts();
+
+ public static final int LOCKOUT_NONE = 0;
+ public static final int LOCKOUT_TIMED = 1;
+ public static final int LOCKOUT_PERMANENT = 2;
+
+ public AuthenticationClient(Context context, long halDeviceId, IBinder token,
+ IFaceServiceReceiver receiver, int targetUserId, long opId,
+ boolean restricted, String owner) {
+ super(context, halDeviceId, token, receiver, targetUserId, restricted, owner);
+ mOpId = opId;
+ }
+
+ @Override
+ public boolean onAuthenticated(int faceId) {
+ boolean result = false;
+ boolean authenticated = faceId != 0;
+
+ IFaceServiceReceiver receiver = getReceiver();
+ if (receiver != null) {
+ try {
+ MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_AUTH,
+ authenticated); // TODO: Define ACTION_FACE_AUTH constant and use here
+ if (!authenticated) {
+ receiver.onAuthenticationFailed(getHalDeviceId());
+ } else {
+ if (DEBUG) {
+ Slog.v(TAG, "onAuthenticated(owner=" + getOwnerString());
+ }
+ Face face = !getIsRestricted()
+ ? new Face("" /* TODO */, faceId, getHalDeviceId())
+ : null;
+ receiver.onAuthenticationSucceeded(getHalDeviceId(), face);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify Authenticated:", e);
+ result = true; // client failed
+ }
+ } else {
+ result = true; // client not listening
+ }
+ if (!authenticated) {
+ // allow system-defined limit of number of attempts before giving up
+ int lockoutMode = handleFailedAttempt();
+ if (lockoutMode != LOCKOUT_NONE) {
+ try {
+ Slog.w(TAG, "Forcing lockout (face authentication driver code should do " +
+ "this!), mode(" + lockoutMode + ")");
+ stop(false);
+ int errorCode = lockoutMode == LOCKOUT_TIMED ?
+ FaceManager.FACE_ERROR_LOCKOUT :
+ FaceManager.FACE_ERROR_LOCKOUT_PERMANENT;
+ receiver.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify lockout:", e);
+ }
+ }
+ result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
+ } else {
+ if (receiver != null) {
+ vibrateSuccess();
+ }
+ result |= true; // we have a valid face, done
+ resetFailedAttempts();
+ }
+ return result;
+ }
+
+ /**
+ * Start authentication
+ */
+ @Override
+ public int start() {
+ IBiometricsFace daemon = getFaceDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "start authentication: no face HAL!");
+ return ERROR_ESRCH;
+ }
+ try {
+ final int result = daemon.authenticate(mOpId);
+ if (result != 0) {
+ Slog.w(TAG, "startAuthentication failed, result=" + result);
+ MetricsLogger.histogram(getContext(), "faced_auth_start_error", result);
+ onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ return result;
+ }
+ if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating...");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "startAuthentication failed", e);
+ return ERROR_ESRCH;
+ }
+ return 0; // success
+ }
+
+ @Override
+ public int stop(boolean initiatedByClient) {
+ if (mAlreadyCancelled) {
+ Slog.w(TAG, "stopAuthentication: already cancelled!");
+ return 0;
+ }
+ IBiometricsFace daemon = getFaceDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "stopAuthentication: no face HAL!");
+ return ERROR_ESRCH;
+ }
+ try {
+ final int result = daemon.cancel();
+ if (result != 0) {
+ Slog.w(TAG, "stopAuthentication failed, result=" + result);
+ return result;
+ }
+ if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer authenticating");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "stopAuthentication failed", e);
+ return ERROR_ESRCH;
+ }
+ mAlreadyCancelled = true;
+ return 0; // success
+ }
+
+ @Override
+ public boolean onEnrollResult(int faceId, int remaining) {
+ if (DEBUG) Slog.w(TAG, "onEnrollResult() called for authenticate!");
+ return true; // Invalid for Authenticate
+ }
+
+ @Override
+ public boolean onRemoved(int faceId, int remaining) {
+ if (DEBUG) Slog.w(TAG, "onRemoved() called for authenticate!");
+ return true; // Invalid for Authenticate
+ }
+}
--- /dev/null
+/**
+ * Copyright (C) 2018 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.face;
+
+import android.Manifest;
+import android.content.Context;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Slog;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Abstract base class for keeping track and dispatching events from face HAL to the
+ * current client. Subclasses are responsible for coordinating the interaction with
+ * face HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
+ */
+public abstract class ClientMonitor implements IBinder.DeathRecipient {
+ protected static final String TAG = FaceService.TAG; // TODO: get specific name
+ protected static final int ERROR_ESRCH = 3; // Likely face HAL is dead. See errno.h.
+ protected static final boolean DEBUG = FaceService.DEBUG;
+ private static final long[] DEFAULT_SUCCESS_VIBRATION_PATTERN = new long[] {0, 30};
+ private final Context mContext;
+ private final long mHalDeviceId;
+ private final int mTargetUserId;
+ // True if client does not have MANAGE_FACE_AUTHENTICATION permission
+ private final boolean mIsRestricted;
+ private final String mOwner;
+ private final VibrationEffect mSuccessVibrationEffect;
+ private IBinder mToken;
+ private IFaceServiceReceiver mReceiver;
+ protected boolean mAlreadyCancelled;
+
+ /**
+ * @param context context of FaceService
+ * @param halDeviceId the HAL device ID of the associated face authentication hardware
+ * @param token a unique token for the client
+ * @param receiver recipient of related events (e.g. authentication)
+ * @param userId target user id for operation
+ * @param restricted whether or not client has the {@link Manifest#MANAGE_FACE}
+ * permission
+ * @param owner name of the client that owns this
+ */
+ public ClientMonitor(Context context, long halDeviceId, IBinder token,
+ IFaceServiceReceiver receiver, int userId, boolean restricted,
+ String owner) {
+ mContext = context;
+ mHalDeviceId = halDeviceId;
+ mToken = token;
+ mReceiver = receiver;
+ mTargetUserId = userId;
+ mIsRestricted = restricted;
+ mOwner = owner;
+ mSuccessVibrationEffect = getSuccessVibrationEffect(context);
+ try {
+ if (token != null) {
+ token.linkToDeath(this, 0);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
+ }
+ }
+
+ /**
+ * Contacts face HAL to start the client.
+ * @return 0 on success, errno from driver on failure
+ */
+ public abstract int start();
+
+ /**
+ * Contacts face HAL to stop the client.
+ * @param initiatedByClient whether the operation is at the request of a client
+ */
+ public abstract int stop(boolean initiatedByClient);
+
+ /**
+ * Method to explicitly poke powermanager on events
+ */
+ public abstract void notifyUserActivity();
+
+ /**
+ * Gets the face daemon from the cached state in the container class.
+ */
+ public abstract IBiometricsFace getFaceDaemon();
+
+ // Event callbacks from driver. Inappropriate calls is flagged/logged by the
+ // respective client (e.g. enrolling shouldn't get authenticate events).
+ // All of these return 'true' if the operation is completed and it's ok to move
+ // to the next client (e.g. authentication accepts or rejects a face).
+ public abstract boolean onEnrollResult(int faceId, int remaining);
+ public abstract boolean onAuthenticated(int faceId);
+ public abstract boolean onRemoved(int faceId, int remaining);
+
+ /**
+ * Called when we get notification from face HAL that an image has been acquired.
+ * Common to authenticate and enroll.
+ * @param acquiredInfo info about the current image acquisition
+ * @return true if client should be removed
+ */
+ public boolean onAcquired(int acquiredInfo, int vendorCode) {
+ if (mReceiver == null)
+ return true; // client not connected
+ try {
+ mReceiver.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode);
+ return false; // acquisition continues...
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke sendAcquired:", e);
+ return true; // client failed
+ } finally {
+ // Good scans will keep the device awake
+ if (acquiredInfo == FaceManager.FACE_ACQUIRED_GOOD) {
+ notifyUserActivity();
+ }
+ }
+ }
+
+ /**
+ * Called when we get notification from face HAL that an error has occurred with the
+ * current operation. Common to authenticate, enroll, enumerate and remove.
+ * @param error
+ * @return true if client should be removed
+ */
+ public boolean onError(int error, int vendorCode) {
+ if (mReceiver != null) {
+ try {
+ mReceiver.onError(getHalDeviceId(), error, vendorCode);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke sendError:", e);
+ }
+ }
+ return true; // errors always remove current client
+ }
+
+ public void destroy() {
+ if (mToken != null) {
+ try {
+ mToken.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // TODO: remove when duplicate call bug is found
+ Slog.e(TAG, "destroy(): " + this + ":", new Exception("here"));
+ }
+ mToken = null;
+ }
+ mReceiver = null;
+ }
+
+ @Override
+ public void binderDied() {
+ mToken = null;
+ mReceiver = null;
+ onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mToken != null) {
+ if (DEBUG) Slog.w(TAG, "removing leaked reference: " + mToken);
+ onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public final Context getContext() {
+ return mContext;
+ }
+
+ public final long getHalDeviceId() {
+ return mHalDeviceId;
+ }
+
+ public final String getOwnerString() {
+ return mOwner;
+ }
+
+ public final IFaceServiceReceiver getReceiver() {
+ return mReceiver;
+ }
+
+ public final boolean getIsRestricted() {
+ return mIsRestricted;
+ }
+
+ public final int getTargetUserId() {
+ return mTargetUserId;
+ }
+
+ public final IBinder getToken() {
+ return mToken;
+ }
+
+ public final void vibrateSuccess() {
+ Vibrator vibrator = mContext.getSystemService(Vibrator.class);
+ if (vibrator != null) {
+ vibrator.vibrate(mSuccessVibrationEffect);
+ }
+ }
+
+// public final void vibrateError() {
+// }
+
+ private static VibrationEffect getSuccessVibrationEffect(Context ctx) {
+ int[] arr = ctx.getResources().getIntArray(
+ com.android.internal.R.array.config_longPressVibePattern);
+ final long[] vibePattern;
+ if (arr == null || arr.length == 0) {
+ vibePattern = DEFAULT_SUCCESS_VIBRATION_PATTERN;
+ } else {
+ vibePattern = new long[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ vibePattern[i] = arr[i];
+ }
+ }
+ if (vibePattern.length == 1) {
+ return VibrationEffect.createOneShot(
+ vibePattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
+ } else {
+ return VibrationEffect.createWaveform(vibePattern, -1);
+ }
+ }
+
+}
--- /dev/null
+/**
+ * Copyright (C) 2018 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.face;
+
+import android.content.Context;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * A class to keep track of the enrollment state for a given client.
+ */
+public abstract class EnrollClient extends ClientMonitor {
+ private static final long MS_PER_SEC = 1000;
+ private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute
+ private byte[] mCryptoToken;
+
+ public EnrollClient(Context context, long halDeviceId, IBinder token,
+ IFaceServiceReceiver receiver, int userId, byte [] cryptoToken, boolean restricted,
+ String owner) {
+ super(context, halDeviceId, token, receiver, userId, restricted, owner);
+ mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
+ }
+
+ @Override
+ public boolean onEnrollResult(int faceId, int remaining) {
+ if (remaining == 0) {
+ FaceUtils.getInstance().addFaceForUser(getContext(), faceId, getTargetUserId());
+ }
+ return sendEnrollResult(faceId, remaining);
+ }
+
+ /*
+ * @return true if we're done.
+ */
+ private boolean sendEnrollResult(int faceId, int remaining) {
+ IFaceServiceReceiver receiver = getReceiver();
+ if (receiver == null)
+ return true; // client not listening
+
+ vibrateSuccess();
+ MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_ENROLL);
+ // TODO: create ACTION_FACE_ENROLL constant and use it here
+ try {
+ receiver.onEnrollResult(getHalDeviceId(), faceId, remaining);
+ return remaining == 0;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify EnrollResult:", e);
+ return true;
+ }
+ }
+
+ @Override
+ public int start() {
+ IBiometricsFace daemon = getFaceDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "enroll: no face HAL!");
+ return ERROR_ESRCH;
+ }
+ final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
+ try {
+ // ugh...
+ final ArrayList<Byte> token = new ArrayList<>();
+ for (int i = 0; i < mCryptoToken.length; i++) {
+ token.add(mCryptoToken[i]);
+ }
+ final int result = daemon.enroll(token, timeout);
+ if (result != 0) {
+ Slog.w(TAG, "startEnroll failed, result=" + result);
+ MetricsLogger.histogram(getContext(), "faced_enroll_start_error", result);
+ onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ return result;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "startEnroll failed", e);
+ }
+ return 0; // success
+ }
+
+ @Override
+ public int stop(boolean initiatedByClient) {
+ if (mAlreadyCancelled) {
+ Slog.w(TAG, "stopEnroll: already cancelled!");
+ return 0;
+ }
+ IBiometricsFace daemon = getFaceDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "stopEnrollment: no face HAL!");
+ return ERROR_ESRCH;
+ }
+ try {
+ final int result = daemon.cancel();
+ if (result != 0) {
+ Slog.w(TAG, "startEnrollCancel failed, result = " + result);
+ return result;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "stopEnrollment failed", e);
+ }
+ if (initiatedByClient) {
+ onError(FaceManager.FACE_ERROR_CANCELED, 0 /* vendorCode */);
+ }
+ mAlreadyCancelled = true;
+ return 0;
+ }
+
+ @Override
+ public boolean onRemoved(int faceId, int remaining) {
+ if (DEBUG) Slog.w(TAG, "onRemoved() called for enroll!");
+ return true; // Invalid for EnrollClient
+ }
+
+ @Override
+ public boolean onAuthenticated(int faceId) {
+ if (DEBUG) Slog.w(TAG, "onAuthenticated() called for enroll!");
+ return true; // Invalid for EnrollClient
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 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.face;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_FACE;
+import static android.Manifest.permission.RESET_FACE_LOCKOUT;
+import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.app.SynchronousUserSwitchObserver;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceService;
+import android.hardware.face.IFaceServiceLockoutResetCallback;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IHwBinder;
+import android.os.IRemoteCallback;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.security.KeyStore;
+import android.service.face.FaceActionStatsProto;
+import android.service.face.FaceServiceDumpProto;
+import android.service.face.FaceUserStatsProto;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.SystemService;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A service to manage multiple clients that want to access the face HAL API.
+ * The service is responsible for maintaining a list of clients and dispatching all
+ * face -related events.
+ *
+ * @hide
+ */
+public class FaceService extends SystemService implements IHwBinder.DeathRecipient {
+ static final String TAG = "FaceService";
+ static final boolean DEBUG = true;
+ private static final String FACE_DATA_DIR = "facedata";
+ private static final int MSG_USER_SWITCHING = 10;
+ private static final String ACTION_LOCKOUT_RESET =
+ "com.android.server.face.ACTION_LOCKOUT_RESET";
+
+ private class PerformanceStats {
+ int accept; // number of accepted faces
+ int reject; // number of rejected faces
+ int acquire; // total number of acquisitions. Should be >= accept+reject due to poor image
+ // acquisition in some cases (too high, too low, poor gaze, etc.)
+ int lockout; // total number of lockouts
+ int permanentLockout; // total number of permanent lockouts
+ }
+
+ private final ArrayList<FaceServiceLockoutResetMonitor> mLockoutMonitors =
+ new ArrayList<>();
+ private final Map<Integer, Long> mAuthenticatorIds =
+ Collections.synchronizedMap(new HashMap<>());
+ private final AppOpsManager mAppOps;
+ private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000;
+ private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
+ private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20;
+
+ private static final long CANCEL_TIMEOUT_LIMIT_MS = 3000; // max wait for onCancel() from HAL,in ms
+ private final String mKeyguardPackage;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private final FaceUtils mFaceUtils = FaceUtils.getInstance();
+ private Context mContext;
+ private long mHalDeviceId;
+ private boolean mTimedLockoutCleared;
+ private int mFailedAttempts;
+ @GuardedBy("this")
+ private IBiometricsFace mDaemon;
+ private final PowerManager mPowerManager;
+ private final AlarmManager mAlarmManager;
+ private final UserManager mUserManager;
+ private ClientMonitor mCurrentClient;
+ private ClientMonitor mPendingClient;
+ private PerformanceStats mPerformanceStats;
+
+
+ private IBinder mToken = new Binder(); // used for internal FaceService enumeration
+
+ private class UserFace {
+ Face f;
+ int userId;
+ public UserFace(Face f, int userId) {
+ this.f = f;
+ this.userId = userId;
+ }
+ }
+
+ // Normal face authentications are tracked by mPerformanceMap.
+ private HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>();
+
+ // Transactions that make use of CryptoObjects are tracked by mCryptoPerformaceMap.
+ private HashMap<Integer, PerformanceStats> mCryptoPerformanceMap = new HashMap<>();
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(android.os.Message msg) {
+ switch (msg.what) {
+ case MSG_USER_SWITCHING:
+ handleUserSwitching(msg.arg1);
+ break;
+
+ default:
+ Slog.w(TAG, "Unknown message:" + msg.what);
+ }
+ }
+ };
+
+ private final BroadcastReceiver mLockoutReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) {
+ resetFailedAttempts(false /* clearAttemptCounter */);
+ }
+ }
+ };
+
+ private final Runnable mResetFailedAttemptsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ resetFailedAttempts(true /* clearAttemptCounter */);
+ }
+ };
+
+ private final Runnable mResetClientState = new Runnable() {
+ @Override
+ public void run() {
+ // Warning: if we get here, the driver never confirmed our call to cancel the current
+ // operation (authenticate, enroll, remove, enumerate, etc), which is
+ // really bad. The result will be a 3-second delay in starting each new client.
+ // If you see this on a device, make certain the driver notifies with
+ // {@link FaceAuthenticationManager#FACE_ERROR_CANCEL} in response to cancel()
+ // once it has successfully switched to the IDLE state in the face HAL.
+ // Additionally,{@link FaceAuthenticationManager#FACE_ERROR_CANCEL} should only be sent
+ // in response to an actual cancel() call.
+ Slog.w(TAG, "Client "
+ + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null")
+ + " failed to respond to cancel, starting client "
+ + (mPendingClient != null ? mPendingClient.getOwnerString() : "null"));
+
+ mCurrentClient = null;
+ startClient(mPendingClient, false);
+ }
+ };
+
+ public FaceService(Context context) {
+ super(context);
+ mContext = context;
+ mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString(
+ com.android.internal.R.string.config_keyguardComponent)).getPackageName();
+ mAppOps = context.getSystemService(AppOpsManager.class);
+ mPowerManager = mContext.getSystemService(PowerManager.class);
+ mAlarmManager = mContext.getSystemService(AlarmManager.class);
+ mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
+ RESET_FACE_LOCKOUT, null /* handler */);
+ mUserManager = UserManager.get(mContext);
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.v(TAG, "face HAL died");
+ MetricsLogger.count(mContext, "faced_died", 1);
+ handleError(mHalDeviceId, FaceManager.FACE_ERROR_HW_UNAVAILABLE,
+ 0 /*vendorCode */);
+ }
+
+ public synchronized IBiometricsFace getFaceDaemon() {
+ if (mDaemon == null) {
+ Slog.v(TAG, "mDaemon was null, reconnect to face");
+ try {
+ mDaemon = IBiometricsFace.getService();
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened. Logged below.
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get biometric interface", e);
+ }
+ if (mDaemon == null) {
+ Slog.w(TAG, "face HIDL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0);
+
+ try {
+ mHalDeviceId = mDaemon.setCallback(mDaemonCallback).value;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to open face HAL", e);
+ mDaemon = null; // try again later!
+ }
+
+ if (DEBUG) Slog.v(TAG, "face HAL id: " + mHalDeviceId);
+ if (mHalDeviceId != 0) {
+ loadAuthenticatorIds();
+ updateActiveGroup(ActivityManager.getCurrentUser(), null);
+ } else {
+ Slog.w(TAG, "Failed to open Face HAL!");
+ MetricsLogger.count(mContext, "faced_openhal_error", 1);
+ mDaemon = null;
+ }
+ }
+ return mDaemon;
+ }
+
+ /** Populates existing authenticator ids. To be used only during the start of the service. */
+ private void loadAuthenticatorIds() {
+ // This operation can be expensive, so keep track of the elapsed time. Might need to move to
+ // background if it takes too long.
+ long t = System.currentTimeMillis();
+ mAuthenticatorIds.clear();
+ for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+ int userId = getUserOrWorkProfileId(null, user.id);
+ if (!mAuthenticatorIds.containsKey(userId)) {
+ updateActiveGroup(userId, null);
+ }
+ }
+
+ t = System.currentTimeMillis() - t;
+ if (t > 1000) {
+ Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms");
+ }
+ }
+
+ protected void handleError(long deviceId, int error, int vendorCode) {
+ ClientMonitor client = mCurrentClient;
+ if (client != null && client.onError(error, vendorCode)) {
+ removeClient(client);
+ }
+
+ if (DEBUG) Slog.v(TAG, "handleError(client="
+ + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
+ // This is the magic code that starts the next client when the old client finishes.
+ if (error == FaceManager.FACE_ERROR_CANCELED) {
+ mHandler.removeCallbacks(mResetClientState);
+ if (mPendingClient != null) {
+ if (DEBUG) Slog.v(TAG, "start pending client " + mPendingClient.getOwnerString());
+ startClient(mPendingClient, false);
+ mPendingClient = null;
+ }
+ } else if (error == FaceManager.FACE_ERROR_HW_UNAVAILABLE) {
+ // If we get HW_UNAVAILABLE, try to connect again later...
+ Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client.");
+ synchronized (this) {
+ mDaemon = null;
+ mHalDeviceId = 0;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ }
+ }
+
+ protected void handleRemoved(long deviceId, int faceId, int userId, int remaining) {
+ if (DEBUG) Slog.w(TAG, "Removed: fid=" + faceId
+ + ", uid=" + userId
+ + ", dev=" + deviceId
+ + ", rem=" + remaining);
+
+ ClientMonitor client = mCurrentClient;
+ if (client != null && client.onRemoved(faceId, remaining)) {
+ removeClient(client);
+ }
+ }
+
+ protected void handleAuthenticated(long deviceId, int faceId, int userId,
+ ArrayList<Byte> token) {
+ ClientMonitor client = mCurrentClient;
+ if (faceId != 0) {
+ // Ugh...
+ final byte[] byteToken = new byte[token.size()];
+ for (int i = 0; i < token.size(); i++) {
+ byteToken[i] = token.get(i);
+ }
+ // Send to Keystore
+ KeyStore.getInstance().addAuthToken(byteToken);
+ }
+ if (client != null && client.onAuthenticated(faceId)) {
+ removeClient(client);
+ }
+ if (faceId != 0) {
+ mPerformanceStats.accept++;
+ } else {
+ mPerformanceStats.reject++;
+ }
+ }
+
+ protected void handleAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+ ClientMonitor client = mCurrentClient;
+ if (client != null && client.onAcquired(acquiredInfo, vendorCode)) {
+ removeClient(client);
+ }
+ if (mPerformanceStats != null && getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
+ && client instanceof AuthenticationClient) {
+ // ignore enrollment acquisitions or acquisitions when we're locked out
+ mPerformanceStats.acquire++;
+ }
+ }
+
+ protected void handleEnrollResult(long deviceId, int faceId, int userId, int remaining) {
+ ClientMonitor client = mCurrentClient;
+ if (client != null && client.onEnrollResult(faceId, remaining)) {
+ removeClient(client);
+ }
+ }
+
+ private void userActivity() {
+ long now = SystemClock.uptimeMillis();
+ mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+ }
+
+ void handleUserSwitching(int userId) {
+ updateActiveGroup(userId, null);
+ }
+
+ private void removeClient(ClientMonitor client) {
+ if (client != null) {
+ client.destroy();
+ if (client != mCurrentClient && mCurrentClient != null) {
+ Slog.w(TAG, "Unexpected client: " + client.getOwnerString() + "expected: "
+ + mCurrentClient != null ? mCurrentClient.getOwnerString() : "null");
+ }
+ }
+ if (mCurrentClient != null) {
+ if (DEBUG) Slog.v(TAG, "Done with client: " + client.getOwnerString());
+ mCurrentClient = null;
+ }
+ }
+
+ private int getLockoutMode() {
+ if (mFailedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
+ return AuthenticationClient.LOCKOUT_PERMANENT;
+ } else if (mFailedAttempts > 0 && mTimedLockoutCleared == false &&
+ (mFailedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
+ return AuthenticationClient.LOCKOUT_TIMED;
+ }
+ return AuthenticationClient.LOCKOUT_NONE;
+ }
+
+ private void scheduleLockoutReset() {
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, getLockoutResetIntent());
+ }
+
+ private void cancelLockoutReset() {
+ mAlarmManager.cancel(getLockoutResetIntent());
+ }
+
+ private PendingIntent getLockoutResetIntent() {
+ return PendingIntent.getBroadcast(mContext, 0,
+ new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ public long startPreEnroll(IBinder token) {
+ IBiometricsFace daemon = getFaceDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "startPreEnroll: no face HAL!");
+ return 0;
+ }
+ try {
+ return daemon.preEnroll().value;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "startPreEnroll failed", e);
+ }
+ return 0;
+ }
+
+ public int startPostEnroll(IBinder token) {
+ IBiometricsFace daemon = getFaceDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "startPostEnroll: no face HAL!");
+ return 0;
+ }
+ try {
+ return daemon.postEnroll();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "startPostEnroll failed", e);
+ }
+ return 0;
+ }
+
+ /**
+ * Calls face HAL to switch states to the new task. If there's already a current task,
+ * it calls cancel() and sets mPendingClient to begin when the current task finishes
+ * ({@link FaceManager#FACE_ERROR_CANCELED}).
+ * @param newClient the new client that wants to connect
+ * @param initiatedByClient true for authenticate, remove and enroll
+ */
+ private void startClient(ClientMonitor newClient, boolean initiatedByClient) {
+ ClientMonitor currentClient = mCurrentClient;
+ if (currentClient != null) {
+ if (DEBUG) Slog.v(TAG, "request stop current client " + currentClient.getOwnerString());
+ if (currentClient instanceof InternalRemovalClient) {
+ // This condition means we're currently running internal diagnostics to
+ // remove a face in the hardware and/or the software
+ // TODO: design an escape hatch in case client never finishes
+ }
+ else {
+ currentClient.stop(initiatedByClient);
+ }
+ mPendingClient = newClient;
+ mHandler.removeCallbacks(mResetClientState);
+ mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT_MS);
+ } else if (newClient != null) {
+ mCurrentClient = newClient;
+ if (DEBUG) Slog.v(TAG, "starting client "
+ + newClient.getClass().getSuperclass().getSimpleName()
+ + "(" + newClient.getOwnerString() + ")"
+ + ", initiatedByClient = " + initiatedByClient + ")");
+ newClient.start();
+ }
+ }
+
+ void startRemove(IBinder token, int userId, IFaceServiceReceiver receiver, boolean restricted,
+ boolean internal) {
+ IBiometricsFace daemon = getFaceDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "startRemove: no face HAL!");
+ return;
+ }
+
+ if (internal) {
+ Context context = getContext();
+ InternalRemovalClient client = new InternalRemovalClient(context, mHalDeviceId,
+ token, receiver, userId, restricted, context.getOpPackageName()) {
+ @Override
+ public void notifyUserActivity() {
+
+ }
+ @Override
+ public IBiometricsFace getFaceDaemon() {
+ return FaceService.this.getFaceDaemon();
+ }
+ };
+ startClient(client, true);
+ }
+ else {
+ RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
+ receiver, userId, restricted, token.toString()) {
+ @Override
+ public void notifyUserActivity() {
+ FaceService.this.userActivity();
+ }
+
+ @Override
+ public IBiometricsFace getFaceDaemon() {
+ return FaceService.this.getFaceDaemon();
+ }
+ };
+ startClient(client, true);
+ }
+ }
+
+ /*
+ * @hide
+ */
+ public Face getEnrolledFace(int userId) {
+ return mFaceUtils.getFaceForUser(mContext, userId);
+ }
+
+ public boolean hasEnrolledFace(int userId) {
+ if (userId != UserHandle.getCallingUserId()) {
+ checkPermission(INTERACT_ACROSS_USERS);
+ }
+ return mFaceUtils.getFaceForUser(mContext, userId) != null;
+ }
+
+ boolean hasPermission(String permission) {
+ return getContext().checkCallingOrSelfPermission(permission)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ void checkPermission(String permission) {
+ getContext().enforceCallingOrSelfPermission(permission,
+ "Must have " + permission + " permission.");
+ }
+
+ int getEffectiveUserId(int userId) {
+ UserManager um = UserManager.get(mContext);
+ if (um != null) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ userId = um.getCredentialOwnerProfile(userId);
+ Binder.restoreCallingIdentity(callingIdentity);
+ } else {
+ Slog.e(TAG, "Unable to acquire UserManager");
+ }
+ return userId;
+ }
+
+ boolean isCurrentUserOrProfile(int userId) {
+ UserManager um = UserManager.get(mContext);
+ if (um == null) {
+ Slog.e(TAG, "Unable to acquire UserManager");
+ return false;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Allow current user or profiles of the current user...
+ for (int profileId : um.getEnabledProfileIds(ActivityManager.getCurrentUser())) {
+ if (profileId == userId) {
+ return true;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return false;
+ }
+
+ private boolean isForegroundActivity(int uid, int pid) {
+ try {
+ List<RunningAppProcessInfo> procs =
+ ActivityManager.getService().getRunningAppProcesses();
+ int N = procs.size();
+ for (int i = 0; i < N; i++) {
+ RunningAppProcessInfo proc = procs.get(i);
+ if (proc.pid == pid && proc.uid == uid
+ && proc.importance == IMPORTANCE_FOREGROUND) {
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "am.getRunningAppProcesses() failed");
+ }
+ return false;
+ }
+
+ /**
+ * @param opPackageName name of package for caller
+ * @param requireForeground only allow this call while app is in the foreground
+ * @return true if caller can use face API
+ */
+ private boolean canUseFace(String opPackageName, boolean requireForeground, int uid,
+ int pid, int userId) {
+ checkPermission(USE_BIOMETRIC);
+ if (isKeyguard(opPackageName)) {
+ return true; // Keyguard is always allowed
+ }
+ if (!isCurrentUserOrProfile(userId)) {
+ Slog.w(TAG,"Rejecting " + opPackageName + " ; not a current user or profile");
+ return false;
+ }
+ if (mAppOps.noteOp(AppOpsManager.OP_USE_FACE, uid, opPackageName)
+ != AppOpsManager.MODE_ALLOWED) {
+ Slog.w(TAG, "Rejecting " + opPackageName + " ; permission denied");
+ return false;
+ }
+ if (requireForeground && !(isForegroundActivity(uid, pid) || currentClient(opPackageName))){
+ Slog.w(TAG, "Rejecting " + opPackageName + " ; not in foreground");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @param opPackageName package of the caller
+ * @return true if this is the same client currently using face
+ */
+ private boolean currentClient(String opPackageName) {
+ return mCurrentClient != null && mCurrentClient.getOwnerString().equals(opPackageName);
+ }
+
+ /**
+ * @param clientPackage
+ * @return true if this is keyguard package
+ */
+ private boolean isKeyguard(String clientPackage) {
+ return mKeyguardPackage.equals(clientPackage);
+ }
+
+ private void addLockoutResetMonitor(FaceServiceLockoutResetMonitor monitor) {
+ if (!mLockoutMonitors.contains(monitor)) {
+ mLockoutMonitors.add(monitor);
+ }
+ }
+
+ private void removeLockoutResetCallback(
+ FaceServiceLockoutResetMonitor monitor) {
+ mLockoutMonitors.remove(monitor);
+ }
+
+ private void notifyLockoutResetMonitors() {
+ for (int i = 0; i < mLockoutMonitors.size(); i++) {
+ mLockoutMonitors.get(i).sendLockoutReset();
+ }
+ }
+
+ private void startAuthentication(IBinder token, long opId, int callingUserId,
+ IFaceServiceReceiver receiver, int flags, boolean restricted,
+ String opPackageName) {
+ updateActiveGroup(callingUserId, opPackageName);
+
+ if (DEBUG) Slog.v(TAG, "startAuthentication(" + opPackageName + ")");
+
+ AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token,
+ receiver, mCurrentUserId, opId, restricted, opPackageName) {
+ @Override
+ public int handleFailedAttempt() {
+ mFailedAttempts++;
+ mTimedLockoutCleared = false;
+ final int lockoutMode = getLockoutMode();
+ if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
+ mPerformanceStats.permanentLockout++;
+ } else if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
+ mPerformanceStats.lockout++;
+ }
+
+ // Failing multiple times will continue to push out the lockout time
+ if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
+ scheduleLockoutReset();
+ return lockoutMode;
+ }
+ return AuthenticationClient.LOCKOUT_NONE;
+ }
+
+ @Override
+ public void resetFailedAttempts() {
+ FaceService.this.resetFailedAttempts(true /* clearAttemptCounter */);
+ }
+
+ @Override
+ public void notifyUserActivity() {
+ FaceService.this.userActivity();
+ }
+
+ @Override
+ public IBiometricsFace getFaceDaemon() {
+ return FaceService.this.getFaceDaemon();
+ }
+ };
+
+ int lockoutMode = getLockoutMode();
+ if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
+ Slog.v(TAG, "In lockout mode(" + lockoutMode +
+ ") ; disallowing authentication");
+ int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
+ FaceManager.FACE_ERROR_LOCKOUT :
+ FaceManager.FACE_ERROR_LOCKOUT_PERMANENT;
+ if (!client.onError(errorCode, 0 /* vendorCode */)) {
+ Slog.w(TAG, "Cannot send permanent lockout message to client");
+ }
+ return;
+ }
+ startClient(client, true /* initiatedByClient */);
+ }
+
+ private void startEnrollment(IBinder token, byte [] cryptoToken, int userId,
+ IFaceServiceReceiver receiver, int flags, boolean restricted,
+ String opPackageName) {
+ updateActiveGroup(userId, opPackageName);
+
+ EnrollClient client = new EnrollClient(getContext(), mHalDeviceId, token, receiver,
+ userId, cryptoToken, restricted, opPackageName) {
+
+ @Override
+ public IBiometricsFace getFaceDaemon() {
+ return FaceService.this.getFaceDaemon();
+ }
+
+ @Override
+ public void notifyUserActivity() {
+ FaceService.this.userActivity();
+ }
+ };
+ startClient(client, true /* initiatedByClient */);
+ }
+
+ // attempt counter should only be cleared when Keyguard goes away or when
+ // a face is successfully authenticated
+ protected void resetFailedAttempts(boolean clearAttemptCounter) {
+ if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
+ Slog.v(TAG, "Reset face lockout, clearAttemptCounter=" + clearAttemptCounter);
+ }
+ if (clearAttemptCounter) {
+ mFailedAttempts = 0;
+ }
+ mTimedLockoutCleared = true;
+ // If we're asked to reset failed attempts externally (i.e. from Keyguard),
+ // the alarm might still be pending; remove it.
+ cancelLockoutReset();
+ notifyLockoutResetMonitors();
+ }
+
+ private class FaceServiceLockoutResetMonitor {
+
+ private static final long WAKELOCK_TIMEOUT_MS = 2000;
+ private final IFaceServiceLockoutResetCallback mCallback;
+ private final WakeLock mWakeLock;
+
+ public FaceServiceLockoutResetMonitor(
+ IFaceServiceLockoutResetCallback callback) {
+ mCallback = callback;
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "lockout reset callback");
+ }
+
+ public void sendLockoutReset() {
+ if (mCallback != null) {
+ try {
+ mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
+ mCallback.onLockoutReset(mHalDeviceId, new IRemoteCallback.Stub() {
+
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+ });
+ } catch (DeadObjectException e) {
+ Slog.w(TAG, "Death object while invoking onLockoutReset: ", e);
+ mHandler.post(mRemoveCallbackRunnable);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke onLockoutReset: ", e);
+ }
+ }
+ }
+
+ private final Runnable mRemoveCallbackRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ removeLockoutResetCallback(FaceServiceLockoutResetMonitor.this);
+ }
+ };
+ }
+
+ private IBiometricsFaceClientCallback mDaemonCallback =
+ new IBiometricsFaceClientCallback.Stub() {
+
+ @Override
+ public void onEnrollResult(final long deviceId, int faceId, int userId, int remaining) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ handleEnrollResult(deviceId, faceId, userId, remaining);
+ }
+ });
+ }
+
+ @Override
+ public void onAcquired(final long deviceId, final int userId, final int acquiredInfo,
+ final int vendorCode) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ handleAcquired(deviceId, acquiredInfo, vendorCode);
+ }
+ });
+ }
+
+ @Override
+ public void onAuthenticated(final long deviceId, final int faceId, final int userId,
+ ArrayList<Byte> token) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ handleAuthenticated(deviceId, faceId, userId, token);
+ }
+ });
+ }
+
+ @Override
+ public void onError(final long deviceId, final int userId, final int error,
+ final int vendorCode) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ handleError(deviceId, error, vendorCode);
+ }
+ });
+ }
+
+ @Override
+ public void onRemoved(final long deviceId, final int faceId, final int userId,
+ final int remaining) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ handleRemoved(deviceId, faceId, userId, remaining);
+ }
+ });
+ }
+
+ @Override
+ public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId)
+ throws RemoteException {
+
+ }
+ };
+
+ private final class FaceServiceWrapper extends IFaceService.Stub {
+ @Override // Binder call
+ public long preEnroll(IBinder token) {
+ checkPermission(MANAGE_FACE);
+ return startPreEnroll(token);
+ }
+
+ @Override // Binder call
+ public int postEnroll(IBinder token) {
+ checkPermission(MANAGE_FACE);
+ return startPostEnroll(token);
+ }
+
+ @Override // Binder call
+ public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
+ final IFaceServiceReceiver receiver, final int flags,
+ final String opPackageName) {
+ checkPermission(MANAGE_FACE);
+
+ Face enrolledFace = FaceService.this.getEnrolledFace(userId);
+
+ if (enrolledFace != null) {
+ Slog.w(TAG, "Multiple faces enrollment is not supported");
+ return;
+ }
+
+
+ final boolean restricted = isRestricted();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ startEnrollment(token, cryptoToken, userId, receiver, flags,
+ restricted, opPackageName);
+ }
+ });
+ }
+
+ private boolean isRestricted() {
+ // Only give privileged apps (like Settings) access to faces info
+ final boolean restricted = !hasPermission(MANAGE_FACE);
+ return restricted;
+ }
+
+ @Override // Binder call
+ public void cancelEnrollment(final IBinder token) {
+ checkPermission(MANAGE_FACE);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ClientMonitor client = mCurrentClient;
+ if (client instanceof EnrollClient && client.getToken() == token) {
+ client.stop(client.getToken() == token);
+ }
+ }
+ });
+ }
+
+ @Override // Binder call
+ public void authenticate(final IBinder token, final long opId,
+ final IFaceServiceReceiver receiver, final int flags,
+ final String opPackageName) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getCallingUserId();
+ final int pid = Binder.getCallingPid();
+ final boolean restricted = isRestricted();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (!canUseFace(opPackageName, true /* foregroundOnly */,
+ callingUid, pid, callingUserId)) {
+ if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName);
+ return;
+ }
+
+ MetricsLogger.histogram(mContext, "faces_token", opId != 0L ? 1 : 0);
+
+ // Get performance stats object for this user.
+ HashMap<Integer, PerformanceStats> pmap
+ = (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap;
+ PerformanceStats stats = pmap.get(mCurrentUserId);
+ if (stats == null) {
+ stats = new PerformanceStats();
+ pmap.put(mCurrentUserId, stats);
+ }
+ mPerformanceStats = stats;
+
+ startAuthentication(token, opId, callingUserId, receiver,
+ flags, restricted, opPackageName);
+ }
+ });
+ }
+
+ @Override // Binder call
+ public void cancelAuthentication(final IBinder token, final String opPackageName) {
+ final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ final int callingUserId = UserHandle.getCallingUserId();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (!canUseFace(opPackageName, true /* foregroundOnly */, uid, pid,
+ callingUserId)) {
+ if (DEBUG) Slog.v(TAG, "cancelAuthentication(): reject " + opPackageName);
+ } else {
+ ClientMonitor client = mCurrentClient;
+ if (client instanceof AuthenticationClient) {
+ if (client.getToken() == token) {
+ if (DEBUG) Slog.v(TAG, "stop client " + client.getOwnerString());
+ client.stop(client.getToken() == token);
+ } else {
+ if (DEBUG) Slog.v(TAG, "can't stop client "
+ + client.getOwnerString() + " since tokens don't match");
+ }
+ } else if (client != null) {
+ if (DEBUG) Slog.v(TAG, "can't cancel non-authenticating client "
+ + client.getOwnerString());
+ }
+ }
+ }
+ });
+ }
+
+ @Override // Binder call
+ public void setActiveUser(final int userId) {
+ checkPermission(MANAGE_FACE);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ updateActiveGroup(userId, null);
+ }
+ });
+ }
+
+ @Override // Binder call
+ public void remove(final IBinder token, final int userId,
+ final IFaceServiceReceiver receiver) {
+ checkPermission(MANAGE_FACE); // TODO: Maybe have another permission
+ final boolean restricted = isRestricted();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ startRemove(token, userId, receiver, restricted, false /* internal */);
+ }
+ });
+ }
+
+ @Override // Binder call
+ public boolean isHardwareDetected(long deviceId, String opPackageName) {
+ if (!canUseFace(opPackageName, false /* foregroundOnly */,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ UserHandle.getCallingUserId())) {
+ return false;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ IBiometricsFace daemon = getFaceDaemon();
+ return daemon != null && mHalDeviceId != 0;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public Face getEnrolledFace(int userId, String opPackageName) {
+ if (!canUseFace(opPackageName, false /* foregroundOnly */,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ UserHandle.getCallingUserId())) {
+ return null;
+ }
+
+ return FaceService.this.getEnrolledFace(userId);
+ }
+
+ @Override // Binder call
+ public boolean hasEnrolledFace(int userId, String opPackageName) {
+ if (!canUseFace(opPackageName, false /* foregroundOnly */,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ UserHandle.getCallingUserId())) {
+ return false;
+ }
+
+ return FaceService.this.hasEnrolledFace(userId);
+ }
+
+ @Override // Binder call
+ public long getAuthenticatorId(String opPackageName) {
+ // In this method, we're not checking whether the caller is permitted to use face
+ // API because current authenticator ID is leaked (in a more contrived way) via Android
+ // Keystore (android.security.keystore package): the user of that API can create a key
+ // which requires face authentication for its use, and then query the key's
+ // characteristics (hidden API) which returns, among other things, face
+ // authenticator ID which was active at key creation time.
+ //
+ // Reason: The part of Android Keystore which runs inside an app's process invokes this
+ // method in certain cases. Those cases are not always where the developer demonstrates
+ // explicit intent to use face functionality. Thus, to avoiding throwing an
+ // unexpected SecurityException this method does not check whether its caller is
+ // permitted to use face API.
+ //
+ // The permission check should be restored once Android Keystore no longer invokes this
+ // method from inside app processes.
+
+ return FaceService.this.getAuthenticatorId(opPackageName);
+ }
+
+ @Override // Binder call
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (args.length > 0 && "--proto".equals(args[0])) {
+ dumpProto(fd);
+ } else {
+ dumpInternal(pw);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void resetTimeout(byte [] token) {
+ checkPermission(RESET_FACE_LOCKOUT);
+ // TODO: confirm security token when we move timeout management into the HAL layer.
+ mHandler.post(mResetFailedAttemptsRunnable);
+ }
+
+ @Override
+ public void addLockoutResetCallback(final IFaceServiceLockoutResetCallback callback)
+ throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ addLockoutResetMonitor(
+ new FaceServiceLockoutResetMonitor(callback));
+ }
+ });
+ }
+ }
+
+ private void dumpInternal(PrintWriter pw) {
+ JSONObject dump = new JSONObject();
+ try {
+ dump.put("service", "Face Manager");
+
+ JSONArray sets = new JSONArray();
+ for (UserInfo user : UserManager.get(getContext()).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+ PerformanceStats stats = mPerformanceMap.get(userId);
+ PerformanceStats cryptoStats = mCryptoPerformanceMap.get(userId);
+ JSONObject set = new JSONObject();
+ set.put("id", userId);
+ set.put("accept", (stats != null) ? stats.accept : 0);
+ set.put("reject", (stats != null) ? stats.reject : 0);
+ set.put("acquire", (stats != null) ? stats.acquire : 0);
+ set.put("lockout", (stats != null) ? stats.lockout : 0);
+ set.put("permanentLockout", (stats != null) ? stats.permanentLockout : 0);
+ // cryptoStats measures statistics about secure face transactions
+ // (e.g. to unlock password storage, make secure purchases, etc.)
+ set.put("acceptCrypto", (cryptoStats != null) ? cryptoStats.accept : 0);
+ set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
+ set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
+ set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
+ sets.put(set);
+ }
+
+ dump.put("prints", sets);
+ } catch (JSONException e) {
+ Slog.e(TAG, "dump formatting failure", e);
+ }
+ pw.println(dump);
+ }
+
+ private void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ for (UserInfo user : UserManager.get(getContext()).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+
+ final long userToken = proto.start(FaceServiceDumpProto.USERS);
+
+ proto.write(FaceUserStatsProto.USER_ID, userId);
+
+ // Normal face authentications (e.g. lockscreen)
+ final PerformanceStats normal = mPerformanceMap.get(userId);
+ if (normal != null) {
+ final long countsToken = proto.start(FaceUserStatsProto.NORMAL);
+ proto.write(FaceActionStatsProto.ACCEPT, normal.accept);
+ proto.write(FaceActionStatsProto.REJECT, normal.reject);
+ proto.write(FaceActionStatsProto.ACQUIRE, normal.acquire);
+ proto.write(FaceActionStatsProto.LOCKOUT, normal.lockout);
+ proto.write(FaceActionStatsProto.LOCKOUT_PERMANENT, normal.lockout);
+ proto.end(countsToken);
+ }
+
+ // Statistics about secure face transactions (e.g. to unlock password
+ // storage, make secure purchases, etc.)
+ final PerformanceStats crypto = mCryptoPerformanceMap.get(userId);
+ if (crypto != null) {
+ final long countsToken = proto.start(FaceUserStatsProto.CRYPTO);
+ proto.write(FaceActionStatsProto.ACCEPT, crypto.accept);
+ proto.write(FaceActionStatsProto.REJECT, crypto.reject);
+ proto.write(FaceActionStatsProto.ACQUIRE, crypto.acquire);
+ proto.write(FaceActionStatsProto.LOCKOUT, crypto.lockout);
+ proto.write(FaceActionStatsProto.LOCKOUT_PERMANENT, crypto.lockout);
+ proto.end(countsToken);
+ }
+
+ proto.end(userToken);
+ }
+ proto.flush();
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());
+ SystemServerInitThreadPool.get().submit(this::getFaceDaemon, TAG + ".onStart");
+ listenForUserSwitches();
+ }
+
+ private void updateActiveGroup(int userId, String clientPackage) {
+ IBiometricsFace daemon = getFaceDaemon();
+
+ if (daemon != null) {
+ try {
+ userId = getUserOrWorkProfileId(clientPackage, userId);
+ if (userId != mCurrentUserId) {
+ final File systemDir = Environment.getUserSystemDirectory(userId);
+ final File faceDir = new File(systemDir, FACE_DATA_DIR);
+ if (!faceDir.exists()) {
+ if (!faceDir.mkdir()) {
+ Slog.v(TAG, "Cannot make directory: " + faceDir.getAbsolutePath());
+ return;
+ }
+ // Calling mkdir() from this process will create a directory with our
+ // permissions (inherited from the containing dir). This command fixes
+ // the label.
+ if (!SELinux.restorecon(faceDir)) {
+ Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
+ return;
+ }
+ }
+ daemon.setActiveUser(userId, faceDir.getAbsolutePath());
+ mCurrentUserId = userId;
+ }
+ mAuthenticatorIds.put(userId,
+ hasEnrolledFace(userId) ? daemon.getAuthenticatorId().value : 0L);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to setActiveUser():", e);
+ }
+ }
+ }
+
+ /**
+ * @param clientPackage the package of the caller
+ * @return the profile id
+ */
+ private int getUserOrWorkProfileId(String clientPackage, int userId) {
+ if (!isKeyguard(clientPackage) && isWorkProfile(userId)) {
+ return userId;
+ }
+ return getEffectiveUserId(userId);
+ }
+
+ /**
+ * @param userId
+ * @return true if this is a work profile
+ */
+ private boolean isWorkProfile(int userId) {
+ UserInfo userInfo = null;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ userInfo = mUserManager.getUserInfo(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return userInfo != null && userInfo.isManagedProfile();
+ }
+
+ private void listenForUserSwitches() {
+ try {
+ ActivityManager.getService().registerUserSwitchObserver(
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) throws RemoteException {
+ mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
+ .sendToTarget();
+ }
+ }, TAG);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to listen for user switching event" ,e);
+ }
+ }
+
+ /***
+ * @param opPackageName the name of the calling package
+ * @return authenticator id for the calling user
+ */
+ public long getAuthenticatorId(String opPackageName) {
+ final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
+ return mAuthenticatorIds.getOrDefault(userId, 0L);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 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.face;
+
+import android.content.Context;
+import android.hardware.face.Face;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+
+import libcore.io.IoUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Class managing the set of faces per user across device reboots.
+ */
+class FaceUserState {
+
+ private static final String TAG = "FaceState";
+ private static final String FACE_FILE = "settings_face.xml";
+
+ private static final String TAG_FACE = "face";
+ private static final String ATTR_DEVICE_ID = "deviceId";
+
+ private final File mFile;
+
+ @GuardedBy("this")
+ private Face mFace = null;
+ private final Context mCtx;
+
+ public FaceUserState(Context ctx, int userId) {
+ mFile = getFileForUser(userId);
+ mCtx = ctx;
+ synchronized (this) {
+ readStateSyncLocked();
+ }
+ }
+
+ public void addFace(int faceId) {
+ synchronized (this) {
+ mFace = new Face("Face", faceId, 0);
+ scheduleWriteStateLocked();
+ }
+ }
+
+ public void removeFace() {
+ synchronized (this) {
+ mFace = null;
+ scheduleWriteStateLocked();
+ }
+ }
+
+ public Face getFace() {
+ synchronized (this) {
+ return getCopy(mFace);
+ }
+ }
+
+ private static File getFileForUser(int userId) {
+ return new File(Environment.getUserSystemDirectory(userId), FACE_FILE);
+ }
+
+ private final Runnable mWriteStateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ doWriteState();
+ }
+ };
+
+ private void scheduleWriteStateLocked() {
+ AsyncTask.execute(mWriteStateRunnable);
+ }
+
+ private Face getCopy(Face f) {
+ if (f == null) {
+ return null;
+ }
+ return new Face(f.getName(), f.getFaceId(), f.getDeviceId());
+ }
+
+ private void doWriteState() {
+ AtomicFile destination = new AtomicFile(mFile);
+
+ Face face;
+
+ synchronized (this) {
+ face = getCopy(mFace);
+ }
+
+ FileOutputStream out = null;
+ try {
+ out = destination.startWrite();
+
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_FACE);
+ if (face != null) {
+ serializer.attribute(null, ATTR_DEVICE_ID, Long.toString(face.getDeviceId()));
+ }
+ serializer.endTag(null, TAG_FACE);
+ serializer.endDocument();
+ destination.finishWrite(out);
+
+ // Any error while writing is fatal.
+ } catch (Throwable t) {
+ Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
+ destination.failWrite(out);
+ throw new IllegalStateException("Failed to write face", t);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private void readStateSyncLocked() {
+ FileInputStream in;
+ if (!mFile.exists()) {
+ return;
+ }
+ try {
+ in = new FileInputStream(mFile);
+ } catch (FileNotFoundException fnfe) {
+ Slog.i(TAG, "No face state");
+ return;
+ }
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseStateLocked(parser);
+
+ } catch (XmlPullParserException | IOException e) {
+ throw new IllegalStateException("Failed parsing settings file: "
+ + mFile , e);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ private void parseStateLocked(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_FACE)) {
+ parseFaceLocked(parser);
+ }
+ }
+ }
+
+ private void parseFaceLocked(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String deviceId = parser.getAttributeValue(null, ATTR_DEVICE_ID);
+
+ mFace = new Face("", 0, Integer.parseInt(deviceId));
+ }
+
+}
--- /dev/null
+/**
+ * Copyright (C) 2018 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.face;
+
+import android.content.Context;
+import android.hardware.face.Face;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Utility class for dealing with faces and face settings.
+ */
+public class FaceUtils {
+
+ private static final Object sInstanceLock = new Object();
+ private static FaceUtils sInstance;
+
+ @GuardedBy("this")
+ private final SparseArray<FaceUserState> mUsers = new SparseArray<>();
+
+ public static FaceUtils getInstance() {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new FaceUtils();
+ }
+ }
+ return sInstance;
+ }
+
+ private FaceUtils() {
+ }
+
+ public Face getFaceForUser(Context ctx, int userId) {
+ return getStateForUser(ctx, userId).getFace();
+ }
+
+ public void addFaceForUser(Context ctx, int faceId, int userId) {
+ getStateForUser(ctx, userId).addFace(faceId);
+ }
+
+ public void removeFaceForUser(Context ctx, int userId) {
+ getStateForUser(ctx, userId).removeFace();
+ }
+
+ private FaceUserState getStateForUser(Context ctx, int userId) {
+ synchronized (this) {
+ FaceUserState state = mUsers.get(userId);
+ if (state == null) {
+ state = new FaceUserState(ctx, userId);
+ mUsers.put(userId, state);
+ }
+ return state;
+ }
+ }
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2018 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.face;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.hardware.face.IFaceServiceReceiver;
+import com.android.server.face.RemovalClient;
+
+public abstract class InternalRemovalClient extends RemovalClient {
+
+ public InternalRemovalClient(Context context, long halDeviceId, IBinder token,
+ IFaceServiceReceiver receiver, int userId, boolean restricted, String owner) {
+
+ super(context, halDeviceId, token, receiver, userId, restricted, owner);
+
+ }
+}
--- /dev/null
+/**
+ * Copyright (C) 2018 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.face;
+
+import android.content.Context;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceManager;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
+
+/**
+ * A class to keep track of the remove state for a given client.
+ */
+public abstract class RemovalClient extends ClientMonitor {
+
+ public RemovalClient(Context context, long halDeviceId, IBinder token,
+ IFaceServiceReceiver receiver, int userId,
+ boolean restricted, String owner) {
+ super(context, halDeviceId, token, receiver, userId, restricted, owner);
+ }
+
+ @Override
+ public int start() {
+ IBiometricsFace daemon = getFaceDaemon();
+ // The face template ids will be removed when we get confirmation from the HAL
+ try {
+ final int result = daemon.remove(getTargetUserId());
+ if (result != 0) {
+ Slog.w(TAG, "startRemove failed, result=" + result);
+ MetricsLogger.histogram(getContext(), "faced_remove_start_error", result);
+ onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ return result;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "startRemove failed", e);
+ }
+ return 0;
+ }
+
+ @Override
+ public int stop(boolean initiatedByClient) {
+ if (mAlreadyCancelled) {
+ Slog.w(TAG, "stopRemove: already cancelled!");
+ return 0;
+ }
+ IBiometricsFace daemon = getFaceDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "stopRemoval: no face HAL!");
+ return ERROR_ESRCH;
+ }
+ try {
+ final int result = daemon.cancel();
+ if (result != 0) {
+ Slog.w(TAG, "stopRemoval failed, result=" + result);
+ return result;
+ }
+ if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer removing");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "stopRemoval failed", e);
+ return ERROR_ESRCH;
+ }
+ mAlreadyCancelled = true;
+ return 0; // success
+ }
+
+ /*
+ * @return true if we're done.
+ */
+ private boolean sendRemoved(int faceId, int remaining) {
+ IFaceServiceReceiver receiver = getReceiver();
+ try {
+ if (receiver != null) {
+ receiver.onRemoved(getHalDeviceId(), faceId, remaining);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify Removed:", e);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onRemoved(int faceId, int remaining) {
+ FaceUtils.getInstance().removeFaceForUser(getContext(), getTargetUserId());
+ return sendRemoved(faceId, remaining);
+ }
+
+ @Override
+ public boolean onEnrollResult(int faceId, int remaining) {
+ if (DEBUG) Slog.w(TAG, "onEnrollResult() called for remove!");
+ return true; // Invalid for Remove
+ }
+
+ @Override
+ public boolean onAuthenticated(int faceId) {
+ if (DEBUG) Slog.w(TAG, "onAuthenticated() called for remove!");
+ return true; // Invalid for Remove.
+ }
+}
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
+import android.hardware.biometrics.BiometricSourceType;
import android.app.trust.ITrustListener;
import android.app.trust.ITrustManager;
import android.content.BroadcastReceiver;
private final SparseBooleanArray mTrustUsuallyManagedForUser = new SparseBooleanArray();
// set to true only if user can skip bouncer
- @GuardedBy("mUsersUnlockedByFingerprint")
- private final SparseBooleanArray mUsersUnlockedByFingerprint = new SparseBooleanArray();
+ @GuardedBy("mUsersUnlockedByBiometric")
+ private final SparseBooleanArray mUsersUnlockedByBiometric = new SparseBooleanArray();
private final StrongAuthTracker mStrongAuthTracker;
boolean secure = mLockPatternUtils.isSecure(id);
boolean trusted = aggregateIsTrusted(id);
boolean showingKeyguard = true;
- boolean fingerprintAuthenticated = false;
+ boolean biometricAuthenticated = false;
if (mCurrentUser == id) {
- synchronized(mUsersUnlockedByFingerprint) {
- fingerprintAuthenticated = mUsersUnlockedByFingerprint.get(id, false);
+ synchronized(mUsersUnlockedByBiometric) {
+ biometricAuthenticated = mUsersUnlockedByBiometric.get(id, false);
}
try {
showingKeyguard = wm.isKeyguardLocked();
}
}
boolean deviceLocked = secure && showingKeyguard && !trusted &&
- !fingerprintAuthenticated;
+ !biometricAuthenticated;
setDeviceLockedForUser(id, deviceLocked);
}
}
}
@Override
- public void unlockedByFingerprintForUser(int userId) {
+ public void unlockedByBiometricForUser(int userId, BiometricSourceType biometricSource) {
enforceReportPermission();
- synchronized(mUsersUnlockedByFingerprint) {
- mUsersUnlockedByFingerprint.put(userId, true);
+ synchronized(mUsersUnlockedByBiometric) {
+ mUsersUnlockedByBiometric.put(userId, true);
}
mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, userId,
0 /* arg2 */).sendToTarget();
}
@Override
- public void clearAllFingerprints() {
+ public void clearAllBiometricRecognized(BiometricSourceType biometricSource) {
enforceReportPermission();
- synchronized(mUsersUnlockedByFingerprint) {
- mUsersUnlockedByFingerprint.clear();
+ synchronized(mUsersUnlockedByBiometric) {
+ mUsersUnlockedByBiometric.clear();
}
mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, UserHandle.USER_ALL,
0 /* arg2 */).sendToTarget();
synchronized (mTrustUsuallyManagedForUser) {
mTrustUsuallyManagedForUser.delete(userId);
}
- synchronized (mUsersUnlockedByFingerprint) {
- mUsersUnlockedByFingerprint.delete(userId);
+ synchronized (mUsersUnlockedByBiometric) {
+ mUsersUnlockedByBiometric.delete(userId);
}
refreshAgentList(userId);
refreshDeviceLockedForUser(userId);
}
@Override
- public void reportFailedFingerprintAttempt(int userHandle) {
+ public void reportFailedBiometricAttempt(int userHandle) {
enforceFullCrossUsersPermission(userHandle);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
}
@Override
- public void reportSuccessfulFingerprintAttempt(int userHandle) {
+ public void reportSuccessfulBiometricAttempt(int userHandle) {
enforceFullCrossUsersPermission(userHandle);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.face.FaceService;
import com.android.server.biometrics.fingerprint.FingerprintService;
import com.android.server.hdmi.HdmiControlService;
import com.android.server.input.InputManagerService;
}
traceEnd();
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+ traceBeginAndSlog("StartFaceSensor");
+ mSystemServiceManager.startService(FaceService.class);
+ traceEnd();
+ }
+
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
traceBeginAndSlog("StartFingerprintSensor");
mSystemServiceManager.startService(FingerprintService.class);