Merge "Give platform permissions a dummy group"
diff --git a/Android.bp b/Android.bp
index 25e738c..41d5f28 100644
--- a/Android.bp
+++ b/Android.bp
@@ -158,9 +158,9 @@
"core/java/android/hardware/IConsumerIrService.aidl",
"core/java/android/hardware/ISerialManager.aidl",
"core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl",
- "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl",
"core/java/android/hardware/biometrics/IBiometricService.aidl",
"core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl",
+ "core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl",
"core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl",
"core/java/android/hardware/display/IDisplayManager.aidl",
"core/java/android/hardware/display/IDisplayManagerCallback.aidl",
diff --git a/api/current.txt b/api/current.txt
index 0dce8d4..855ac8c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11339,7 +11339,7 @@
method public abstract int checkSignatures(java.lang.String, java.lang.String);
method public abstract int checkSignatures(int, int);
method public abstract void clearInstantAppCookie();
- method public abstract void clearPackagePreferredActivities(java.lang.String);
+ method public abstract deprecated void clearPackagePreferredActivities(java.lang.String);
method public abstract java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]);
method public abstract void extendVerificationTimeout(int, int, long);
method public abstract android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -11383,8 +11383,8 @@
method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int);
method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public abstract int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String);
- method public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
+ method public abstract deprecated int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String);
+ method public abstract deprecated java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
method public abstract android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -49074,6 +49074,7 @@
method public float getTranslationX();
method public float getTranslationY();
method public float getTranslationZ();
+ method public long getUniqueDrawingId();
method public int getVerticalFadingEdgeLength();
method public int getVerticalScrollbarPosition();
method public int getVerticalScrollbarWidth();
diff --git a/api/system-current.txt b/api/system-current.txt
index 41b5bc7..7661d49c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1244,7 +1244,7 @@
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle);
method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
- method public void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName);
+ method public deprecated void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName);
method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public void sendDeviceCustomizationReadyBroadcast();
method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index b871c78..74b974e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -943,6 +943,10 @@
package android.os.storage {
+ public class StorageManager {
+ method public static boolean hasIsolatedStorage();
+ }
+
public final class StorageVolume implements android.os.Parcelable {
method public java.lang.String getPath();
}
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 25bd033..3c31667 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -2695,7 +2695,6 @@
Lcom/android/internal/telephony/dataconnection/DcTracker;->onSetUserDataEnabled(Z)V
Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Lcom/android/internal/telephony/dataconnection/ApnContext;)Z
Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Ljava/lang/String;)Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->registerForAllDataDisconnected(Landroid/os/Handler;ILjava/lang/Object;)V
Lcom/android/internal/telephony/dataconnection/DcTracker;->registerSettingsObserver()V
Lcom/android/internal/telephony/dataconnection/DcTracker;->resetPollStats()V
Lcom/android/internal/telephony/dataconnection/DcTracker;->restartDataStallAlarm()V
@@ -2709,7 +2708,6 @@
Lcom/android/internal/telephony/dataconnection/DcTracker;->stopDataStallAlarm()V
Lcom/android/internal/telephony/dataconnection/DcTracker;->stopNetStatPoll()V
Lcom/android/internal/telephony/dataconnection/DcTracker;->unregisterForAllDataDisconnected(Landroid/os/Handler;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->updateRecords()V
Lcom/android/internal/telephony/DctConstants$Activity;->DATAIN:Lcom/android/internal/telephony/DctConstants$Activity;
Lcom/android/internal/telephony/DctConstants$Activity;->DATAINANDOUT:Lcom/android/internal/telephony/DctConstants$Activity;
Lcom/android/internal/telephony/DctConstants$Activity;->DATAOUT:Lcom/android/internal/telephony/DctConstants$Activity;
@@ -3201,7 +3199,6 @@
Lcom/android/internal/telephony/Phone;->isWifiCallingEnabled()Z
Lcom/android/internal/telephony/Phone;->mCi:Lcom/android/internal/telephony/CommandsInterface;
Lcom/android/internal/telephony/Phone;->mContext:Landroid/content/Context;
-Lcom/android/internal/telephony/Phone;->mDcTracker:Lcom/android/internal/telephony/dataconnection/DcTracker;
Lcom/android/internal/telephony/Phone;->mIccRecords:Ljava/util/concurrent/atomic/AtomicReference;
Lcom/android/internal/telephony/Phone;->mImsPhone:Lcom/android/internal/telephony/Phone;
Lcom/android/internal/telephony/Phone;->mMmiRegistrants:Landroid/os/RegistrantList;
@@ -3294,7 +3291,6 @@
Lcom/android/internal/telephony/ProxyController;->mRadioCapabilitySessionId:I
Lcom/android/internal/telephony/ProxyController;->mSetRadioAccessFamilyStatus:[I
Lcom/android/internal/telephony/ProxyController;->mUniqueIdGenerator:Ljava/util/concurrent/atomic/AtomicInteger;
-Lcom/android/internal/telephony/ProxyController;->registerForAllDataDisconnected(ILandroid/os/Handler;ILjava/lang/Object;)V
Lcom/android/internal/telephony/ProxyController;->sendRadioCapabilityRequest(IIIILjava/lang/String;II)V
Lcom/android/internal/telephony/ProxyController;->sProxyController:Lcom/android/internal/telephony/ProxyController;
Lcom/android/internal/telephony/RadioCapability;->getRadioAccessFamily()I
@@ -3394,7 +3390,6 @@
Lcom/android/internal/telephony/ServiceStateTracker;->notifyDataRegStateRilRadioTechnologyChanged()V
Lcom/android/internal/telephony/ServiceStateTracker;->notifySignalStrength()Z
Lcom/android/internal/telephony/ServiceStateTracker;->pollState()V
-Lcom/android/internal/telephony/ServiceStateTracker;->powerOffRadioSafely(Lcom/android/internal/telephony/dataconnection/DcTracker;)V
Lcom/android/internal/telephony/ServiceStateTracker;->reRegisterNetwork(Landroid/os/Message;)V
Lcom/android/internal/telephony/ServiceStateTracker;->resetServiceStateInIwlanMode()V
Lcom/android/internal/telephony/ServiceStateTracker;->setOperatorIdd(Ljava/lang/String;)V
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 437039d..7d5202d 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -97,8 +97,7 @@
*
* @hide
*/
- public static final boolean DEPRECATE_DATA_COLUMNS = SystemProperties
- .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false);
+ public static final boolean DEPRECATE_DATA_COLUMNS = StorageManager.hasIsolatedStorage();
/**
* Special filesystem path prefix which indicates that a path should be
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6421dc5..b7df2bf 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5372,6 +5372,10 @@
public abstract void removePackageFromPreferred(String packageName);
/**
+ * @deprecated This function no longer does anything; it was an old
+ * approach to managing preferred activities, which has been superseded
+ * by (and conflicts with) the modern activity-based preferences.
+ *
* Retrieve the list of all currently configured preferred packages. The
* first package on the list is the most preferred, the last is the least
* preferred.
@@ -5380,6 +5384,7 @@
* @return A List of PackageInfo objects, one for each preferred
* application, in order of preference.
*/
+ @Deprecated
public abstract List<PackageInfo> getPreferredPackages(@PackageInfoFlags int flags);
/**
@@ -5406,11 +5411,16 @@
ComponentName[] set, ComponentName activity);
/**
+ * @deprecated This is a protected API that should not have been available
+ * to third party applications. It is the platform's responsibility for
+ * assigning preferred activities and this cannot be directly modified.
+ *
* Same as {@link #addPreferredActivity(IntentFilter, int,
ComponentName[], ComponentName)}, but with a specific userId to apply the preference
to.
* @hide
*/
+ @Deprecated
@UnsupportedAppUsage
public void addPreferredActivityAsUser(IntentFilter filter, int match,
ComponentName[] set, ComponentName activity, @UserIdInt int userId) {
@@ -5444,6 +5454,10 @@
ComponentName[] set, ComponentName activity);
/**
+ * @deprecated This is a protected API that should not have been available
+ * to third party applications. It is the platform's responsibility for
+ * assigning preferred activities and this cannot be directly modified.
+ *
* Replaces an existing preferred activity mapping to the system, and if that were not present
* adds a new preferred activity. This will be used to automatically select the given activity
* component when {@link Context#startActivity(Intent) Context.startActivity()} finds multiple
@@ -5459,6 +5473,7 @@
*
* @hide
*/
+ @Deprecated
@SystemApi
public void replacePreferredActivity(@NonNull IntentFilter filter, int match,
@NonNull List<ComponentName> set, @NonNull ComponentName activity) {
@@ -5476,6 +5491,10 @@
}
/**
+ * @deprecated This function no longer does anything; it was an old
+ * approach to managing preferred activities, which has been superseded
+ * by (and conflicts with) the modern activity-based preferences.
+ *
* Remove all preferred activity mappings, previously added with
* {@link #addPreferredActivity}, from the
* system whose activities are implemented in the given package name.
@@ -5484,9 +5503,14 @@
* @param packageName The name of the package whose preferred activity
* mappings are to be removed.
*/
+ @Deprecated
public abstract void clearPackagePreferredActivities(String packageName);
/**
+ * @deprecated This function no longer does anything; it was an old
+ * approach to managing preferred activities, which has been superseded
+ * by (and conflicts with) the modern activity-based preferences.
+ *
* Retrieve all preferred activities, previously added with
* {@link #addPreferredActivity}, that are
* currently registered with the system.
@@ -5503,6 +5527,7 @@
* (the number of distinct IntentFilter records, not the number of unique
* activity components) that were found.
*/
+ @Deprecated
public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters,
@NonNull List<ComponentName> outActivities, String packageName);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 49189e5..ac18dca 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2513,7 +2513,7 @@
// If the storage model feature flag is disabled, we need to fiddle
// around with permission definitions to return us to pre-Q behavior.
// STOPSHIP(b/112545973): remove once feature enabled by default
- if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+ if (!StorageManager.hasIsolatedStorage()) {
if ("android".equals(pkg.packageName)) {
final ArraySet<String> newGroups = new ArraySet<>();
newGroups.add(android.Manifest.permission_group.MEDIA_AURAL);
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index 79e15a7a..0ec812f 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -30,19 +30,29 @@
public interface BiometricAuthenticator {
/**
+ * No biometric methods or nothing has been enrolled.
+ * Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist"
+ * modalities when calling authenticate().
* @hide
*/
- int TYPE_FINGERPRINT = 1;
+ int TYPE_NONE = 0;
+ /**
+ * Constant representing fingerprint.
+ * @hide
+ */
+ int TYPE_FINGERPRINT = 1 << 0;
/**
+ * Constant representing iris.
* @hide
*/
- int TYPE_IRIS = 2;
+ int TYPE_IRIS = 1 << 1;
/**
+ * Constant representing face.
* @hide
*/
- int TYPE_FACE = 3;
+ int TYPE_FACE = 1 << 2;
/**
* Container for biometric data
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index bd149fd..b238d77 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -251,9 +251,40 @@
private Executor mExecutor;
private AuthenticationCallback mAuthenticationCallback;
- IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
+ private final IBiometricServiceReceiver mBiometricServiceReceiver =
+ new IBiometricServiceReceiver.Stub() {
+
@Override
- public void onDialogDismissed(int reason) {
+ public void onAuthenticationSucceeded() throws RemoteException {
+ mExecutor.execute(() -> {
+ final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
+ mAuthenticationCallback.onAuthenticationSucceeded(result);
+ });
+ }
+
+ @Override
+ public void onAuthenticationFailed() throws RemoteException {
+ mExecutor.execute(() -> {
+ mAuthenticationCallback.onAuthenticationFailed();
+ });
+ }
+
+ @Override
+ public void onError(int error, String message) throws RemoteException {
+ mExecutor.execute(() -> {
+ mAuthenticationCallback.onAuthenticationError(error, message);
+ });
+ }
+
+ @Override
+ public void onAcquired(int acquireInfo, String message) throws RemoteException {
+ mExecutor.execute(() -> {
+ mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
+ });
+ }
+
+ @Override
+ public void onDialogDismissed(int reason) throws RemoteException {
// Check the reason and invoke OnClickListener(s) if necessary
if (reason == DISMISSED_REASON_POSITIVE) {
mPositiveButtonInfo.executor.execute(() -> {
@@ -267,40 +298,6 @@
}
};
- IBiometricServiceReceiver mBiometricServiceReceiver =
- new IBiometricServiceReceiver.Stub() {
-
- @Override
- public void onAuthenticationSucceeded(long deviceId) throws RemoteException {
- mExecutor.execute(() -> {
- final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
- mAuthenticationCallback.onAuthenticationSucceeded(result);
- });
- }
-
- @Override
- public void onAuthenticationFailed(long deviceId) throws RemoteException {
- mExecutor.execute(() -> {
- mAuthenticationCallback.onAuthenticationFailed();
- });
- }
-
- @Override
- public void onError(long deviceId, int error, String message)
- throws RemoteException {
- mExecutor.execute(() -> {
- mAuthenticationCallback.onAuthenticationError(error, message);
- });
- }
-
- @Override
- public void onAcquired(long deviceId, int acquireInfo, String message) {
- mExecutor.execute(() -> {
- mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
- });
- }
- };
-
private BiometricPrompt(Context context, Bundle bundle,
ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) {
mContext = context;
@@ -557,9 +554,8 @@
mExecutor = executor;
mAuthenticationCallback = callback;
final long sessionId = crypto != null ? crypto.getOpId() : 0;
- mService.authenticate(mToken, sessionId, userId,
- mBiometricServiceReceiver, 0 /* flags */, mContext.getOpPackageName(),
- mBundle, mDialogReceiver);
+ mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
+ mContext.getOpPackageName(), mBundle);
} catch (RemoteException e) {
Log.e(TAG, "Remote exception while authenticating", e);
mExecutor.execute(() -> {
diff --git a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl
deleted file mode 100644
index 27d25b8..0000000
--- a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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;
-
-/**
- * Communication channel from the BiometricPrompt (SysUI) back to AuthenticationClient.
- * @hide
- */
-oneway interface IBiometricPromptReceiver {
- void onDialogDismissed(int reason);
-}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index e17feff..53a0761 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -18,7 +18,6 @@
import android.os.Bundle;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
-import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiver;
/**
@@ -32,8 +31,7 @@
// Requests authentication. The service choose the appropriate biometric to use, and show
// the corresponding BiometricDialog.
void authenticate(IBinder token, long sessionId, int userId,
- IBiometricServiceReceiver receiver, int flags, String opPackageName,
- in Bundle bundle, IBiometricPromptReceiver dialogReceiver);
+ IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle);
// Cancel authentication for the given sessionId
void cancelAuthentication(IBinder token, String opPackageName);
@@ -46,4 +44,8 @@
// Explicitly set the active user.
void setActiveUser(int userId);
+
+ // Notify BiometricService when <Biometric>Service is ready to start the prepared client.
+ // Client lifecycle is still managed in <Biometric>Service.
+ void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId);
}
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index a6e3696..22ef33e 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -16,12 +16,18 @@
package android.hardware.biometrics;
/**
- * Communication channel from the BiometricService back to BiometricPrompt.
+ * Communication channel from BiometricService back to BiometricPrompt
* @hide
*/
oneway interface IBiometricServiceReceiver {
- void onAuthenticationSucceeded(long deviceId);
- void onAuthenticationFailed(long deviceId);
- void onError(long deviceId, int error, String message);
- void onAcquired(long deviceId, int acquiredInfo, String message);
+ // Notify BiometricPrompt that authentication was successful
+ void onAuthenticationSucceeded();
+ // Noties that authentication failed.
+ void onAuthenticationFailed();
+ // Notify BiometricPrompt that an error has occurred.
+ void onError(int error, String message);
+ // Notifies that a biometric has been acquired.
+ void onAcquired(int acquiredInfo, String message);
+ // Notifies that the SystemUI dialog has been dismissed.
+ void onDialogDismissed(int reason);
}
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
new file mode 100644
index 0000000..180daaf
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Communication channel from
+ * 1) BiometricDialogImpl (SysUI) back to BiometricService
+ * 2) <Biometric>Service back to BiometricService
+ * Receives messages from the above and does some handling before forwarding to BiometricPrompt
+ * via IBiometricServiceReceiver.
+ * @hide
+ */
+oneway interface IBiometricServiceReceiverInternal {
+ // Notify BiometricService that authentication was successful. If user confirmation is required,
+ // the auth token must be submitted into KeyStore.
+ void onAuthenticationSucceeded(boolean requireConfirmation, in byte[] token);
+ // Notify BiometricService that an error has occurred.
+ void onAuthenticationFailed(int cookie, boolean requireConfirmation);
+ // Notify BiometricService than an error has occured. Forward to the correct receiver depending
+ // on the cookie.
+ void onError(int cookie, int error, String message);
+ // Notifies that a biometric has been acquired.
+ void onAcquired(int acquiredInfo, String message);
+ // Notifies that the SystemUI dialog has been dismissed.
+ void onDialogDismissed(int reason);
+ // Notifies that the user has pressed the "try again" button on SystemUI
+ void onTryAgainPressed();
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 47df8e8..a15dcec 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,9 +15,7 @@
*/
package android.hardware.face;
-import android.os.Bundle;
-import android.hardware.biometrics.IBiometricPromptReceiver;
-import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.face.Face;
@@ -32,19 +30,24 @@
void authenticate(IBinder token, long sessionId,
IFaceServiceReceiver receiver, int flags, String opPackageName);
- // This method invokes the BiometricDialog. The arguments are almost the same as above,
- // but should only be called from (BiometricPromptService).
- void authenticateFromService(boolean requireConfirmation, IBinder token, long sessionId,
- int userId, IBiometricServiceReceiver receiver, int flags, String opPackageName,
- in Bundle bundle, IBiometricPromptReceiver dialogReceiver,
- int callingUid, int callingPid, int callingUserId);
+ // This method prepares the service to start authenticating, but doesn't start authentication.
+ // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
+ // called from BiometricService. The additional uid, pid, userId arguments should be determined
+ // by BiometricService. To start authentication after the clients are ready, use
+ // startPreparedClient().
+ void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId,
+ int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
+ int cookie, int callingUid, int callingPid, int callingUserId);
+
+ // Starts authentication with the previously prepared client.
+ void startPreparedClient(int cookie);
// Cancel authentication for the given sessionId
void cancelAuthentication(IBinder token, String opPackageName);
// Same as above, with extra arguments.
void cancelAuthenticationFromService(IBinder token, String opPackageName,
- int callingUid, int callingPid, int callingUserId);
+ int callingUid, int callingPid, int callingUserId, boolean fromClient);
// Start face enrollment
void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 2662a11..dd6b29d 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -15,9 +15,7 @@
*/
package android.hardware.fingerprint;
-import android.os.Bundle;
-import android.hardware.biometrics.IBiometricPromptReceiver;
-import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -35,22 +33,25 @@
void authenticate(IBinder token, long sessionId, int userId,
IFingerprintServiceReceiver receiver, int flags, String opPackageName);
- // This method invokes the BiometricDialog. The arguments are almost the same as above, except
- // this is protected by the MANAGE_BIOMETRIC signature permission. This method should only be
- // called from BiometricPromptService. The additional uid, pid, userId arguments should be
- // determined by BiometricPromptService.
- void authenticateFromService(IBinder token, long sessionId, int userId,
- IBiometricServiceReceiver receiver, int flags, String opPackageName,
- in Bundle bundle, IBiometricPromptReceiver dialogReceiver,
+ // This method prepares the service to start authenticating, but doesn't start authentication.
+ // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
+ // called from BiometricService. The additional uid, pid, userId arguments should be determined
+ // by BiometricService. To start authentication after the clients are ready, use
+ // startPreparedClient().
+ void prepareForAuthentication(IBinder token, long sessionId, int userId,
+ IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie,
int callingUid, int callingPid, int callingUserId);
+ // Starts authentication with the previously prepared client.
+ void startPreparedClient(int cookie);
+
// Cancel authentication for the given sessionId
void cancelAuthentication(IBinder token, String opPackageName);
// Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes
// an additional uid, pid, userid.
void cancelAuthenticationFromService(IBinder token, String opPackageName,
- int callingUid, int callingPid, int callingUserId);
+ int callingUid, int callingPid, int callingUserId, boolean fromClient);
// Start fingerprint enrollment
void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver,
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index b42f1c4..8e11d85 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -25,6 +25,7 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
import android.app.Activity;
@@ -1533,6 +1534,12 @@
return SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false);
}
+ /** {@hide} */
+ @TestApi
+ public static boolean hasIsolatedStorage() {
+ return SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false);
+ }
+
/**
* @deprecated disabled now that FUSE has been replaced by sdcardfs
* @hide
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2767505..981a0df 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -23545,6 +23545,16 @@
}
/**
+ * Get the identifier used for this view by the drawing system.
+ *
+ * @see RenderNode#getUniqueId()
+ * @return A long that uniquely identifies this view's drawing component
+ */
+ public long getUniqueDrawingId() {
+ return mRenderNode.getUniqueId();
+ }
+
+ /**
* Returns this view's tag.
*
* @return the Object stored in this view as a tag, or {@code null} if not
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 604537f..600b1b3 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -18,7 +18,7 @@
import android.content.ComponentName;
import android.graphics.Rect;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
@@ -141,7 +141,7 @@
void showShutdownUi(boolean isReboot, String reason);
// Used to show the dialog when BiometricService starts authentication
- void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type,
+ void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
void onBiometricAuthenticated();
@@ -151,4 +151,6 @@
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
+ // Used to request the "try again" button for authentications which requireConfirmation=true
+ void showBiometricTryAgain();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index b7ffb57..bf82dc61 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -21,7 +21,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
@@ -92,7 +92,7 @@
void showPinningEscapeToast();
// Used to show the dialog when BiometricService starts authentication
- void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type,
+ void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
void onBiometricAuthenticated();
@@ -102,4 +102,6 @@
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
+ // Used to request the "try again" button for authentications which requireConfirmation=true
+ void showBiometricTryAgain();
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 8495850..b97a9fa 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -934,7 +934,7 @@
// If the storage model feature flag is disabled, we need to fiddle
// around with permission definitions to return us to pre-Q behavior.
// STOPSHIP(b/112545973): remove once feature enabled by default
- if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+ if (!StorageManager.hasIsolatedStorage()) {
if (newPermissions.contains(android.Manifest.permission.READ_MEDIA_AUDIO) ||
newPermissions.contains(android.Manifest.permission.READ_MEDIA_VIDEO) ||
newPermissions.contains(android.Manifest.permission.READ_MEDIA_IMAGES)) {
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index bd6d976..477f894 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1444,9 +1444,14 @@
<!-- Title shown when the system-provided biometric dialog is shown, asking the user to authenticate. [CHAR LIMIT=40] -->
<string name="biometric_dialog_default_title">Application <xliff:g id="app" example="Gmail">%s</xliff:g> wants to authenticate.</string>
-
<!-- Message shown when biometric hardware is not available [CHAR LIMIT=50] -->
<string name="biometric_error_hw_unavailable">Biometric hardware unavailable</string>
+ <!-- Message shown when biometric authentication was canceled by the user [CHAR LIMIT=50] -->
+ <string name="biometric_error_user_canceled">Authentication canceled</string>
+ <!-- Message shown by the biometric dialog when biometric is not recognized -->
+ <string name="biometric_not_recognized">Not recognized</string>
+ <!-- Message shown when biometric authentication has been canceled [CHAR LIMIT=50] -->
+ <string name="biometric_error_canceled">Authentication canceled</string>
<!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized -->
<string name="fingerprint_acquired_partial">Partial fingerprint detected. Please try again.</string>
@@ -1462,8 +1467,6 @@
<string-array name="fingerprint_acquired_vendor">
</string-array>
- <!-- Message shown by the biometric dialog when biometric is not recognized -->
- <string name="biometric_not_recognized">Not recognized</string>
<!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
<string name="fingerprint_authenticated">Fingerprint authenticated</string>
<!-- Accessibility message announced when a face has been authenticated [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e251e27..b815fea 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2406,7 +2406,9 @@
<!-- Biometric messages -->
<java-symbol type="string" name="biometric_dialog_default_title" />
<java-symbol type="string" name="biometric_error_hw_unavailable" />
+ <java-symbol type="string" name="biometric_error_user_canceled" />
<java-symbol type="string" name="biometric_not_recognized" />
+ <java-symbol type="string" name="biometric_error_canceled" />
<!-- Fingerprint messages -->
<java-symbol type="string" name="fingerprint_error_unable_to_process" />
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index b047f8d..111dd0f 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -29,6 +29,7 @@
import android.content.res.AssetFileDescriptor;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer2.DrmInfo;
import android.media.MediaPlayer2Proto.PlayerMessage;
import android.media.MediaPlayer2Proto.Value;
import android.net.Uri;
@@ -72,7 +73,11 @@
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -300,21 +305,7 @@
private volatile float mVolume = 1.0f;
private VideoSize mVideoSize = new VideoSize(0, 0);
- // TODO: create per-source drm fields in SourceInfo
- // Modular DRM
- private final Object mDrmLock = new Object();
- //--- guarded by |mDrmLock| start
- private UUID mDrmUUID;
- private DrmInfo mDrmInfo;
- private MediaDrm mDrmObj;
- private byte[] mDrmSessionId;
- private boolean mDrmInfoResolved;
- private boolean mActiveDrmScheme;
- private boolean mDrmConfigAllowed;
- private boolean mDrmProvisioningInProgress;
- private boolean mPrepareDrmInProgress;
- private ProvisioningThread mDrmProvisioningThread;
- //--- guarded by |mDrmLock| end
+ private ExecutorService mDrmThreadPool = Executors.newCachedThreadPool();
// Creating a dummy audio track, used for keeping session id alive
private final Object mSessionIdLock = new Object();
@@ -328,6 +319,7 @@
private final List<Task> mPendingTasks = new LinkedList<>();
@GuardedBy("mTaskLock")
private Task mCurrentTask;
+ private final AtomicLong mTaskIdGenerator = new AtomicLong(0);
@GuardedBy("mTaskLock")
boolean mIsPreviousCommandSeekTo = false;
@@ -413,15 +405,13 @@
mHandlerThread = null;
}
- setCurrentSourceInfo(null);
- clearNextSourceInfos();
+ clearSourceInfos();
// Modular DRM clean up
mOnDrmConfigHelper = null;
synchronized (mDrmEventCbLock) {
mDrmEventCallbackRecords.clear();
}
- resetDrmState();
native_release();
@@ -461,13 +451,8 @@
synchronized (mDrmEventCbLock) {
mDrmEventCallbackRecords.clear();
}
- setCurrentSourceInfo(null);
- clearNextSourceInfos();
- synchronized (mTaskLock) {
- mPendingTasks.clear();
- mIsPreviousCommandSeekTo = false;
- }
+ clearSourceInfos();
stayAwake(false);
native_reset();
@@ -481,7 +466,6 @@
mTaskHandler.removeCallbacksAndMessages(null);
}
- resetDrmState();
}
private native void native_reset();
@@ -706,13 +690,14 @@
}
synchronized (mSrcLock) {
- setCurrentSourceInfo(new SourceInfo(dsd));
+ setCurrentSourceInfo_l(new SourceInfo(dsd));
handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId);
}
} finally {
dsd.close();
}
}
+
});
}
@@ -732,7 +717,7 @@
void process() {
Media2Utils.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
synchronized (mSrcLock) {
- clearNextSourceInfos();
+ clearNextSourceInfos_l();
mNextSourceInfos.add(new SourceInfo(dsd));
}
prepareNextDataSource();
@@ -758,7 +743,7 @@
}
synchronized (mSrcLock) {
- clearNextSourceInfos();
+ clearNextSourceInfos_l();
for (DataSourceDesc dsd : dsds) {
if (dsd != null) {
mNextSourceInfos.add(new SourceInfo(dsd));
@@ -781,7 +766,9 @@
return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
@Override
void process() {
- clearNextSourceInfos();
+ synchronized (mSrcLock) {
+ clearNextSourceInfos_l();
+ }
}
});
}
@@ -1073,7 +1060,7 @@
SourceInfo nextSourceInfo = mNextSourceInfos.peek();
if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) {
// Switch to next source only when it has been prepared.
- setCurrentSourceInfo(mNextSourceInfos.poll());
+ setCurrentSourceInfo_l(mNextSourceInfos.poll());
long srcId = mCurrentSourceInfo.mId;
try {
@@ -2173,7 +2160,7 @@
final int what = msg.arg1;
final int extra = msg.arg2;
- final SourceInfo sourceInfo = getSourceInfoById(srcId);
+ final SourceInfo sourceInfo = getSourceInfo(srcId);
if (sourceInfo == null) {
return;
}
@@ -2227,11 +2214,11 @@
Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
} else if (msg.obj instanceof byte[]) {
// The PlayerMessage was parsed already in postEventFromNative
- final DrmInfo drmInfo;
- synchronized (mDrmLock) {
- if (mDrmInfo != null) {
- drmInfo = mDrmInfo.makeCopy();
+ final DrmInfo drmInfo;
+ synchronized (sourceInfo) {
+ if (sourceInfo.mDrmInfo != null) {
+ drmInfo = sourceInfo.mDrmInfo.makeCopy();
} else {
drmInfo = null;
}
@@ -2303,7 +2290,7 @@
}
});
- SourceInfo src = getSourceInfoById(srcId);
+ SourceInfo src = getSourceInfo(srcId);
if (src != null) {
src.mBufferedPercentage.set(percent);
}
@@ -2504,6 +2491,7 @@
return;
}
+ final SourceInfo sourceInfo = mp.getSourceInfo(srcId);
switch (what) {
case MEDIA_DRM_INFO:
// We need to derive mDrmInfo before prepare() returns so processing it here
@@ -2511,7 +2499,7 @@
// notification looper so its handleMessage might process the event after prepare()
// has returned.
Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
- if (obj != null) {
+ if (obj != null && sourceInfo != null) {
PlayerMessage playerMsg;
try {
playerMsg = PlayerMessage.parseFrom(obj);
@@ -2520,11 +2508,12 @@
break;
}
DrmInfo drmInfo = new DrmInfo(playerMsg);
- synchronized (mp.mDrmLock) {
- mp.mDrmInfo = drmInfo;
+ synchronized (sourceInfo) {
+ sourceInfo.mDrmInfo = drmInfo;
}
} else {
- Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
+ Log.w(TAG, "MEDIA_DRM_INFO sourceInfo " + sourceInfo
+ + " msg.obj of unexpected type " + obj);
}
break;
@@ -2533,8 +2522,10 @@
// mainly for prepare() use case. For prepare(), this still can run to a race
// condition b/c MediaPlayerNative releases the prepare() lock before calling notify
// so we also set mDrmInfoResolved in prepare().
- synchronized (mp.mDrmLock) {
- mp.mDrmInfoResolved = true;
+ if (sourceInfo != null) {
+ synchronized (sourceInfo) {
+ sourceInfo.mDrmInfoResolved = true;
+ }
}
break;
}
@@ -3211,9 +3202,7 @@
*/
// This is a synchronous call.
public void setOnDrmConfigHelper(OnDrmConfigHelper listener) {
- synchronized (mDrmLock) {
- mOnDrmConfigHelper = listener;
- }
+ mOnDrmConfigHelper = listener;
}
private OnDrmConfigHelper mOnDrmConfigHelper;
@@ -3358,24 +3347,27 @@
* @throws IllegalStateException if called before being prepared
*/
public DrmInfo getDrmInfo(@NonNull DataSourceDesc dsd) {
- // TODO: this implementation only works when dsd is the only data source
- DrmInfo drmInfo = null;
+ final SourceInfo sourceInfo = getSourceInfo(dsd);
+ if (sourceInfo != null) {
+ DrmInfo drmInfo = null;
- // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
- // regardless below returns drmInfo anyway instead of raising an exception
- synchronized (mDrmLock) {
- if (!mDrmInfoResolved && mDrmInfo == null) {
- final String msg = "The Player has not been prepared yet";
- Log.v(TAG, msg);
- throw new IllegalStateException(msg);
- }
+ // there is not much point if the app calls getDrmInfo within an OnDrmInfoListener;
+ // regardless below returns drmInfo anyway instead of raising an exception
+ synchronized (sourceInfo) {
+ if (!sourceInfo.mDrmInfoResolved && sourceInfo.mDrmInfo == null) {
+ final String msg = "The Player has not been prepared yet";
+ Log.v(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
- if (mDrmInfo != null) {
- drmInfo = mDrmInfo.makeCopy();
- }
- } // synchronized
+ if (sourceInfo.mDrmInfo != null) {
+ drmInfo = sourceInfo.mDrmInfo.makeCopy();
+ }
+ } // synchronized
- return drmInfo;
+ return drmInfo;
+ }
+ return null;
}
/**
@@ -3411,15 +3403,28 @@
*/
// This is an asynchronous call.
public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) {
- // TODO: this implementation only works when dsd is the only data source
return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) {
@Override
void process() {
- int status = PREPARE_DRM_STATUS_SUCCESS;
+ final SourceInfo sourceInfo = getSourceInfo(dsd);
+ int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
boolean sendEvent = true;
+ if (sourceInfo == null) {
+ Log.e(TAG, "prepareDrm(): DataSource not found.");
+ } else if (sourceInfo.mDrmInfo == null) {
+ // only allowing if tied to a protected source;
+ // might relax for releasing offline keys
+ Log.e(TAG, "prepareDrm(): Wrong usage: The player must be prepared and "
+ + "DRM info be retrieved before this call.");
+ } else {
+ status = PREPARE_DRM_STATUS_SUCCESS;
+ }
+
try {
- doPrepareDrm(dsd, uuid);
+ if (status == PREPARE_DRM_STATUS_SUCCESS) {
+ sourceInfo.mDrmHandle.prepare(uuid);
+ }
} catch (ResourceBusyException e) {
status = PREPARE_DRM_STATUS_RESOURCE_BUSY;
} catch (UnsupportedSchemeException e) {
@@ -3428,14 +3433,14 @@
Log.w(TAG, "prepareDrm: NotProvisionedException");
// handle provisioning internally; it'll reset mPrepareDrmInProgress
- status = handleProvisioninig(dsd, uuid);
+ status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId);
if (status == PREPARE_DRM_STATUS_SUCCESS) {
// DrmEventCallback will be fired in provisioning
sendEvent = false;
} else {
- synchronized (mDrmLock) {
- cleanDrmObj();
+ synchronized (sourceInfo.mDrmHandle) {
+ sourceInfo.mDrmHandle.cleanDrmObj();
}
switch (status) {
@@ -3478,95 +3483,6 @@
});
}
- private void doPrepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid)
- throws UnsupportedSchemeException, ResourceBusyException,
- NotProvisionedException {
- Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
-
- synchronized (mDrmLock) {
- // only allowing if tied to a protected source; might relax for releasing offline keys
- if (mDrmInfo == null) {
- final String msg = "prepareDrm(): Wrong usage: The player must be prepared and "
- + "DRM info be retrieved before this call.";
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- }
-
- if (mActiveDrmScheme) {
- final String msg = "prepareDrm(): Wrong usage: There is already "
- + "an active DRM scheme with " + mDrmUUID;
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- }
-
- if (mPrepareDrmInProgress) {
- final String msg = "prepareDrm(): Wrong usage: There is already "
- + "a pending prepareDrm call.";
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- }
-
- if (mDrmProvisioningInProgress) {
- final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress.";
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- }
-
- // shouldn't need this; just for safeguard
- cleanDrmObj();
-
- mPrepareDrmInProgress = true;
-
- try {
- // only creating the DRM object to allow pre-openSession configuration
- prepareDrm_createDrmStep(uuid);
- } catch (Exception e) {
- Log.w(TAG, "prepareDrm(): Exception ", e);
- mPrepareDrmInProgress = false;
- throw e;
- }
-
- mDrmConfigAllowed = true;
- } // synchronized
-
- // call the callback outside the lock
- if (mOnDrmConfigHelper != null) {
- mOnDrmConfigHelper.onDrmConfig(this, dsd);
- }
-
- synchronized (mDrmLock) {
- mDrmConfigAllowed = false;
- boolean earlyExit = false;
-
- try {
- prepareDrm_openSessionStep(uuid);
-
- mDrmUUID = uuid;
- mActiveDrmScheme = true;
- mPrepareDrmInProgress = false;
- } catch (IllegalStateException e) {
- final String msg = "prepareDrm(): Wrong usage: The player must be "
- + "in the prepared state to call prepareDrm().";
- Log.e(TAG, msg);
- earlyExit = true;
- mPrepareDrmInProgress = false;
- throw new IllegalStateException(msg);
- } catch (NotProvisionedException e) {
- Log.w(TAG, "prepareDrm: NotProvisionedException", e);
- throw e;
- } catch (Exception e) {
- Log.e(TAG, "prepareDrm: Exception " + e);
- earlyExit = true;
- mPrepareDrmInProgress = false;
- throw e;
- } finally {
- if (earlyExit) { // clean up object if didn't succeed
- cleanDrmObj();
- }
- } // finally
- } // synchronized
- }
-
/**
* Releases the DRM session for the given data source
* <p>
@@ -3581,35 +3497,10 @@
// This is a synchronous call.
public void releaseDrm(@NonNull DataSourceDesc dsd)
throws NoDrmSchemeException {
- // TODO: this implementation only works when dsd is the only data source
- synchronized (mDrmLock) {
- Log.v(TAG, "releaseDrm:");
-
- if (!mActiveDrmScheme) {
- Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
- throw new NoDrmSchemeException(
- "releaseDrm: No active DRM scheme to release.");
- }
-
- try {
- // we don't have the player's state in this layer. The below call raises
- // exception if we're in a non-stopped/prepared state.
-
- // for cleaning native/mediaserver crypto object
- native_releaseDrm();
-
- // for cleaning client-side MediaDrm object; only called if above has succeeded
- cleanDrmObj();
-
- mActiveDrmScheme = false;
- } catch (IllegalStateException e) {
- Log.w(TAG, "releaseDrm: Exception ", e);
- throw new IllegalStateException(
- "releaseDrm: The player is not in a valid state.");
- } catch (Exception e) {
- Log.e(TAG, "releaseDrm: Exception ", e);
- }
- } // synchronized
+ final SourceInfo sourceInfo = getSourceInfo(dsd);
+ if (sourceInfo != null) {
+ sourceInfo.mDrmHandle.release();
+ }
}
private native void native_releaseDrm();
@@ -3653,51 +3544,22 @@
*
* @throws NoDrmSchemeException if there is no active DRM session
*/
- @NonNull
public MediaDrm.KeyRequest getDrmKeyRequest(
@NonNull DataSourceDesc dsd,
@Nullable byte[] keySetId, @Nullable byte[] initData,
@Nullable String mimeType, @MediaDrmKeyType int keyType,
@Nullable Map<String, String> optionalParameters)
throws NoDrmSchemeException {
- // TODO: this implementation only works when dsd is the only data source
- Log.v(TAG, "getDrmKeyRequest: "
- + " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType
- + " keyType: " + keyType + " optionalParameters: " + optionalParameters);
+ Log.v(TAG, "getDrmKeyRequest: " +
+ " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
+ " keyType: " + keyType + " optionalParameters: " + optionalParameters);
- synchronized (mDrmLock) {
- if (!mActiveDrmScheme) {
- Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
- throw new NoDrmSchemeException(
- "getDrmKeyRequest: Has to set a DRM scheme first.");
- }
-
- try {
- byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE)
- ? mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
- keySetId; // keySetId for KEY_TYPE_RELEASE
-
- HashMap<String, String> hmapOptionalParameters =
- (optionalParameters != null)
- ? new HashMap<String, String>(optionalParameters) :
- null;
-
- MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
- keyType, hmapOptionalParameters);
- Log.v(TAG, "getDrmKeyRequest: --> request: " + request);
-
- return request;
-
- } catch (NotProvisionedException e) {
- Log.w(TAG, "getDrmKeyRequest NotProvisionedException: "
- + "Unexpected. Shouldn't have reached here.");
- throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error.");
- } catch (Exception e) {
- Log.w(TAG, "getDrmKeyRequest Exception " + e);
- throw e;
- }
-
- } // synchronized
+ final SourceInfo sourceInfo = getSourceInfo(dsd);
+ if (sourceInfo != null) {
+ return sourceInfo.mDrmHandle.getDrmKeyRequest(
+ keySetId, initData, mimeType, keyType, optionalParameters);
+ }
+ return null;
}
/**
@@ -3727,40 +3589,13 @@
@NonNull DataSourceDesc dsd,
@Nullable byte[] keySetId, @NonNull byte[] response)
throws NoDrmSchemeException, DeniedByServerException {
- // TODO: this implementation only works when dsd is the only data source
Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response);
- synchronized (mDrmLock) {
-
- if (!mActiveDrmScheme) {
- Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
- throw new NoDrmSchemeException(
- "getDrmKeyRequest: Has to set a DRM scheme first.");
- }
-
- try {
- byte[] scope = (keySetId == null)
- ? mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
- keySetId; // keySetId for KEY_TYPE_RELEASE
-
- byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
-
- Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response
- + " --> " + keySetResult);
-
-
- return keySetResult;
-
- } catch (NotProvisionedException e) {
- Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: "
- + "Unexpected. Shouldn't have reached here.");
- throw new IllegalStateException("provideDrmKeyResponse: "
- + "Unexpected provisioning error.");
- } catch (Exception e) {
- Log.w(TAG, "provideDrmKeyResponse Exception " + e);
- throw e;
- }
- } // synchronized
+ final SourceInfo sourceInfo = getSourceInfo(dsd);
+ if (sourceInfo != null) {
+ return sourceInfo.mDrmHandle.provideDrmKeyResponse(keySetId, response);
+ }
+ return null;
}
/**
@@ -3779,23 +3614,12 @@
@NonNull DataSourceDesc dsd,
@NonNull byte[] keySetId)
throws NoDrmSchemeException {
- // TODO: this implementation only works when dsd is the only data source
Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId);
- synchronized (mDrmLock) {
- if (!mActiveDrmScheme) {
- Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
- throw new NoDrmSchemeException(
- "restoreDrmKeys: Has to set a DRM scheme first.");
- }
-
- try {
- mDrmObj.restoreKeys(mDrmSessionId, keySetId);
- } catch (Exception e) {
- Log.w(TAG, "restoreKeys Exception " + e);
- throw e;
- }
- } // synchronized
+ final SourceInfo sourceInfo = getSourceInfo(dsd);
+ if (sourceInfo != null) {
+ sourceInfo.mDrmHandle.restoreDrmKeys(keySetId);
+ }
}
/**
@@ -3812,34 +3636,17 @@
*
* @throws NoDrmSchemeException if there is no active DRM session
*/
- @NonNull
public String getDrmPropertyString(
@NonNull DataSourceDesc dsd,
@NonNull @MediaDrmStringProperty String propertyName)
throws NoDrmSchemeException {
- // TODO: this implementation only works when dsd is the only data source
Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
- String value;
- synchronized (mDrmLock) {
-
- if (!mActiveDrmScheme && !mDrmConfigAllowed) {
- Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
- throw new NoDrmSchemeException(
- "getDrmPropertyString: Has to prepareDrm() first.");
- }
-
- try {
- value = mDrmObj.getPropertyString(propertyName);
- } catch (Exception e) {
- Log.w(TAG, "getDrmPropertyString Exception " + e);
- throw e;
- }
- } // synchronized
-
- Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value);
-
- return value;
+ final SourceInfo sourceInfo = getSourceInfo(dsd);
+ if (sourceInfo != null) {
+ return sourceInfo.mDrmHandle.getDrmPropertyString(propertyName);
+ }
+ return null;
}
/**
@@ -3863,21 +3670,10 @@
// TODO: this implementation only works when dsd is the only data source
Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
- synchronized (mDrmLock) {
-
- if (!mActiveDrmScheme && !mDrmConfigAllowed) {
- Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
- throw new NoDrmSchemeException(
- "setDrmPropertyString: Has to prepareDrm() first.");
- }
-
- try {
- mDrmObj.setPropertyString(propertyName, value);
- } catch (Exception e) {
- Log.w(TAG, "setDrmPropertyString Exception " + e);
- throw e;
- }
- } // synchronized
+ final SourceInfo sourceInfo = getSourceInfo(dsd);
+ if (sourceInfo != null) {
+ sourceInfo.mDrmHandle.setDrmPropertyString(propertyName, value);
+ }
}
/**
@@ -4029,43 +3825,6 @@
private native void native_prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId);
- // Modular DRM helpers
-
- private void prepareDrm_createDrmStep(@NonNull UUID uuid)
- throws UnsupportedSchemeException {
- Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
-
- try {
- mDrmObj = new MediaDrm(uuid);
- Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
- } catch (Exception e) { // UnsupportedSchemeException
- Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
- throw e;
- }
- }
-
- private void prepareDrm_openSessionStep(@NonNull UUID uuid)
- throws NotProvisionedException, ResourceBusyException {
- Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
-
- // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do
- // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
- // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
- try {
- mDrmSessionId = mDrmObj.openSession();
- Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
-
- // Sending it down to native/mediaserver to create the crypto object
- // This call could simply fail due to bad player state, e.g., after play().
- native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
- Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded");
-
- } catch (Exception e) { //ResourceBusyException, NotProvisionedException
- Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
- throw e;
- }
- }
-
// Instantiated from the native side
@SuppressWarnings("unused")
private static class StreamEventCallback extends AudioTrack.StreamEventCallback {
@@ -4097,227 +3856,28 @@
}
}
- private class ProvisioningThread extends Thread {
- public static final int TIMEOUT_MS = 60000;
-
- private final DataSourceDesc mDSD;
- private UUID mUuid;
- private String mUrlStr;
- private Object mDrmLock;
- private MediaPlayer2 mMediaPlayer;
- private int mStatus;
- public int status() {
- return mStatus;
- }
-
- public ProvisioningThread(MediaDrm.ProvisionRequest request,
- DataSourceDesc dsd,
- UUID uuid, MediaPlayer2 mediaPlayer) {
- // lock is held by the caller
- mDSD = dsd;
- mDrmLock = mediaPlayer.mDrmLock;
- mMediaPlayer = mediaPlayer;
-
- mUrlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
- mUuid = uuid;
-
- mStatus = PREPARE_DRM_STATUS_PREPARATION_ERROR;
-
- Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + mUrlStr);
- }
-
- public void run() {
-
- byte[] response = null;
- boolean provisioningSucceeded = false;
- try {
- URL url = new URL(mUrlStr);
- final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- try {
- connection.setRequestMethod("POST");
- connection.setDoOutput(false);
- connection.setDoInput(true);
- connection.setConnectTimeout(TIMEOUT_MS);
- connection.setReadTimeout(TIMEOUT_MS);
-
- connection.connect();
- response = readInputStreamFully(connection.getInputStream());
-
- Log.v(TAG, "handleProvisioninig: Thread run: response "
- + response.length + " " + response);
- } catch (Exception e) {
- mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
- Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url);
- } finally {
- connection.disconnect();
- }
- } catch (Exception e) {
- mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
- Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e);
- }
-
- if (response != null) {
- try {
- mDrmObj.provideProvisionResponse(response);
- Log.v(TAG, "handleProvisioninig: Thread run: "
- + "provideProvisionResponse SUCCEEDED!");
-
- provisioningSucceeded = true;
- } catch (Exception e) {
- mStatus = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
- Log.w(TAG, "handleProvisioninig: Thread run: "
- + "provideProvisionResponse " + e);
- }
- }
-
- boolean succeeded = false;
-
- synchronized (mDrmLock) {
- // continuing with prepareDrm
- if (provisioningSucceeded) {
- succeeded = mMediaPlayer.resumePrepareDrm(mUuid);
- mStatus = (succeeded)
- ? PREPARE_DRM_STATUS_SUCCESS :
- PREPARE_DRM_STATUS_PREPARATION_ERROR;
- }
- mMediaPlayer.mDrmProvisioningInProgress = false;
- mMediaPlayer.mPrepareDrmInProgress = false;
- if (!succeeded) {
- cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock
- }
- } // synchronized
-
- // calling the callback outside the lock
- sendDrmEvent(new DrmEventNotifier() {
- @Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmPrepared(
- mMediaPlayer, mDSD, mStatus);
- }
- });
-
- synchronized (mTaskLock) {
- if (mCurrentTask != null
- && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
- && mCurrentTask.mNeedToWaitForEventToComplete) {
- mCurrentTask = null;
- processPendingTask_l();
- }
- }
- }
-
- /**
- * Returns a byte[] containing the remainder of 'in', closing it when done.
- */
- private byte[] readInputStreamFully(InputStream in) throws IOException {
- try {
- return readInputStreamFullyNoClose(in);
- } finally {
- in.close();
- }
- }
-
- /**
- * Returns a byte[] containing the remainder of 'in'.
- */
- private byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int count;
- while ((count = in.read(buffer)) != -1) {
- bytes.write(buffer, 0, count);
- }
- return bytes.toByteArray();
- }
- } // ProvisioningThread
-
- private int handleProvisioninig(DataSourceDesc dsd, UUID uuid) {
- synchronized (mDrmLock) {
- if (mDrmProvisioningInProgress) {
- Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress");
- return PREPARE_DRM_STATUS_PREPARATION_ERROR;
- }
-
- MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
- if (provReq == null) {
- Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null.");
- return PREPARE_DRM_STATUS_PREPARATION_ERROR;
- }
-
- Log.v(TAG, "handleProvisioninig provReq "
- + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
-
- // networking in a background thread
- mDrmProvisioningInProgress = true;
-
- mDrmProvisioningThread = new ProvisioningThread(provReq, dsd, uuid, this);
- mDrmProvisioningThread.start();
-
- return PREPARE_DRM_STATUS_SUCCESS;
- }
- }
-
- private boolean resumePrepareDrm(UUID uuid) {
- Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
-
- // mDrmLock is guaranteed to be held
- boolean success = false;
+ /**
+ * Returns a byte[] containing the remainder of 'in', closing it when done.
+ */
+ private static byte[] readInputStreamFully(InputStream in) throws IOException {
try {
- // resuming
- prepareDrm_openSessionStep(uuid);
-
- mDrmUUID = uuid;
- mActiveDrmScheme = true;
-
- success = true;
- } catch (Exception e) {
- Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed with " + e);
- // mDrmObj clean up is done by the caller
+ return readInputStreamFullyNoClose(in);
+ } finally {
+ in.close();
}
-
- return success;
}
- private void resetDrmState() {
- synchronized (mDrmLock) {
- Log.v(TAG, "resetDrmState:"
- + " mDrmInfo=" + mDrmInfo
- + " mDrmProvisioningThread=" + mDrmProvisioningThread
- + " mPrepareDrmInProgress=" + mPrepareDrmInProgress
- + " mActiveDrmScheme=" + mActiveDrmScheme);
-
- mDrmInfoResolved = false;
- mDrmInfo = null;
-
- if (mDrmProvisioningThread != null) {
- // timeout; relying on HttpUrlConnection
- try {
- mDrmProvisioningThread.join();
- } catch (InterruptedException e) {
- Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
- }
- mDrmProvisioningThread = null;
- }
-
- mPrepareDrmInProgress = false;
- mActiveDrmScheme = false;
-
- cleanDrmObj();
- } // synchronized
- }
-
- private void cleanDrmObj() {
- // the caller holds mDrmLock
- Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
-
- if (mDrmSessionId != null) {
- mDrmObj.closeSession(mDrmSessionId);
- mDrmSessionId = null;
+ /**
+ * Returns a byte[] containing the remainder of 'in'.
+ */
+ private static byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int count;
+ while ((count = in.read(buffer)) != -1) {
+ bytes.write(buffer, 0, count);
}
- if (mDrmObj != null) {
- mDrmObj.release();
- mDrmObj = null;
- }
+ return bytes.toByteArray();
}
private static byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
@@ -4333,8 +3893,6 @@
return uuidBytes;
}
- // Modular DRM end
-
private static class TimedTextUtil {
// These keys must be in sync with the keys in TextDescription2.h
private static final int KEY_START_TIME = 7; // int
@@ -4410,6 +3968,7 @@
}
private abstract class Task implements Runnable {
+ final long mTaskId = mTaskIdGenerator.getAndIncrement();
private final int mMediaCallType;
private final boolean mNeedToWaitForEventToComplete;
private DataSourceDesc mDSD;
@@ -4501,7 +4060,503 @@
}
};
- private final class SourceInfo {
+ // Modular DRM
+ final class DrmHandle {
+
+ static final int PROVISION_TIMEOUT_MS = 60000;
+
+ final DataSourceDesc mDSD;
+
+ //--- guarded by |this| start
+ MediaDrm mDrmObj;
+ byte[] mDrmSessionId;
+ UUID mActiveDrmUUID;
+ boolean mDrmConfigAllowed;
+ boolean mDrmProvisioningInProgress;
+ boolean mPrepareDrmInProgress;
+ Future<?> mProvisionResult;
+ //--- guarded by |this| end
+
+ DrmHandle(DataSourceDesc dsd) {
+ mDSD = dsd;
+ }
+
+ void prepare(UUID uuid) throws UnsupportedSchemeException,
+ ResourceBusyException, NotProvisionedException {
+ final OnDrmConfigHelper onDrmConfigHelper = mOnDrmConfigHelper;
+ Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + onDrmConfigHelper);
+
+ synchronized (this) {
+ if (mActiveDrmUUID != null) {
+ final String msg = "prepareDrm(): Wrong usage: There is already "
+ + "an active DRM scheme with " + uuid;
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mPrepareDrmInProgress) {
+ final String msg = "prepareDrm(): Wrong usage: There is already "
+ + "a pending prepareDrm call.";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mDrmProvisioningInProgress) {
+ final String msg = "prepareDrm(): Unexpectd: Provisioning already in progress";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ // shouldn't need this; just for safeguard
+ cleanDrmObj();
+
+ mPrepareDrmInProgress = true;
+
+ try {
+ // only creating the DRM object to allow pre-openSession configuration
+ prepareDrm_createDrmStep(uuid);
+ } catch (Exception e) {
+ Log.w(TAG, "prepareDrm(): Exception ", e);
+ mPrepareDrmInProgress = false;
+ throw e;
+ }
+
+ mDrmConfigAllowed = true;
+ } // synchronized
+
+ // call the callback outside the lock
+ if (onDrmConfigHelper != null) {
+ onDrmConfigHelper.onDrmConfig(MediaPlayer2.this, mDSD);
+ }
+
+ synchronized (this) {
+ mDrmConfigAllowed = false;
+ boolean earlyExit = false;
+
+ try {
+ prepareDrm_openSessionStep(uuid);
+
+ this.mActiveDrmUUID = uuid;
+ mPrepareDrmInProgress = false;
+ } catch (IllegalStateException e) {
+ final String msg = "prepareDrm(): Wrong usage: The player must be "
+ + "in the prepared state to call prepareDrm().";
+ Log.e(TAG, msg);
+ earlyExit = true;
+ mPrepareDrmInProgress = false;
+ throw new IllegalStateException(msg);
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "prepareDrm: NotProvisionedException", e);
+ throw e;
+ } catch (Exception e) {
+ Log.e(TAG, "prepareDrm: Exception " + e);
+ earlyExit = true;
+ mPrepareDrmInProgress = false;
+ throw e;
+ } finally {
+ if (earlyExit) { // clean up object if didn't succeed
+ cleanDrmObj();
+ }
+ } // finally
+ } // synchronized
+ }
+
+ void prepareDrm_createDrmStep(UUID uuid)
+ throws UnsupportedSchemeException {
+ Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
+
+ try {
+ mDrmObj = new MediaDrm(uuid);
+ Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
+ } catch (Exception e) { // UnsupportedSchemeException
+ Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
+ throw e;
+ }
+ }
+
+ void prepareDrm_openSessionStep(UUID uuid)
+ throws NotProvisionedException, ResourceBusyException {
+ Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
+
+ // TODO:
+ // don't need an open session for a future specialKeyReleaseDrm mode but we should do
+ // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
+ // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
+ try {
+ mDrmSessionId = mDrmObj.openSession();
+ Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
+
+ // Sending it down to native/mediaserver to create the crypto object
+ // This call could simply fail due to bad player state, e.g., after play().
+ MediaPlayer2.this.native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
+ Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded");
+
+ } catch (Exception e) { //ResourceBusyException, NotProvisionedException
+ Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
+ throw e;
+ }
+
+ }
+
+ int handleProvisioninig(UUID uuid, long taskId) {
+ synchronized (this) {
+ if (mDrmProvisioningInProgress) {
+ Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress");
+ return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+
+ MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
+ if (provReq == null) {
+ Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null.");
+ return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+
+ Log.v(TAG, "handleProvisioninig provReq "
+ + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
+
+ // networking in a background thread
+ mDrmProvisioningInProgress = true;
+
+ mProvisionResult = mDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
+
+ return PREPARE_DRM_STATUS_SUCCESS;
+ }
+ }
+
+ void provision(UUID uuid, long taskId) {
+
+ MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
+ String urlStr = provReq.getDefaultUrl();
+ urlStr += "&signedRequest=" + new String(provReq.getData());
+ Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + urlStr);
+
+ byte[] response = null;
+ boolean provisioningSucceeded = false;
+ int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ try {
+ URL url = new URL(urlStr);
+ final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ try {
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(false);
+ connection.setDoInput(true);
+ connection.setConnectTimeout(PROVISION_TIMEOUT_MS);
+ connection.setReadTimeout(PROVISION_TIMEOUT_MS);
+
+ connection.connect();
+ response = readInputStreamFully(connection.getInputStream());
+
+ Log.v(TAG, "handleProvisioninig: Thread run: response " +
+ response.length + " " + response);
+ } catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+ Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url);
+ } finally {
+ connection.disconnect();
+ }
+ } catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+ Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e);
+ }
+
+ if (response != null) {
+ try {
+ mDrmObj.provideProvisionResponse(response);
+ Log.v(TAG, "handleProvisioninig: Thread run: " +
+ "provideProvisionResponse SUCCEEDED!");
+
+ provisioningSucceeded = true;
+ } catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
+ Log.w(TAG, "handleProvisioninig: Thread run: " +
+ "provideProvisionResponse " + e);
+ }
+ }
+
+ boolean succeeded = false;
+
+ synchronized (this) {
+ // continuing with prepareDrm
+ if (provisioningSucceeded) {
+ succeeded = resumePrepare(uuid);
+ status = (succeeded) ?
+ PREPARE_DRM_STATUS_SUCCESS :
+ PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+ mDrmProvisioningInProgress = false;
+ mPrepareDrmInProgress = false;
+ if (!succeeded) {
+ cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock
+ }
+ } // synchronized
+
+ // calling the callback outside the lock
+ final int finalStatus = status;
+ sendDrmEvent(new DrmEventNotifier() {
+ @Override
+ public void notify(DrmEventCallback callback) {
+ callback.onDrmPrepared(
+ MediaPlayer2.this, mDSD, finalStatus);
+ }
+ });
+
+ synchronized (mTaskLock) {
+ if (mCurrentTask != null
+ && mCurrentTask.mTaskId == taskId
+ && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
+ && mCurrentTask.mNeedToWaitForEventToComplete) {
+ mCurrentTask = null;
+ processPendingTask_l();
+ }
+ }
+ }
+
+ Runnable newProvisioningTask(UUID uuid, long taskId) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ provision(uuid, taskId);
+ }
+ };
+ }
+
+ boolean resumePrepare(UUID uuid) {
+ Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
+
+ // mDrmLock is guaranteed to be held
+ boolean success = false;
+ try {
+ // resuming
+ prepareDrm_openSessionStep(uuid);
+
+ this.mActiveDrmUUID = uuid;
+
+ success = true;
+ } catch (Exception e) {
+ Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed:" + e);
+ // mDrmObj clean up is done by the caller
+ }
+
+ return success;
+ }
+
+ void cleanDrmObj() {
+ // the caller holds mDrmLock
+ Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
+
+ if (mDrmSessionId != null) {
+ mDrmObj.closeSession(mDrmSessionId);
+ mDrmSessionId = null;
+ }
+ if (mDrmObj != null) {
+ mDrmObj.close();
+ mDrmObj = null;
+ }
+ }
+
+ void release() throws NoDrmSchemeException {
+ synchronized (this) {
+ Log.v(TAG, "releaseDrm:");
+
+ if (mActiveDrmUUID == null) {
+ Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
+ throw new NoDrmSchemeException(
+ "releaseDrm: No active DRM scheme to release.");
+ }
+
+ try {
+ // we don't have the player's state in this layer. The below call raises
+ // exception if we're in a non-stopped/prepared state.
+
+ // for cleaning native/mediaserver crypto object
+ native_releaseDrm();
+
+ // for cleaning client-side MediaDrm object; only called if above has succeeded
+ cleanDrmObj();
+
+ this.mActiveDrmUUID = null;
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "releaseDrm: Exception ", e);
+ throw new IllegalStateException(
+ "releaseDrm: The player is not in a valid state.");
+ } catch (Exception e) {
+ Log.e(TAG, "releaseDrm: Exception ", e);
+ }
+ } // synchronized
+ }
+
+ void cleanup() {
+ synchronized (this) {
+ Log.v(TAG, "cleanupDrm: " +
+ " mProvisioningTask=" + mProvisionResult +
+ " mPrepareDrmInProgress=" + mPrepareDrmInProgress +
+ " mActiveDrmScheme=" + mActiveDrmUUID);
+
+ if (mProvisionResult != null) {
+ // timeout; relying on HttpUrlConnection
+ try {
+ mProvisionResult.get();
+ }
+ catch (InterruptedException | ExecutionException e) {
+ Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
+ }
+ }
+
+ // set to false to avoid duplicate release calls
+ this.mActiveDrmUUID = null;
+
+ cleanDrmObj();
+ } // synchronized
+ }
+
+ Runnable newCleanupTask() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ cleanup();
+ }
+ };
+ }
+
+ MediaDrm.KeyRequest getDrmKeyRequest(
+ byte[] keySetId, byte[] initData,
+ String mimeType, int keyType,
+ Map<String, String> optionalParameters)
+ throws NoDrmSchemeException {
+ synchronized (this) {
+ if (mActiveDrmUUID == null) {
+ Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "getDrmKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
+ mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+ keySetId; // keySetId for KEY_TYPE_RELEASE
+
+ HashMap<String, String> hmapOptionalParameters =
+ (optionalParameters != null)
+ ? new HashMap<String, String>(optionalParameters)
+ : null;
+
+ MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(
+ scope, initData, mimeType, keyType, hmapOptionalParameters);
+ Log.v(TAG, "getDrmKeyRequest: --> request: " + request);
+
+ return request;
+
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " +
+ "Unexpected. Shouldn't have reached here.");
+ throw new IllegalStateException("getDrmKeyRequest: provisioning error.");
+ } catch (Exception e) {
+ Log.w(TAG, "getDrmKeyRequest Exception " + e);
+ throw e;
+ }
+
+ }
+ }
+
+ byte[] provideDrmKeyResponse(byte[] keySetId, byte[] response)
+ throws NoDrmSchemeException, DeniedByServerException {
+ synchronized (this) {
+
+ if (mActiveDrmUUID == null) {
+ Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "getDrmKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ byte[] scope = (keySetId == null) ?
+ mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+ keySetId; // keySetId for KEY_TYPE_RELEASE
+
+ byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
+
+ Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId
+ + " response: " + response + " --> " + keySetResult);
+
+
+ return keySetResult;
+
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " +
+ "Unexpected. Shouldn't have reached here.");
+ throw new IllegalStateException("provideDrmKeyResponse: " +
+ "Unexpected provisioning error.");
+ } catch (Exception e) {
+ Log.w(TAG, "provideDrmKeyResponse Exception " + e);
+ throw e;
+ }
+ }
+ }
+
+ void restoreDrmKeys(byte[] keySetId)
+ throws NoDrmSchemeException {
+ synchronized (this) {
+ if (mActiveDrmUUID == null) {
+ Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "restoreDrmKeys: Has to set a DRM scheme first.");
+ }
+
+ try {
+ mDrmObj.restoreKeys(mDrmSessionId, keySetId);
+ } catch (Exception e) {
+ Log.w(TAG, "restoreKeys Exception " + e);
+ throw e;
+ }
+ }
+ }
+
+ String getDrmPropertyString(String propertyName)
+ throws NoDrmSchemeException {
+ String v;
+ synchronized (this) {
+
+ if (mActiveDrmUUID == null && !mDrmConfigAllowed) {
+ Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "getDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ v = mDrmObj.getPropertyString(propertyName);
+ } catch (Exception e) {
+ Log.w(TAG, "getDrmPropertyString Exception " + e);
+ throw e;
+ }
+ } // synchronized
+
+ Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + v);
+
+ return v;
+ }
+
+ void setDrmPropertyString(String propertyName, String value)
+ throws NoDrmSchemeException {
+ synchronized (this) {
+
+ if ( mActiveDrmUUID == null && !mDrmConfigAllowed ) {
+ Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
+ throw new NoDrmSchemeException(
+ "setDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ mDrmObj.setPropertyString(propertyName, value);
+ } catch ( Exception e ) {
+ Log.w(TAG, "setDrmPropertyString Exception " + e);
+ throw e;
+ }
+ }
+ }
+
+ }
+
+ final class SourceInfo {
final DataSourceDesc mDSD;
final long mId = mSrcIdGenerator.getAndIncrement();
AtomicInteger mBufferedPercentage = new AtomicInteger(0);
@@ -4513,8 +4568,14 @@
int mStateAsNextSource = NEXT_SOURCE_STATE_INIT;
boolean mPlayPendingAsNextSource = false;
+ // Modular DRM
+ final DrmHandle mDrmHandle;
+ DrmInfo mDrmInfo;
+ boolean mDrmInfoResolved;
+
SourceInfo(DataSourceDesc dsd) {
this.mDSD = dsd;
+ mDrmHandle = new DrmHandle(dsd);
}
void close() {
@@ -4535,7 +4596,7 @@
}
- private SourceInfo getSourceInfoById(long srcId) {
+ private SourceInfo getSourceInfo(long srcId) {
synchronized (mSrcLock) {
if (isCurrentSource(srcId)) {
return mCurrentSourceInfo;
@@ -4547,34 +4608,65 @@
return null;
}
+ private SourceInfo getSourceInfo(DataSourceDesc dsd) {
+ synchronized (mSrcLock) {
+ if (isCurrentSource(dsd)) {
+ return mCurrentSourceInfo;
+ }
+ if (isNextSource(dsd)) {
+ return mNextSourceInfos.peek();
+ }
+ }
+ return null;
+ }
+
private boolean isCurrentSource(long srcId) {
synchronized (mSrcLock) {
return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId;
}
}
+ private boolean isCurrentSource(DataSourceDesc dsd) {
+ synchronized (mSrcLock) {
+ return mCurrentSourceInfo != null && mCurrentSourceInfo.mDSD == dsd;
+ }
+ }
+
private boolean isNextSource(long srcId) {
SourceInfo nextSourceInfo = mNextSourceInfos.peek();
return nextSourceInfo != null && nextSourceInfo.mId == srcId;
}
- private void setCurrentSourceInfo(SourceInfo newSourceInfo) {
- synchronized (mSrcLock) {
- if (mCurrentSourceInfo != null) {
- mCurrentSourceInfo.close();
- }
- mCurrentSourceInfo = newSourceInfo;
+ private boolean isNextSource(DataSourceDesc dsd) {
+ SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+ return nextSourceInfo != null && nextSourceInfo.mDSD == dsd;
+ }
+
+ @GuardedBy("mSrcLock")
+ private void setCurrentSourceInfo_l(SourceInfo sourceInfo) {
+ cleanupSourceInfo(mCurrentSourceInfo);
+ mCurrentSourceInfo = sourceInfo;
+ }
+
+ @GuardedBy("mSrcLock")
+ private void clearNextSourceInfos_l() {
+ while (!mNextSourceInfos.isEmpty()) {
+ cleanupSourceInfo(mNextSourceInfos.poll());
}
}
- private void clearNextSourceInfos() {
+ private void cleanupSourceInfo(SourceInfo sourceInfo) {
+ if (sourceInfo != null) {
+ sourceInfo.close();
+ Runnable task = sourceInfo.mDrmHandle.newCleanupTask();
+ mDrmThreadPool.submit(task);
+ }
+ }
+
+ private void clearSourceInfos() {
synchronized (mSrcLock) {
- for (SourceInfo sourceInfo : mNextSourceInfos) {
- if (sourceInfo != null) {
- sourceInfo.close();
- }
- }
- mNextSourceInfos.clear();
+ setCurrentSourceInfo_l(null);
+ clearNextSourceInfos_l();
}
}
diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml
index 5ca34b0..1e8cd5a 100644
--- a/packages/SystemUI/res/layout/biometric_dialog.xml
+++ b/packages/SystemUI/res/layout/biometric_dialog.xml
@@ -160,6 +160,15 @@
android:maxLines="2"
android:text="@string/biometric_dialog_confirm"
android:visibility="gone"/>
+ <!-- Try Again Button -->
+ <Button android:id="@+id/button_try_again"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:gravity="center"
+ android:maxLines="2"
+ android:text="@string/biometric_dialog_try_again"
+ android:visibility="gone"/>
<Space android:id="@+id/rightSpacer"
android:layout_width="12dip"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2148e27..ec7c9b2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -272,6 +272,8 @@
<string name="accessibility_biometric_dialog_help_area">Help message area</string>
<!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] -->
<string name="biometric_dialog_confirm">Confirm</string>
+ <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR_LIMIT=30] -->
+ <string name="biometric_dialog_try_again">Try again</string>
<!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
<string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index c0047c0..a90a7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -21,7 +21,7 @@
import android.content.res.Configuration;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -52,15 +52,20 @@
private static final int MSG_BUTTON_NEGATIVE = 6;
private static final int MSG_USER_CANCELED = 7;
private static final int MSG_BUTTON_POSITIVE = 8;
+ private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9;
+ private static final int MSG_TRY_AGAIN_PRESSED = 10;
private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view
private SomeArgs mCurrentDialogArgs;
private BiometricDialogView mCurrentDialog;
private WindowManager mWindowManager;
- private IBiometricPromptReceiver mReceiver;
+ private IBiometricServiceReceiverInternal mReceiver;
private boolean mDialogShowing;
private Callback mCallback = new Callback();
+ private boolean mTryAgainShowing; // No good place to save state before config change :/
+ private boolean mConfirmShowing; // No good place to save state before config change :/
+
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -89,6 +94,15 @@
case MSG_BUTTON_POSITIVE:
handleButtonPositive();
break;
+ case MSG_BIOMETRIC_SHOW_TRY_AGAIN:
+ handleShowTryAgain();
+ break;
+ case MSG_TRY_AGAIN_PRESSED:
+ handleTryAgainPressed();
+ break;
+ default:
+ Log.w(TAG, "Unknown message: " + msg.what);
+ break;
}
}
};
@@ -96,7 +110,7 @@
private class Callback implements DialogViewCallback {
@Override
public void onUserCanceled() {
- mHandler.obtainMessage(BiometricDialogImpl.MSG_USER_CANCELED).sendToTarget();
+ mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget();
}
@Override
@@ -107,12 +121,17 @@
@Override
public void onNegativePressed() {
- mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget();
+ mHandler.obtainMessage(MSG_BUTTON_NEGATIVE).sendToTarget();
}
@Override
public void onPositivePressed() {
- mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_POSITIVE).sendToTarget();
+ mHandler.obtainMessage(MSG_BUTTON_POSITIVE).sendToTarget();
+ }
+
+ @Override
+ public void onTryAgainPressed() {
+ mHandler.obtainMessage(MSG_TRY_AGAIN_PRESSED).sendToTarget();
}
}
@@ -139,13 +158,14 @@
}
@Override
- public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
- boolean requireConfirmation, int userId) {
+ public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
+ int type, boolean requireConfirmation, int userId) {
if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type);
// Remove these messages as they are part of the previous client
mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
mHandler.removeMessages(MSG_BIOMETRIC_HELP);
mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED);
+ mHandler.removeMessages(MSG_HIDE_DIALOG);
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
args.arg2 = receiver;
@@ -179,6 +199,12 @@
mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
}
+ @Override
+ public void showBiometricTryAgain() {
+ if (DEBUG) Log.d(TAG, "showBiometricTryAgain");
+ mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget();
+ }
+
private void handleShowDialog(SomeArgs args, boolean skipAnimation) {
mCurrentDialogArgs = args;
final int type = args.argi1;
@@ -193,11 +219,13 @@
Log.w(TAG, "Dialog already showing");
return;
}
- mReceiver = (IBiometricPromptReceiver) args.arg2;
+ mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
mCurrentDialog.setBundle((Bundle)args.arg1);
mCurrentDialog.setRequireConfirmation((boolean) args.arg3);
mCurrentDialog.setUserId(args.argi2);
mCurrentDialog.setSkipIntro(skipAnimation);
+ mCurrentDialog.setPendingTryAgain(mTryAgainShowing);
+ mCurrentDialog.setPendingConfirm(mConfirmShowing);
mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
mDialogShowing = true;
}
@@ -209,7 +237,8 @@
mContext.getResources()
.getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
if (mCurrentDialog.requiresConfirmation()) {
- mCurrentDialog.showConfirmationButton();
+ mConfirmShowing = true;
+ mCurrentDialog.showConfirmationButton(true /* show */);
} else {
handleHideDialog(false /* userCanceled */);
}
@@ -226,6 +255,7 @@
if (DEBUG) Log.d(TAG, "Dialog already dismissed");
return;
}
+ mTryAgainShowing = false;
mCurrentDialog.showErrorMessage(error);
}
@@ -246,6 +276,8 @@
}
mReceiver = null;
mDialogShowing = false;
+ mConfirmShowing = false;
+ mTryAgainShowing = false;
mCurrentDialog.startDismiss();
}
@@ -259,6 +291,7 @@
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling negative button", e);
}
+ mTryAgainShowing = false;
handleHideDialog(false /* userCanceled */);
}
@@ -272,13 +305,31 @@
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling positive button", e);
}
+ mConfirmShowing = false;
handleHideDialog(false /* userCanceled */);
}
private void handleUserCanceled() {
+ mTryAgainShowing = false;
+ mConfirmShowing = false;
handleHideDialog(true /* userCanceled */);
}
+ private void handleShowTryAgain() {
+ mCurrentDialog.showTryAgainButton(true /* show */);
+ mTryAgainShowing = true;
+ }
+
+ private void handleTryAgainPressed() {
+ try {
+ mCurrentDialog.clearTemporaryMessage();
+ mTryAgainShowing = false;
+ mReceiver.onTryAgainPressed();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException when handling try again", e);
+ }
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index 38427ad..e085f23 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -87,6 +87,9 @@
protected boolean mRequireConfirmation;
private int mUserId; // used to determine if we should show work background
+ private boolean mPendingShowTryAgain;
+ private boolean mPendingShowConfirm;
+
protected abstract void updateIcon(int lastState, int newState);
protected abstract int getHintStringResourceId();
protected abstract int getAuthenticatedAccessibilityResourceId();
@@ -178,6 +181,7 @@
final Button negative = mLayout.findViewById(R.id.button2);
final Button positive = mLayout.findViewById(R.id.button1);
final ImageView icon = mLayout.findViewById(R.id.biometric_icon);
+ final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
icon.setContentDescription(getResources().getString(getIconDescriptionResourceId()));
@@ -193,6 +197,11 @@
mCallback.onPositivePressed();
});
+ tryAgain.setOnClickListener((View v) -> {
+ showTryAgainButton(false /* show */);
+ mCallback.onTryAgainPressed();
+ });
+
mLayout.setFocusableInTouchMode(true);
mLayout.requestFocus();
}
@@ -207,7 +216,6 @@
final TextView subtitle = mLayout.findViewById(R.id.subtitle);
final TextView description = mLayout.findViewById(R.id.description);
final Button negative = mLayout.findViewById(R.id.button2);
- final Button positive = mLayout.findViewById(R.id.button1);
final ImageView backgroundView = mLayout.findViewById(R.id.background);
if (mUserManager.isManagedProfile(mUserId)) {
@@ -233,8 +241,6 @@
title.setText(titleText);
title.setSelected(true);
- positive.setVisibility(View.INVISIBLE);
-
final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
if (TextUtils.isEmpty(subtitleText)) {
subtitle.setVisibility(View.GONE);
@@ -243,7 +249,8 @@
subtitle.setText(subtitleText);
}
- final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
+ final CharSequence descriptionText =
+ mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
if (TextUtils.isEmpty(descriptionText)) {
description.setVisibility(View.GONE);
} else {
@@ -253,6 +260,9 @@
negative.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
+ showTryAgainButton(mPendingShowTryAgain);
+ showConfirmationButton(mPendingShowConfirm);
+
if (mWasForceRemoved || mSkipIntro) {
// Show the dialog immediately
mLayout.animate().cancel();
@@ -281,11 +291,17 @@
public void startDismiss() {
mAnimatingAway = true;
+ // This is where final cleanup should occur.
final Runnable endActionRunnable = new Runnable() {
@Override
public void run() {
mWindowManager.removeView(BiometricDialogView.this);
mAnimatingAway = false;
+ // Set the icons / text back to normal state
+ handleClearMessage();
+ showTryAgainButton(false /* show */);
+ mPendingShowTryAgain = false;
+ mPendingShowConfirm = false;
}
};
@@ -345,9 +361,13 @@
return mRequireConfirmation;
}
- public void showConfirmationButton() {
+ public void showConfirmationButton(boolean show) {
final Button positive = mLayout.findViewById(R.id.button1);
- positive.setVisibility(View.VISIBLE);
+ if (show) {
+ positive.setVisibility(View.VISIBLE);
+ } else {
+ positive.setVisibility(View.GONE);
+ }
}
public void setUserId(int userId) {
@@ -376,12 +396,18 @@
BiometricPrompt.HIDE_DIALOG_DELAY);
}
+ public void clearTemporaryMessage() {
+ mHandler.removeMessages(MSG_CLEAR_MESSAGE);
+ mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget();
+ }
+
public void showHelpMessage(String message) {
showTemporaryMessage(message);
}
public void showErrorMessage(String error) {
showTemporaryMessage(error);
+ showTryAgainButton(false /* show */);
mCallback.onErrorShown();
}
@@ -390,6 +416,25 @@
mLastState = newState;
}
+ public void showTryAgainButton(boolean show) {
+ final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
+ if (show) {
+ tryAgain.setVisibility(View.VISIBLE);
+ } else {
+ tryAgain.setVisibility(View.GONE);
+ }
+ }
+
+ // Set the state before the window is attached, so we know if the dialog should be started
+ // with or without the button. This is because there's no good onPause signal
+ public void setPendingTryAgain(boolean show) {
+ mPendingShowTryAgain = show;
+ }
+
+ public void setPendingConfirm(boolean show) {
+ mPendingShowConfirm = show;
+ }
+
public WindowManager.LayoutParams getLayoutParams() {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
index f388d9c..24fd22e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
@@ -43,4 +43,9 @@
* should be dismissed.
*/
void onPositivePressed();
+
+ /**
+ * Invoked when the "try again" button is pressed.
+ */
+ void onTryAgainPressed();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 0c8f487..8b93995 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -21,7 +21,7 @@
import android.app.StatusBarManager;
import android.content.ComponentName;
import android.graphics.Rect;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -96,6 +96,7 @@
private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT;
+ private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -163,12 +164,13 @@
default void onRotationProposal(int rotation, boolean isValid) { }
- default void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver,
+ default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) { }
default void onBiometricAuthenticated() { }
default void onBiometricHelp(String message) { }
default void onBiometricError(String error) { }
default void hideBiometricDialog() { }
+ default void showBiometricTryAgain() { }
}
@VisibleForTesting
@@ -523,8 +525,8 @@
}
@Override
- public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
- boolean requireConfirmation, int userId) {
+ public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
+ int type, boolean requireConfirmation, int userId) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
@@ -565,6 +567,13 @@
}
}
+ @Override
+ public void showBiometricTryAgain() {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget();
+ }
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -774,7 +783,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showBiometricDialog(
(Bundle) someArgs.arg1,
- (IBiometricPromptReceiver) someArgs.arg2,
+ (IBiometricServiceReceiverInternal) someArgs.arg2,
someArgs.argi1 /* type */,
(boolean) someArgs.arg3 /* requireConfirmation */,
someArgs.argi2 /* userId */);
@@ -816,6 +825,11 @@
mCallbacks.get(i).showPinningEscapeToast();
}
break;
+ case MSG_BIOMETRIC_TRY_AGAIN:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showBiometricTryAgain();
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 713bd90..fef28cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -375,12 +375,9 @@
// Find the amount to translate up. This is needed in order to understand the
// direction of the remove animation (either downwards or upwards)
- ExpandableViewState viewState =
- ((ExpandableView) event.viewAfterChangingView).getViewState();
- int actualHeight = changingView.getActualHeight();
// upwards by default
float translationDirection = -1.0f;
- if (viewState != null) {
+ if (event.viewAfterChangingView != null) {
float ownPosition = changingView.getTranslationY();
if (changingView instanceof ExpandableNotificationRow
&& event.viewAfterChangingView instanceof ExpandableNotificationRow) {
@@ -396,8 +393,11 @@
ownPosition = changingRow.getTranslationWhenRemoved();
}
}
+ int actualHeight = changingView.getActualHeight();
// there was a view after this one, Approximate the distance the next child
// travelled
+ ExpandableViewState viewState =
+ ((ExpandableView) event.viewAfterChangingView).getViewState();
translationDirection = ((viewState.yTranslation
- (ownPosition + actualHeight / 2.0f)) * 2 /
actualHeight);
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 356a4da..8d912fa 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -692,7 +692,7 @@
}
});
- if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+ if (!StorageManager.hasIsolatedStorage()) {
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
storageManagerInternal.addExternalStoragePolicy(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0e6f8dd..e933bd0 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -183,8 +183,7 @@
private static final String ZRAM_ENABLED_PROPERTY =
"persist.sys.zram_enabled";
- private static final boolean ENABLE_ISOLATED_STORAGE = SystemProperties
- .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false);
+ private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage();
public static class Lifecycle extends SystemService {
private StorageManagerService mStorageManagerService;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 80f47d5..7f4675d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19346,7 +19346,7 @@
@Override
public boolean isAppStorageSandboxed(int pid, int uid) {
- if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+ if (!StorageManager.hasIsolatedStorage()) {
return false;
}
if (uid == SHELL_UID || uid == ROOT_UID) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index a0977be..8c39d75 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -58,8 +58,9 @@
/**
* BROADCASTS
*
- * We keep two broadcast queues and associated bookkeeping, one for those at
- * foreground priority, and one for normal (background-priority) broadcasts.
+ * We keep three broadcast queues and associated bookkeeping, one for those at
+ * foreground priority, and one for normal (background-priority) broadcasts, and one to
+ * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
*/
public final class BroadcastQueue {
private static final String TAG = "BroadcastQueue";
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4b19398..7991783 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -27,7 +27,6 @@
import static android.os.Process.getTotalMemory;
import static android.os.Process.killProcessQuiet;
import static android.os.Process.startWebView;
-import static android.os.storage.StorageManager.PROP_ISOLATED_STORAGE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
@@ -73,6 +72,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
import android.text.TextUtils;
import android.util.EventLog;
@@ -1281,8 +1281,7 @@
final IPackageManager pm = AppGlobals.getPackageManager();
permGids = pm.getPackageGids(app.info.packageName,
MATCH_DIRECT_BOOT_AUTO, app.userId);
- if (SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false)
- && mountExtStorageFull) {
+ if (StorageManager.hasIsolatedStorage() && mountExtStorageFull) {
mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
} else {
StorageManagerInternal storageManagerInternal = LocalServices.getService(
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index 2c2d404..eaa7a83 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -19,19 +19,11 @@
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.IBiometricPromptReceiver;
-import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.security.KeyStore;
-import android.text.TextUtils;
import android.util.Slog;
-import com.android.internal.statusbar.IStatusBarService;
-
import java.util.ArrayList;
/**
@@ -39,88 +31,15 @@
*/
public abstract class AuthenticationClient extends ClientMonitor {
private long mOpId;
- private Handler mHandler;
public abstract int handleFailedAttempt();
public abstract void resetFailedAttempts();
- public abstract String getErrorString(int error, int vendorCode);
- public abstract String getAcquiredString(int acquireInfo, int vendorCode);
- /**
- * @return one of {@link #TYPE_FINGERPRINT} {@link #TYPE_IRIS} or {@link #TYPE_FACE}
- */
- public abstract int getBiometricType();
public static final int LOCKOUT_NONE = 0;
public static final int LOCKOUT_TIMED = 1;
public static final int LOCKOUT_PERMANENT = 2;
private final boolean mRequireConfirmation;
- // Callback mechanism received from the client
- // (BiometricPrompt -> BiometricPromptService -> <Biometric>Service -> AuthenticationClient)
- private IBiometricPromptReceiver mDialogReceiverFromClient;
- private Bundle mBundle;
- private IStatusBarService mStatusBarService;
- private boolean mInLockout;
- private TokenEscrow mEscrow;
- protected boolean mDialogDismissed;
-
- /**
- * Container that holds the identifier and authToken. For biometrics that require user
- * confirmation, these should not be sent to their final destinations until the user confirms.
- */
- class TokenEscrow {
- final BiometricAuthenticator.Identifier mIdentifier;
- final ArrayList<Byte> mToken;
-
- TokenEscrow(BiometricAuthenticator.Identifier identifier, ArrayList<Byte> token) {
- mIdentifier = identifier;
- mToken = token;
- }
-
- BiometricAuthenticator.Identifier getIdentifier() {
- return mIdentifier;
- }
-
- ArrayList<Byte> getToken() {
- return mToken;
- }
- }
-
- // Receives events from SystemUI and handles them before forwarding them to BiometricDialog
- protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
- @Override // binder call
- public void onDialogDismissed(int reason) {
- if (mBundle != null && mDialogReceiverFromClient != null) {
- try {
- if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) {
- // Positive button is used by passive modalities as a "confirm" button,
- // do not send to client
- mDialogReceiverFromClient.onDialogDismissed(reason);
- }
- if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
- onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
- 0 /* vendorCode */);
- } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
- // Have the service send the token to KeyStore, and send onAuthenticated
- // to the application.
- if (mEscrow != null) {
- if (DEBUG) Slog.d(getLogTag(), "Confirmed");
- addTokenToKeyStore(mEscrow.getToken());
- notifyClientAuthenticationSucceeded(mEscrow.getIdentifier());
- mEscrow = null;
- onAuthenticationConfirmed();
- } else {
- Slog.e(getLogTag(), "Escrow is null!!!");
- }
- }
- mDialogDismissed = true;
- } catch (RemoteException e) {
- Slog.e(getLogTag(), "Remote exception", e);
- }
- stop(true /* initiatedByClient */);
- }
- }
- };
/**
* This method is called when authentication starts.
@@ -133,25 +52,13 @@
*/
public abstract void onStop();
- /**
- * This method is called when biometric authentication was confirmed by the user. The client
- * should be removed.
- */
- public abstract void onAuthenticationConfirmed();
-
public AuthenticationClient(Context context, Metrics metrics,
BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId,
- boolean restricted, String owner, Bundle bundle,
- IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService,
- boolean requireConfirmation) {
+ boolean restricted, String owner, int cookie, boolean requireConfirmation) {
super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId,
- restricted, owner);
+ restricted, owner, cookie);
mOpId = opId;
- mBundle = bundle;
- mDialogReceiverFromClient = dialogReceiver;
- mStatusBarService = statusBarService;
- mHandler = new Handler(Looper.getMainLooper());
mRequireConfirmation = requireConfirmation;
}
@@ -164,175 +71,99 @@
stop(false /* initiatedByClient */);
}
- @Override
- public boolean onAcquired(int acquiredInfo, int vendorCode) {
- // If the dialog is showing, the client doesn't need to receive onAcquired messages.
- if (mBundle != null) {
- try {
- if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
- mStatusBarService.onBiometricHelp(getAcquiredString(acquiredInfo, vendorCode));
- }
- return false; // acquisition continues
- } catch (RemoteException e) {
- Slog.e(getLogTag(), "Remote exception when sending acquired message", e);
- return true; // client failed
- } finally {
- // Good scans will keep the device awake
- if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
- notifyUserActivity();
- }
- }
- } else {
- return super.onAcquired(acquiredInfo, vendorCode);
- }
- }
-
- @Override
- public boolean onError(long deviceId, int error, int vendorCode) {
- if (mDialogDismissed) {
- // If user cancels authentication, the application has already received the
- // ERROR_USER_CANCELED message from onDialogDismissed()
- // and stopped the biometric hardware, so there is no need to send a
- // ERROR_CANCELED message.
- return true;
- }
- if (mBundle != null && error != BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED) {
- try {
- mStatusBarService.onBiometricError(getErrorString(error, vendorCode));
- } catch (RemoteException e) {
- Slog.e(getLogTag(), "Remote exception when sending error", e);
- }
- }
- return super.onError(deviceId, error, vendorCode);
- }
-
- public void setTitleIfEmpty(CharSequence title) {
- if (TextUtils.isEmpty(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE))) {
- mBundle.putCharSequence(BiometricPrompt.KEY_TITLE, title);
- }
- }
-
public boolean isBiometricPrompt() {
- return mBundle != null;
+ return getCookie() != 0;
}
- private void notifyClientAuthenticationSucceeded(BiometricAuthenticator.Identifier identifier)
- throws RemoteException {
- final BiometricServiceBase.ServiceListener listener = getListener();
- // Explicitly have if/else here to make it super obvious in case the code is
- // touched in the future.
- if (!getIsRestricted()) {
- listener.onAuthenticationSucceeded(
- getHalDeviceId(), identifier, getTargetUserId());
- } else {
- listener.onAuthenticationSucceeded(
- getHalDeviceId(), null, getTargetUserId());
- }
- }
-
- private void addTokenToKeyStore(ArrayList<Byte> token) {
- // Send the token to KeyStore
- final byte[] byteToken = new byte[token.size()];
- for (int i = 0; i < token.size(); i++) {
- byteToken[i] = token.get(i);
- }
- KeyStore.getInstance().addAuthToken(byteToken);
+ public boolean getRequireConfirmation() {
+ return mRequireConfirmation;
}
@Override
public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
boolean authenticated, ArrayList<Byte> token) {
- if (authenticated) {
- mAlreadyDone = true;
- if (mRequireConfirmation) {
- // Store the token so it can be sent to keystore after the user presses confirm
- mEscrow = new TokenEscrow(identifier, token);
- } else {
- addTokenToKeyStore(token);
- }
- }
+ final BiometricServiceBase.ServiceListener listener = getListener();
+ mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
boolean result = false;
- // If the biometric dialog is showing, notify authentication succeeded
- if (mBundle != null) {
- try {
- if (authenticated) {
- mStatusBarService.onBiometricAuthenticated();
- } else {
- mStatusBarService.onBiometricHelp(getContext().getResources().getString(
- com.android.internal.R.string.biometric_not_recognized));
+ try {
+ if (authenticated) {
+ mAlreadyDone = true;
+ if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + getOwnerString()
+ + ", ID:" + identifier.getBiometricId()
+ + ", isBP: " + isBiometricPrompt()
+ + ", listener: " + listener
+ + ", requireConfirmation: " + mRequireConfirmation);
+ if (listener != null) {
+ vibrateSuccess();
}
- } catch (RemoteException e) {
- Slog.e(getLogTag(), "Failed to notify Authenticated:", e);
- }
- }
+ result = true;
+ resetFailedAttempts();
+ onStop();
- final BiometricServiceBase.ServiceListener listener = getListener();
- if (listener != null) {
- try {
- mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
- if (!authenticated) {
- listener.onAuthenticationFailed(getHalDeviceId());
- } else {
- if (DEBUG) {
- Slog.v(getLogTag(), "onAuthenticated(owner=" + getOwnerString()
- + ", id=" + identifier.getBiometricId());
- }
- if (!mRequireConfirmation) {
- notifyClientAuthenticationSucceeded(identifier);
- }
+ final byte[] byteToken = new byte[token.size()];
+ for (int i = 0; i < token.size(); i++) {
+ byteToken[i] = token.get(i);
}
- } catch (RemoteException e) {
- Slog.w(getLogTag(), "Failed to notify Authenticated:", e);
- result = true; // client failed
- }
- } else {
- result = true; // client not listening
- }
- if (!authenticated) {
- if (listener != null) {
- vibrateError();
- }
- // allow system-defined limit of number of attempts before giving up
- int lockoutMode = handleFailedAttempt();
- if (lockoutMode != LOCKOUT_NONE) {
- try {
- mInLockout = true;
- Slog.w(getLogTag(), "Forcing lockout (fp driver code should do this!), mode(" +
- lockoutMode + ")");
+ if (isBiometricPrompt() && listener != null) {
+ // BiometricService will add the token to keystore
+ listener.onAuthenticationSucceededInternal(mRequireConfirmation, byteToken);
+ } else if (!isBiometricPrompt() && listener != null) {
+ KeyStore.getInstance().addAuthToken(byteToken);
+ try {
+ // Explicitly have if/else here to make it super obvious in case the code is
+ // touched in the future.
+ if (!getIsRestricted()) {
+ listener.onAuthenticationSucceeded(
+ getHalDeviceId(), identifier, getTargetUserId());
+ } else {
+ listener.onAuthenticationSucceeded(
+ getHalDeviceId(), null, getTargetUserId());
+ }
+ } catch (RemoteException e) {
+ Slog.e(getLogTag(), "Remote exception", e);
+ }
+ } else {
+ // Client not listening
+ Slog.w(getLogTag(), "Client not listening");
+ result = true;
+ }
+ } else {
+ if (listener != null) {
+ vibrateError();
+ }
+ // Allow system-defined limit of number of attempts before giving up
+ final int lockoutMode = handleFailedAttempt();
+ if (lockoutMode != LOCKOUT_NONE) {
+ Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode("
+ + lockoutMode + ")");
stop(false);
- int errorCode = lockoutMode == LOCKOUT_TIMED ?
- BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
- BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-
- // Send the lockout message to the system dialog
- if (mBundle != null) {
- mStatusBarService.onBiometricError(
- getErrorString(errorCode, 0 /* vendorCode */));
- mHandler.postDelayed(() -> {
- try {
- listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
- } catch (RemoteException e) {
- Slog.w(getLogTag(), "RemoteException while sending error");
- }
- }, BiometricPrompt.HIDE_DIALOG_DELAY);
- } else {
- listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
+ final int errorCode = lockoutMode == LOCKOUT_TIMED
+ ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+ : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+ if (listener != null) {
+ listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */,
+ getCookie());
}
- } catch (RemoteException e) {
- Slog.w(getLogTag(), "Failed to notify lockout:", e);
+ } else {
+ // Don't send onAuthenticationFailed if we're in lockout, it causes a
+ // janky UI on Keyguard/BiometricPrompt since "authentication failed"
+ // will show briefly and be replaced by "device locked out" message.
+ if (listener != null) {
+ if (isBiometricPrompt()) {
+ listener.onAuthenticationFailedInternal(getCookie(),
+ getRequireConfirmation());
+ } else {
+ listener.onAuthenticationFailed(getHalDeviceId());
+ }
+ }
}
+ result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
}
- result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
- } else {
- if (listener != null) {
- vibrateSuccess();
- }
- // we have a valid biometric that doesn't require confirmation, done
- result |= !mRequireConfirmation;
- resetFailedAttempts();
- onStop();
+ } catch (RemoteException e) {
+ Slog.e(getLogTag(), "Remote exception", e);
+ result = true;
}
return result;
}
@@ -353,16 +184,6 @@
return result;
}
if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
-
- // If authenticating with system dialog, show the dialog
- if (mBundle != null) {
- try {
- mStatusBarService.showBiometricDialog(mBundle, mDialogReceiver,
- getBiometricType(), mRequireConfirmation, getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(getLogTag(), "Unable to show biometric dialog", e);
- }
- }
} catch (RemoteException e) {
Slog.e(getLogTag(), "startAuthentication failed", e);
return ERROR_ESRCH;
@@ -390,18 +211,6 @@
} catch (RemoteException e) {
Slog.e(getLogTag(), "stopAuthentication failed", e);
return ERROR_ESRCH;
- } finally {
- // If the user already cancelled authentication (via some interaction with the
- // dialog, we do not need to hide it since it's already hidden.
- // If the device is in lockout, don't hide the dialog - it will automatically hide
- // after BiometricPrompt.HIDE_DIALOG_DELAY
- if (mBundle != null && !mDialogDismissed && !mInLockout) {
- try {
- mStatusBarService.hideBiometricDialog();
- } catch (RemoteException e) {
- Slog.e(getLogTag(), "Unable to hide biometric dialog", e);
- }
- }
}
mAlreadyCancelled = true;
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 5f09189..add55ea 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -19,9 +19,17 @@
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
+import android.app.IActivityTaskManager;
+import android.app.TaskStackListener;
import android.app.UserSwitchObserver;
import android.content.ContentResolver;
import android.content.Context;
@@ -32,9 +40,9 @@
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
-import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.face.FaceManager;
import android.hardware.face.IFaceService;
import android.hardware.fingerprint.FingerprintManager;
@@ -50,14 +58,21 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
+import android.security.KeyStore;
+import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.server.SystemService;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Random;
/**
* System service that arbitrates the modality for BiometricPrompt to use.
@@ -66,32 +81,10 @@
private static final String TAG = "BiometricService";
- /**
- * No biometric methods or nothing has been enrolled.
- * Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist"
- * modalities when calling authenticate().
- */
- private static final int BIOMETRIC_NONE = 0;
-
- /**
- * Constant representing fingerprint.
- */
- private static final int BIOMETRIC_FINGERPRINT = 1 << 0;
-
- /**
- * Constant representing iris.
- */
- private static final int BIOMETRIC_IRIS = 1 << 1;
-
- /**
- * Constant representing face.
- */
- private static final int BIOMETRIC_FACE = 1 << 2;
-
private static final int[] FEATURE_ID = {
- BIOMETRIC_FINGERPRINT,
- BIOMETRIC_IRIS,
- BIOMETRIC_FACE
+ TYPE_FINGERPRINT,
+ TYPE_IRIS,
+ TYPE_FACE
};
private final AppOpsManager mAppOps;
@@ -242,10 +235,367 @@
*/
private final class BiometricServiceWrapper extends IBiometricService.Stub {
+ /**
+ * Authentication either just called and we have not transitioned to the CALLED state, or
+ * authentication terminated (success or error).
+ */
+ private static final int STATE_AUTH_IDLE = 0;
+ /**
+ * Authentication was called and we are waiting for the <Biometric>Services to return their
+ * cookies before starting the hardware and showing the BiometricPrompt.
+ */
+ private static final int STATE_AUTH_CALLED = 1;
+ /**
+ * Authentication started, BiometricPrompt is showing and the hardware is authenticating.
+ */
+ private static final int STATE_AUTH_STARTED = 2;
+ /**
+ * Authentication is paused, waiting for the user to press "try again" button. Since the
+ * try again button requires us to cancel authentication, this represents the state where
+ * ERROR_CANCELED is not received yet.
+ */
+ private static final int STATE_AUTH_PAUSED = 3;
+ /**
+ * Same as above, except the ERROR_CANCELED has been received.
+ */
+ private static final int STATE_AUTH_PAUSED_CANCELED = 4;
+ /**
+ * Authentication is successful, but we're waiting for the user to press "confirm" button.
+ */
+ private static final int STATE_AUTH_PENDING_CONFIRM = 5;
+
+ final class AuthSession {
+ // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
+ // <Biometric>Services before we can start authenticating. Pairs that have been returned
+ // are moved to mModalitiesMatched.
+ final HashMap<Integer, Integer> mModalitiesWaiting;
+ // Pairs that have been matched.
+ final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>();
+
+ // The following variables are passed to authenticateInternal, which initiates the
+ // appropriate <Biometric>Services.
+ final IBinder mToken;
+ final long mSessionId;
+ final int mUserId;
+ // Original receiver from BiometricPrompt.
+ final IBiometricServiceReceiver mClientReceiver;
+ final String mOpPackageName;
+ // Info to be shown on BiometricDialog when all cookies are returned.
+ final Bundle mBundle;
+ final int mCallingUid;
+ final int mCallingPid;
+ final int mCallingUserId;
+ // Continue authentication with the same modality/modalities after "try again" is
+ // pressed
+ final int mModality;
+
+ // The current state, which can be either idle, called, or started
+ private int mState = STATE_AUTH_IDLE;
+ // For explicit confirmation, do not send to keystore until the user has confirmed
+ // the authentication.
+ byte[] mTokenEscrow;
+
+ AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId,
+ int userId, IBiometricServiceReceiver receiver, String opPackageName,
+ Bundle bundle, int callingUid, int callingPid, int callingUserId,
+ int modality) {
+ mModalitiesWaiting = modalities;
+ mToken = token;
+ mSessionId = sessionId;
+ mUserId = userId;
+ mClientReceiver = receiver;
+ mOpPackageName = opPackageName;
+ mBundle = bundle;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ mCallingUserId = callingUserId;
+ mModality = modality;
+ }
+
+ boolean containsCookie(int cookie) {
+ if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) {
+ return true;
+ }
+ if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ final class BiometricTaskStackListener extends TaskStackListener {
+ @Override
+ public void onTaskStackChanged() {
+ try {
+ final List<ActivityManager.RunningTaskInfo> runningTasks =
+ mActivityTaskManager.getTasks(1);
+ if (!runningTasks.isEmpty()) {
+ final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+ if (mCurrentAuthSession != null
+ && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName)
+ && mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+ // We only care about this state, since <Biometric>Service will
+ // cancel any client that's still in STATE_AUTH_STARTED
+ mStatusBarService.hideBiometricDialog();
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ mCurrentAuthSession.mClientReceiver.onError(
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ getContext().getString(
+ com.android.internal.R.string.biometric_error_canceled)
+ );
+ mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+ mCurrentAuthSession = null;
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to get running tasks", e);
+ }
+ }
+ }
+
+ private final IActivityTaskManager mActivityTaskManager = getContext().getSystemService(
+ ActivityTaskManager.class).getService();
+ private final IStatusBarService mStatusBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ private final BiometricTaskStackListener mTaskStackListener =
+ new BiometricTaskStackListener();
+ private final Random mRandom = new Random();
+
+ // The current authentication session, null if idle/done. We need to track both the current
+ // and pending sessions since errors may be sent to either.
+ private AuthSession mCurrentAuthSession;
+ private AuthSession mPendingAuthSession;
+
+ // Wrap the client's receiver so we can do things with the BiometricDialog first
+ private final IBiometricServiceReceiverInternal mInternalReceiver =
+ new IBiometricServiceReceiverInternal.Stub() {
+ @Override
+ public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token)
+ throws RemoteException {
+ try {
+ if (!requireConfirmation) {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ KeyStore.getInstance().addAuthToken(token);
+ mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+ mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+ mCurrentAuthSession = null;
+ } else {
+ // Store the auth token and submit it to keystore after the confirmation
+ // button has been pressed.
+ mCurrentAuthSession.mTokenEscrow = token;
+ mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM;
+ }
+
+ // Notify SysUI that the biometric has been authenticated. SysUI already knows
+ // the implicit/explicit state and will react accordingly.
+ mStatusBarService.onBiometricAuthenticated();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
+ @Override
+ public void onAuthenticationFailed(int cookie, boolean requireConfirmation)
+ throws RemoteException {
+ try {
+ mStatusBarService.onBiometricHelp(getContext().getResources().getString(
+ com.android.internal.R.string.biometric_not_recognized));
+ if (requireConfirmation) {
+ mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
+ mStatusBarService.showBiometricTryAgain();
+ // Cancel authentication. Skip the token/package check since we are
+ // cancelling from system server. The interface is permission protected so
+ // this is fine.
+ cancelInternal(null /* token */, null /* package */,
+ false /* fromClient */);
+ }
+ mCurrentAuthSession.mClientReceiver.onAuthenticationFailed();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
+ @Override
+ public void onError(int cookie, int error, String message) throws RemoteException {
+ Slog.d(TAG, "Error: " + error + " cookie: " + cookie);
+ // Errors can either be from the current auth session or the pending auth session.
+ // The pending auth session may receive errors such as ERROR_LOCKOUT before
+ // it becomes the current auth session. Similarly, the current auth session may
+ // receive errors such as ERROR_CANCELED while the pending auth session is preparing
+ // to be started. Thus we must match error messages with their cookies to be sure
+ // of their intended receivers.
+ try {
+ if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
+ if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
+ mStatusBarService.onBiometricError(message);
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
+ mCurrentAuthSession.mClientReceiver.onError(error, message);
+ mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+ mCurrentAuthSession = null;
+ mStatusBarService.hideBiometricDialog();
+ } else {
+ // Send errors after the dialog is dismissed.
+ mHandler.postDelayed(() -> {
+ try {
+ mCurrentAuthSession.mClientReceiver.onError(error, message);
+ mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+ mCurrentAuthSession = null;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }, BiometricPrompt.HIDE_DIALOG_DELAY);
+ }
+ } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED
+ || mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED) {
+ if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED
+ && error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
+ // Skip the first ERROR_CANCELED message when this happens, since
+ // "try again" requires us to cancel authentication but keep
+ // the prompt showing.
+ mCurrentAuthSession.mState = STATE_AUTH_PAUSED_CANCELED;
+ } else {
+ // In the "try again" state, we should forward canceled errors to
+ // the client and and clean up.
+ mCurrentAuthSession.mClientReceiver.onError(error, message);
+ mStatusBarService.onBiometricError(message);
+ mActivityTaskManager.unregisterTaskStackListener(
+ mTaskStackListener);
+ mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+ mCurrentAuthSession = null;
+ }
+ } else {
+ Slog.e(TAG, "Impossible session error state: "
+ + mCurrentAuthSession.mState);
+ }
+ } else if (mPendingAuthSession != null
+ && mPendingAuthSession.containsCookie(cookie)) {
+ if (mPendingAuthSession.mState == STATE_AUTH_CALLED) {
+ mPendingAuthSession.mClientReceiver.onError(error, message);
+ mPendingAuthSession.mState = STATE_AUTH_IDLE;
+ mPendingAuthSession = null;
+ } else {
+ Slog.e(TAG, "Impossible pending session error state: "
+ + mPendingAuthSession.mState);
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
+ @Override
+ public void onAcquired(int acquiredInfo, String message) throws RemoteException {
+ if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
+ try {
+ mStatusBarService.onBiometricHelp(message);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+ }
+
+ @Override
+ public void onDialogDismissed(int reason) throws RemoteException {
+ if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) {
+ // Positive button is used by passive modalities as a "confirm" button,
+ // do not send to client
+ mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason);
+ // Cancel authentication. Skip the token/package check since we are cancelling
+ // from system server. The interface is permission protected so this is fine.
+ cancelInternal(null /* token */, null /* package */, false /* fromClient */);
+ }
+ if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
+ mCurrentAuthSession.mClientReceiver.onError(
+ BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
+ getContext().getString(
+ com.android.internal.R.string.biometric_error_user_canceled));
+ } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
+ // Have the service send the token to KeyStore, and send onAuthenticated
+ // to the application
+ KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow);
+ mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+ }
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+ mCurrentAuthSession = null;
+ }
+
+ @Override
+ public void onTryAgainPressed() {
+ Slog.d(TAG, "onTryAgainPressed");
+ // No need to check permission, since it can only be invoked by SystemUI
+ // (or system server itself).
+ mHandler.post(() -> {
+ authenticateInternal(mCurrentAuthSession.mToken,
+ mCurrentAuthSession.mSessionId,
+ mCurrentAuthSession.mUserId,
+ mCurrentAuthSession.mClientReceiver,
+ mCurrentAuthSession.mOpPackageName,
+ mCurrentAuthSession.mBundle,
+ mCurrentAuthSession.mCallingUid,
+ mCurrentAuthSession.mCallingPid,
+ mCurrentAuthSession.mCallingUserId,
+ mCurrentAuthSession.mModality);
+ });
+ }
+ };
+
+ @Override // Binder call
+ public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) {
+ checkInternalPermission();
+
+ Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
+ if (pair.getValue() == cookie) {
+ mPendingAuthSession.mModalitiesMatched.put(pair.getKey(), pair.getValue());
+ mPendingAuthSession.mModalitiesWaiting.remove(pair.getKey());
+ Slog.d(TAG, "Matched cookie: " + cookie + ", "
+ + mPendingAuthSession.mModalitiesWaiting.size() + " remaining");
+ break;
+ }
+ }
+
+ if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) {
+ final boolean mContinuing = mCurrentAuthSession != null
+ && mCurrentAuthSession.mState == STATE_AUTH_PAUSED;
+ mCurrentAuthSession = mPendingAuthSession;
+ mPendingAuthSession = null;
+
+ mCurrentAuthSession.mState = STATE_AUTH_STARTED;
+ try {
+ int modality = TYPE_NONE;
+ it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
+ if (pair.getKey() == TYPE_FINGERPRINT) {
+ mFingerprintService.startPreparedClient(pair.getValue());
+ } else if (pair.getKey() == TYPE_IRIS) {
+ Slog.e(TAG, "Iris unsupported");
+ } else if (pair.getKey() == TYPE_FACE) {
+ mFaceService.startPreparedClient(pair.getValue());
+ } else {
+ Slog.e(TAG, "Unknown modality: " + pair.getKey());
+ }
+ modality |= pair.getKey();
+ }
+
+ if (!mContinuing) {
+ mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
+ mInternalReceiver, modality, requireConfirmation, userId);
+ mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+ }
+
@Override // Binder call
public void authenticate(IBinder token, long sessionId, int userId,
- IBiometricServiceReceiver receiver, int flags, String opPackageName,
- Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException {
+ IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle)
+ throws RemoteException {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
@@ -261,16 +611,45 @@
checkInternalPermission();
}
- if (token == null || receiver == null || opPackageName == null || bundle == null
- || dialogReceiver == null) {
+ if (token == null || receiver == null || opPackageName == null || bundle == null) {
Slog.e(TAG, "Unable to authenticate, one or more null arguments");
return;
}
// Check the usage of this in system server. Need to remove this check if it becomes
// a public API.
- if (bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) {
+ final boolean useDefaultTitle =
+ bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false);
+ if (useDefaultTitle) {
checkInternalPermission();
+ // Set the default title if necessary
+ try {
+ if (useDefaultTitle) {
+ final List<ActivityManager.RunningAppProcessInfo> procs =
+ ActivityManager.getService().getRunningAppProcesses();
+ for (int i = 0; i < procs.size(); i++) {
+ final ActivityManager.RunningAppProcessInfo info = procs.get(i);
+ if (info.uid == callingUid
+ && info.importance == IMPORTANCE_FOREGROUND) {
+ PackageManager pm = getContext().getPackageManager();
+ final CharSequence label = pm.getApplicationLabel(
+ pm.getApplicationInfo(info.processName,
+ PackageManager.GET_META_DATA));
+ final String title = getContext()
+ .getString(R.string.biometric_dialog_default_title, label);
+ if (TextUtils.isEmpty(
+ bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) {
+ bundle.putCharSequence(BiometricPrompt.KEY_TITLE, title);
+ }
+ break;
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Name not found", e);
+ }
}
mHandler.post(() -> {
@@ -285,13 +664,13 @@
getContext().getString(R.string.biometric_error_hw_unavailable);
switch (error) {
case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
- receiver.onError(0 /* deviceId */, error, hardwareUnavailable);
+ receiver.onError(error, hardwareUnavailable);
break;
case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
- receiver.onError(0 /* deviceId */, error, hardwareUnavailable);
+ receiver.onError(error, hardwareUnavailable);
break;
case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
- receiver.onError(0 /* deviceId */, error,
+ receiver.onError(error,
getErrorString(modality, error, 0 /* vendorCode */));
break;
default:
@@ -304,60 +683,91 @@
return;
}
- // Actually start authentication
mCurrentModality = modality;
- try {
- // No polymorphism :(
- if (mCurrentModality == BIOMETRIC_FINGERPRINT) {
- mFingerprintService.authenticateFromService(token, sessionId, userId,
- receiver, flags, opPackageName, bundle, dialogReceiver,
- callingUid, callingPid, callingUserId);
- } else if (mCurrentModality == BIOMETRIC_IRIS) {
- Slog.w(TAG, "Unsupported modality");
- } else if (mCurrentModality == BIOMETRIC_FACE) {
- mFaceService.authenticateFromService(true /* requireConfirmation */,
- token, sessionId, userId, receiver, flags, opPackageName,
- bundle, dialogReceiver, callingUid, callingPid, callingUserId);
- } else {
- Slog.w(TAG, "Unsupported modality");
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to start authentication", e);
- }
+
+ // Actually start authentication
+ authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
+ callingUid, callingPid, callingUserId, modality);
});
}
+ /**
+ * authenticate() (above) which is called from BiometricPrompt determines which
+ * modality/modalities to start authenticating with. authenticateInternal() should only be
+ * used for:
+ * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is,
+ * invoked, shortly after which BiometricPrompt is shown and authentication starts
+ * 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown
+ * and the user has pressed "try again"
+ */
+ private void authenticateInternal(IBinder token, long sessionId, int userId,
+ IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
+ int callingUid, int callingPid, int callingUserId, int modality) {
+ try {
+ // Generate random cookies to pass to the services that should prepare to start
+ // authenticating. Store the cookie here and wait for all services to "ack"
+ // with the cookie. Once all cookies are received, we can show the prompt
+ // and let the services start authenticating. The cookie should be non-zero.
+ final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+ Slog.d(TAG, "Creating auth session. Modality: " + modality
+ + ", cookie: " + cookie);
+ final HashMap<Integer, Integer> authenticators = new HashMap<>();
+ authenticators.put(modality, cookie);
+ mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId,
+ receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
+ modality);
+ mPendingAuthSession.mState = STATE_AUTH_CALLED;
+ // No polymorphism :(
+ if ((modality & TYPE_FINGERPRINT) != 0) {
+ mFingerprintService.prepareForAuthentication(token, sessionId, userId,
+ mInternalReceiver, opPackageName, cookie,
+ callingUid, callingPid, callingUserId);
+ }
+ if ((modality & TYPE_IRIS) != 0) {
+ Slog.w(TAG, "Iris unsupported");
+ }
+ if ((modality & TYPE_FACE) != 0) {
+ mFaceService.prepareForAuthentication(true /* requireConfirmation */,
+ token, sessionId, userId, mInternalReceiver, opPackageName,
+ cookie, callingUid, callingPid, callingUserId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to start authentication", e);
+ }
+ }
+
@Override // Binder call
public void cancelAuthentication(IBinder token, String opPackageName)
throws RemoteException {
checkPermission();
-
if (token == null || opPackageName == null) {
Slog.e(TAG, "Unable to cancel, one or more null arguments");
return;
}
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final int callingUserId = UserHandle.getCallingUserId();
+ // We need to check the current authenticators state. If we're pending confirm
+ // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client,
+ // since we won't be getting an onError from the driver.
+ if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+ mHandler.post(() -> {
+ try {
+ // Send error to client
+ mCurrentAuthSession.mClientReceiver.onError(
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ getContext().getString(
+ com.android.internal.R.string.biometric_error_user_canceled)
+ );
- mHandler.post(() -> {
- try {
- if (mCurrentModality == BIOMETRIC_FINGERPRINT) {
- mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
- callingUid, callingPid, callingUserId);
- } else if (mCurrentModality == BIOMETRIC_IRIS) {
- Slog.w(TAG, "Unsupported modality");
- } else if (mCurrentModality == BIOMETRIC_FACE) {
- mFaceService.cancelAuthenticationFromService(token, opPackageName,
- callingUid, callingPid, callingUserId);
- } else {
- Slog.w(TAG, "Unsupported modality");
+ mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+ mCurrentAuthSession = null;
+ mStatusBarService.hideBiometricDialog();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to cancel authentication");
- }
- });
+ });
+ } else {
+ cancelInternal(token, opPackageName, true /* fromClient */);
+ }
}
@Override // Binder call
@@ -402,6 +812,31 @@
Binder.restoreCallingIdentity(ident);
}
}
+
+ void cancelInternal(IBinder token, String opPackageName, boolean fromClient) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int callingUserId = UserHandle.getCallingUserId();
+ mHandler.post(() -> {
+ try {
+ // TODO: For multiple modalities, send a single ERROR_CANCELED only when all
+ // drivers have canceled authentication.
+ if ((mCurrentModality & TYPE_FINGERPRINT) != 0) {
+ mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
+ callingUid, callingPid, callingUserId, fromClient);
+ }
+ if ((mCurrentModality & TYPE_IRIS) != 0) {
+ Slog.w(TAG, "Iris unsupported");
+ }
+ if ((mCurrentModality & TYPE_FACE) != 0) {
+ mFaceService.cancelAuthenticationFromService(token, opPackageName,
+ callingUid, callingPid, callingUserId, fromClient);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to cancel authentication");
+ }
+ });
+ }
}
private void checkAppOp(String opPackageName, int callingUid) {
@@ -413,7 +848,7 @@
}
private void checkInternalPermission() {
- getContext().enforceCallingPermission(USE_BIOMETRIC_INTERNAL,
+ getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL,
"Must have USE_BIOMETRIC_INTERNAL permission");
}
@@ -490,16 +925,19 @@
* returns errors through the callback (no biometric feature, hardware not detected, no
* templates enrolled, etc). This service must not start authentication if errors are sent.
*
- * @Returns A pair [Modality, Error] with Modality being one of {@link #BIOMETRIC_NONE},
- * {@link #BIOMETRIC_FINGERPRINT}, {@link #BIOMETRIC_IRIS}, {@link #BIOMETRIC_FACE}
+ * @Returns A pair [Modality, Error] with Modality being one of
+ * {@link BiometricAuthenticator#TYPE_NONE},
+ * {@link BiometricAuthenticator#TYPE_FINGERPRINT},
+ * {@link BiometricAuthenticator#TYPE_IRIS},
+ * {@link BiometricAuthenticator#TYPE_FACE}
* and the error containing one of the {@link BiometricConstants} errors.
*/
private Pair<Integer, Integer> checkAndGetBiometricModality(int callingUid) {
- int modality = BIOMETRIC_NONE;
+ int modality = TYPE_NONE;
// No biometric features, send error
if (mAuthenticators.isEmpty()) {
- return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
+ return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
}
// Assuming that authenticators are listed in priority-order, the rest of this function
@@ -512,13 +950,13 @@
boolean hasTemplatesEnrolled = false;
boolean enabledForApps = false;
- int firstHwAvailable = BIOMETRIC_NONE;
+ int firstHwAvailable = TYPE_NONE;
for (int i = 0; i < mAuthenticators.size(); i++) {
modality = mAuthenticators.get(i).getType();
BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator();
if (authenticator.isHardwareDetected()) {
isHardwareDetected = true;
- if (firstHwAvailable == BIOMETRIC_NONE) {
+ if (firstHwAvailable == TYPE_NONE) {
// Store the first one since we want to return the error in correct priority
// order.
firstHwAvailable = modality;
@@ -538,13 +976,13 @@
// Check error conditions
if (!isHardwareDetected) {
- return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
} else if (!hasTemplatesEnrolled) {
// Return the modality here so the correct error string can be sent. This error is
// preferred over !enabledForApps
return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
} else if (!enabledForApps) {
- return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
}
return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
@@ -552,11 +990,11 @@
private boolean isEnabledForApp(int modality) {
switch(modality) {
- case BIOMETRIC_FINGERPRINT:
+ case TYPE_FINGERPRINT:
return true;
- case BIOMETRIC_IRIS:
+ case TYPE_IRIS:
return true;
- case BIOMETRIC_FACE:
+ case TYPE_FACE:
return mSettingObserver.getFaceEnabledForApps();
default:
Slog.w(TAG, "Unsupported modality: " + modality);
@@ -566,12 +1004,12 @@
private String getErrorString(int type, int error, int vendorCode) {
switch (type) {
- case BIOMETRIC_FINGERPRINT:
+ case TYPE_FINGERPRINT:
return FingerprintManager.getErrorString(getContext(), error, vendorCode);
- case BIOMETRIC_IRIS:
+ case TYPE_IRIS:
Slog.w(TAG, "Modality not supported");
return null; // not supported
- case BIOMETRIC_FACE:
+ case TYPE_FACE:
return FaceManager.getErrorString(getContext(), error, vendorCode);
default:
Slog.w(TAG, "Unable to get error string for modality: " + type);
@@ -581,12 +1019,12 @@
private BiometricAuthenticator getAuthenticator(int type) {
switch (type) {
- case BIOMETRIC_FINGERPRINT:
+ case TYPE_FINGERPRINT:
return (FingerprintManager)
getContext().getSystemService(Context.FINGERPRINT_SERVICE);
- case BIOMETRIC_IRIS:
+ case TYPE_IRIS:
return null;
- case BIOMETRIC_FACE:
+ case TYPE_FACE:
return (FaceManager)
getContext().getSystemService(Context.FACE_SERVICE);
default:
@@ -596,11 +1034,11 @@
private boolean hasFeature(int type) {
switch (type) {
- case BIOMETRIC_FINGERPRINT:
+ case TYPE_FINGERPRINT:
return mHasFeatureFingerprint;
- case BIOMETRIC_IRIS:
+ case TYPE_IRIS:
return mHasFeatureIris;
- case BIOMETRIC_FACE:
+ case TYPE_FACE:
return mHasFeatureFace;
default:
return false;
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 74d742a..9649ccd 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -16,7 +16,6 @@
package com.android.server.biometrics;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import android.app.ActivityManager;
@@ -36,8 +35,9 @@
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.fingerprint.Fingerprint;
import android.os.Binder;
import android.os.Bundle;
@@ -56,7 +56,6 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.SystemService;
@@ -106,6 +105,7 @@
protected final AppOpsManager mAppOps;
protected final H mHandler = new H();
+ private IBiometricService mBiometricService;
private ClientMonitor mCurrentClient;
private ClientMonitor mPendingClient;
private PerformanceStats mPerformanceStats;
@@ -223,12 +223,9 @@
public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId,
- boolean restricted, String owner, Bundle bundle,
- IBiometricPromptReceiver dialogReceiver,
- IStatusBarService statusBarService, boolean requireConfirmation) {
- super(context, getMetrics(), daemon, halDeviceId, token, listener,
- targetUserId, groupId, opId, restricted, owner, bundle, dialogReceiver,
- statusBarService, requireConfirmation);
+ boolean restricted, String owner, int cookie, boolean requireConfirmation) {
+ super(context, getMetrics(), daemon, halDeviceId, token, listener, targetUserId,
+ groupId, opId, restricted, owner, cookie, requireConfirmation);
}
@Override
@@ -279,11 +276,6 @@
}
return AuthenticationClient.LOCKOUT_NONE;
}
-
- @Override
- public void onAuthenticationConfirmed() {
- removeClient(mCurrentClient);
- }
}
protected class EnrollClientImpl extends EnrollClient {
@@ -345,18 +337,28 @@
default void onEnrollResult(BiometricAuthenticator.Identifier identifier,
int remaining) throws RemoteException {};
- void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
- throws RemoteException;
+ void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException;
- void onAuthenticationSucceeded(long deviceId,
- BiometricAuthenticator.Identifier biometric, int userId)
- throws RemoteException;
+ default void onAuthenticationSucceeded(long deviceId,
+ BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
+ throw new UnsupportedOperationException("Stub!");
+ }
- void onAuthenticationFailed(long deviceId)
- throws RemoteException;
+ default void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
+ throws RemoteException {
+ throw new UnsupportedOperationException("Stub!");
+ }
- void onError(long deviceId, int error, int vendorCode)
- throws RemoteException;
+ default void onAuthenticationFailed(long deviceId) throws RemoteException {
+ throw new UnsupportedOperationException("Stub!");
+ }
+
+ default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
+ throws RemoteException {
+ throw new UnsupportedOperationException("Stub!");
+ }
+
+ void onError(long deviceId, int error, int vendorCode, int cookie) throws RemoteException;
default void onRemoved(BiometricAuthenticator.Identifier identifier,
int remaining) throws RemoteException {};
@@ -366,6 +368,37 @@
}
/**
+ * Wraps the callback interface from Service -> BiometricPrompt
+ */
+ protected abstract class BiometricServiceListener implements ServiceListener {
+ private IBiometricServiceReceiverInternal mWrapperReceiver;
+
+ public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) {
+ mWrapperReceiver = wrapperReceiver;
+ }
+
+ public IBiometricServiceReceiverInternal getWrapperReceiver() {
+ return mWrapperReceiver;
+ }
+
+ @Override
+ public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
+ throws RemoteException {
+ if (getWrapperReceiver() != null) {
+ getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token);
+ }
+ }
+
+ @Override
+ public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
+ throws RemoteException {
+ if (getWrapperReceiver() != null) {
+ getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation);
+ }
+ }
+ }
+
+ /**
* Wraps a portion of the interface from Service -> Daemon that is used by the ClientMonitor
* subclasses.
*/
@@ -706,30 +739,6 @@
}
mHandler.post(() -> {
- if (client.isBiometricPrompt()) {
- try {
- final List<ActivityManager.RunningAppProcessInfo> procs =
- ActivityManager.getService().getRunningAppProcesses();
- for (int i = 0; i < procs.size(); i++) {
- final ActivityManager.RunningAppProcessInfo info = procs.get(i);
- if (info.uid == callingUid && info.importance == IMPORTANCE_FOREGROUND) {
- PackageManager pm = getContext().getPackageManager();
- final CharSequence label = pm.getApplicationLabel(
- pm.getApplicationInfo(info.processName,
- PackageManager.GET_META_DATA));
- final String title = getContext()
- .getString(R.string.biometric_dialog_default_title, label);
- client.setTitleIfEmpty(title);
- break;
- }
- }
- } catch (RemoteException e) {
- Slog.e(getTag(), "Unable to get application name", e);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(getTag(), "Unable to get application name", e);
- }
- }
-
mMetricsLogger.histogram(getMetrics().tagAuthToken(), opId != 0L ? 1 : 0);
// Get performance stats object for this user.
@@ -751,29 +760,37 @@
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
- cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId);
+ cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId,
+ true /* fromClient */);
}
protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName,
- int callingUid, int callingPid, int callingUserId) {
- if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
- callingUserId)) {
- if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
- return;
+ int callingUid, int callingPid, int callingUserId, boolean fromClient) {
+ if (fromClient) {
+ // Only check this if cancel was called from the client (app). If cancel was called
+ // from BiometricService, it means the dialog was dismissed due to user interaction.
+ if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
+ callingUserId)) {
+ if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
+ return;
+ }
}
mHandler.post(() -> {
ClientMonitor client = mCurrentClient;
if (client instanceof AuthenticationClient) {
- if (client.getToken() == token) {
- if (DEBUG) Slog.v(getTag(), "stop client " + client.getOwnerString());
+ if (client.getToken() == token || !fromClient) {
+ if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString()
+ + ", fromClient: " + fromClient);
+ // If cancel was from BiometricService, it means the dialog was dismissed
+ // and authentication should be canceled.
client.stop(client.getToken() == token);
} else {
- if (DEBUG) Slog.v(getTag(), "can't stop client "
- + client.getOwnerString() + " since tokens don't match");
+ if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString()
+ + " since tokens don't match. fromClient: " + fromClient);
}
} else if (client != null) {
- if (DEBUG) Slog.v(getTag(), "can't cancel non-authenticating client "
+ if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client "
+ client.getOwnerString());
}
});
@@ -805,8 +822,7 @@
int lockoutMode = getLockoutMode();
if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
- Slog.v(getTag(), "In lockout mode(" + lockoutMode +
- ") ; disallowing authentication");
+ Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication");
int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
@@ -919,7 +935,6 @@
if (currentClient != null) {
if (DEBUG) Slog.v(getTag(), "request stop current client " +
currentClient.getOwnerString());
-
// This check only matters for FingerprintService, since enumerate may call back
// multiple times.
if (currentClient instanceof FingerprintService.EnumerateClientImpl ||
@@ -940,17 +955,51 @@
mHandler.removeCallbacks(mResetClientState);
mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
} else if (newClient != null) {
- mCurrentClient = newClient;
- if (DEBUG) Slog.v(getTag(), "starting client "
- + newClient.getClass().getSuperclass().getSimpleName()
- + "(" + newClient.getOwnerString() + ")"
- + ", initiatedByClient = " + initiatedByClient);
- notifyClientActiveCallbacks(true);
+ // For BiometricPrompt clients, do not start until
+ // <Biometric>Service#startPreparedClient is called. BiometricService waits until all
+ // modalities are ready before initiating authentication.
+ if (newClient instanceof AuthenticationClient) {
+ AuthenticationClient client = (AuthenticationClient) newClient;
+ if (client.isBiometricPrompt()) {
+ if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie());
+ mCurrentClient = newClient;
+ if (mBiometricService == null) {
+ mBiometricService = IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE));
+ }
+ try {
+ mBiometricService.onReadyForAuthentication(client.getCookie(),
+ client.getRequireConfirmation(), client.getTargetUserId());
+ } catch (RemoteException e) {
+ Slog.e(getTag(), "Remote exception", e);
+ }
+ return;
+ }
+ }
- newClient.start();
+ // We are not a BiometricPrompt client, start the client immediately
+ mCurrentClient = newClient;
+ startCurrentClient(mCurrentClient.getCookie());
}
}
+ protected void startCurrentClient(int cookie) {
+ if (mCurrentClient == null) {
+ Slog.e(getTag(), "Trying to start null client!");
+ return;
+ }
+ if (DEBUG) Slog.v(getTag(), "starting client "
+ + mCurrentClient.getClass().getSuperclass().getSimpleName()
+ + "(" + mCurrentClient.getOwnerString() + ")"
+ + " cookie: " + cookie + "/" + mCurrentClient.getCookie());
+ if (cookie != mCurrentClient.getCookie()) {
+ Slog.e(getTag(), "Mismatched cookie");
+ return;
+ }
+ notifyClientActiveCallbacks(true);
+ mCurrentClient.start();
+ }
+
protected void removeClient(ClientMonitor client) {
if (client != null) {
client.destroy();
diff --git a/services/core/java/com/android/server/biometrics/ClientMonitor.java b/services/core/java/com/android/server/biometrics/ClientMonitor.java
index a7ada2f..d19aff6 100644
--- a/services/core/java/com/android/server/biometrics/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/ClientMonitor.java
@@ -58,6 +58,9 @@
private IBinder mToken;
private BiometricServiceBase.ServiceListener mListener;
+ // Currently only used for authentication client. The cookie generated by BiometricService
+ // is never 0.
+ private final int mCookie;
protected final MetricsLogger mMetricsLogger;
protected final Metrics mMetrics;
@@ -80,7 +83,7 @@
public ClientMonitor(Context context, Metrics metrics,
BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
BiometricServiceBase.ServiceListener listener, int userId, int groupId,
- boolean restricted, String owner) {
+ boolean restricted, String owner, int cookie) {
mContext = context;
mMetrics = metrics;
mDaemon = daemon;
@@ -91,6 +94,7 @@
mGroupId = groupId;
mIsRestricted = restricted;
mOwner = owner;
+ mCookie = cookie;
mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
mMetricsLogger = new MetricsLogger();
@@ -107,6 +111,10 @@
return mMetrics.logTag();
}
+ public int getCookie() {
+ return mCookie;
+ }
+
/**
* Contacts the biometric's HAL to start the client.
* @return 0 on success, errno from driver on failure
@@ -174,7 +182,7 @@
public boolean onError(long deviceId, int error, int vendorCode) {
try {
if (mListener != null) {
- mListener.onError(deviceId, error, vendorCode);
+ mListener.onError(deviceId, error, vendorCode, getCookie());
}
} catch (RemoteException e) {
Slog.w(getLogTag(), "Failed to invoke sendError", e);
diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java
index 76dc5a9..f858ef5 100644
--- a/services/core/java/com/android/server/biometrics/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/EnrollClient.java
@@ -40,7 +40,7 @@
BiometricServiceBase.ServiceListener listener, int userId, int groupId,
byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils) {
super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
- owner);
+ owner, 0 /* cookie */);
mBiometricUtils = utils;
mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
}
diff --git a/services/core/java/com/android/server/biometrics/EnumerateClient.java b/services/core/java/com/android/server/biometrics/EnumerateClient.java
index 47dc7ff..df6220c 100644
--- a/services/core/java/com/android/server/biometrics/EnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/EnumerateClient.java
@@ -34,7 +34,7 @@
BiometricServiceBase.ServiceListener listener, int groupId, int userId,
boolean restricted, String owner) {
super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
- owner);
+ owner, 0 /* cookie */);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/RemovalClient.java b/services/core/java/com/android/server/biometrics/RemovalClient.java
index 15b3773..be233ec 100644
--- a/services/core/java/com/android/server/biometrics/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/RemovalClient.java
@@ -37,7 +37,7 @@
BiometricServiceBase.ServiceListener listener, int biometricId, int groupId, int userId,
boolean restricted, String owner, BiometricUtils utils) {
super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
- owner);
+ owner, 0 /* cookie */);
mBiometricId = biometricId;
mBiometricUtils = utils;
}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 7aa2e47..557af04 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -27,9 +27,8 @@
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
-import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
import android.hardware.biometrics.face.V1_0.Status;
@@ -38,7 +37,6 @@
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
@@ -50,7 +48,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.biometrics.BiometricServiceBase;
@@ -89,27 +86,9 @@
public FaceAuthClient(Context context,
DaemonWrapper daemon, long halDeviceId, IBinder token,
ServiceListener listener, int targetUserId, int groupId, long opId,
- boolean restricted, String owner, Bundle bundle,
- IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService,
- boolean requireConfirmation) {
+ boolean restricted, String owner, int cookie, boolean requireConfirmation) {
super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
- restricted, owner, bundle, dialogReceiver, statusBarService,
- requireConfirmation);
- }
-
- @Override
- public String getErrorString(int error, int vendorCode) {
- return FaceManager.getErrorString(getContext(), error, vendorCode);
- }
-
- @Override
- public String getAcquiredString(int acquireInfo, int vendorCode) {
- return FaceManager.getAcquiredString(getContext(), acquireInfo, vendorCode);
- }
-
- @Override
- public int getBiometricType() {
- return BiometricAuthenticator.TYPE_FACE;
+ restricted, owner, cookie, requireConfirmation);
}
}
@@ -162,28 +141,33 @@
final AuthenticationClientImpl client = new FaceAuthClient(getContext(),
mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,
- null /* bundle */, null /* dialogReceiver */, mStatusBarService,
- false /* requireConfirmation */);
+ 0 /* cookie */, false /* requireConfirmation */);
authenticateInternal(client, opId, opPackageName);
}
@Override // Binder call
- public void authenticateFromService(boolean requireConfirmation, IBinder token, long opId,
- int groupId, IBiometricServiceReceiver receiver, int flags,
- String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver,
- int callingUid, int callingPid, int callingUserId) {
+ public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId,
+ int groupId, IBiometricServiceReceiverInternal wrapperReceiver,
+ String opPackageName, int cookie, int callingUid, int callingPid,
+ int callingUserId) {
checkPermission(USE_BIOMETRIC_INTERNAL);
final boolean restricted = true; // BiometricPrompt is always restricted
final AuthenticationClientImpl client = new FaceAuthClient(getContext(),
mDaemonWrapper, mHalDeviceId, token,
- new BiometricPromptServiceListenerImpl(receiver),
- mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,
- bundle, dialogReceiver, mStatusBarService, true /* requireConfirmation */);
+ new BiometricPromptServiceListenerImpl(wrapperReceiver),
+ mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie,
+ true /* requireConfirmation */);
authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
callingUserId);
}
@Override // Binder call
+ public void startPreparedClient(int cookie) {
+ checkPermission(MANAGE_BIOMETRIC);
+ startCurrentClient(cookie);
+ }
+
+ @Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName) {
checkPermission(USE_BIOMETRIC_INTERNAL);
cancelAuthenticationInternal(token, opPackageName);
@@ -191,10 +175,10 @@
@Override // Binder call
public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
- int callingUid, int callingPid, int callingUserId) {
+ int callingUid, int callingPid, int callingUserId, boolean fromClient) {
checkPermission(USE_BIOMETRIC_INTERNAL);
- cancelAuthenticationInternal(token, opPackageName,
- callingUid, callingPid, callingUserId);
+ cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid,
+ callingUserId, fromClient);
}
@Override // Binder call
@@ -405,12 +389,9 @@
* Receives callbacks from the ClientMonitor implementations. The results are forwarded to
* BiometricPrompt.
*/
- private class BiometricPromptServiceListenerImpl implements ServiceListener {
-
- private IBiometricServiceReceiver mBiometricServiceReceiver;
-
- public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) {
- mBiometricServiceReceiver = receiver;
+ private class BiometricPromptServiceListenerImpl extends BiometricServiceListener {
+ BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) {
+ super(wrapperReceiver);
}
@Override
@@ -419,32 +400,18 @@
/**
* Map the acquired codes onto existing {@link BiometricConstants} acquired codes.
*/
- if (mBiometricServiceReceiver != null) {
- mBiometricServiceReceiver.onAcquired(deviceId,
+ if (getWrapperReceiver() != null) {
+ getWrapperReceiver().onAcquired(
FaceManager.getMappedAcquiredInfo(acquiredInfo, vendorCode),
FaceManager.getAcquiredString(getContext(), acquiredInfo, vendorCode));
}
}
@Override
- public void onAuthenticationSucceeded(long deviceId,
- BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
- if (mBiometricServiceReceiver != null) {
- mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId);
- }
- }
-
- @Override
- public void onAuthenticationFailed(long deviceId) throws RemoteException {
- if (mBiometricServiceReceiver != null) {
- mBiometricServiceReceiver.onAuthenticationFailed(deviceId);
- }
- }
-
- @Override
- public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
- if (mBiometricServiceReceiver != null) {
- mBiometricServiceReceiver.onError(deviceId, error,
+ public void onError(long deviceId, int error, int vendorCode, int cookie)
+ throws RemoteException {
+ if (getWrapperReceiver() != null) {
+ getWrapperReceiver().onError(cookie, error,
FaceManager.getErrorString(getContext(), error, vendorCode));
}
}
@@ -455,7 +422,6 @@
* the FaceManager.
*/
private class ServiceListenerImpl implements ServiceListener {
-
private IFaceServiceReceiver mFaceServiceReceiver;
public ServiceListenerImpl(IFaceServiceReceiver receiver) {
@@ -501,7 +467,8 @@
}
@Override
- public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
+ public void onError(long deviceId, int error, int vendorCode, int cookie)
+ throws RemoteException {
if (mFaceServiceReceiver != null) {
mFaceServiceReceiver.onError(deviceId, error, vendorCode);
}
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index b0b788f..6a5bc61 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -30,9 +30,8 @@
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.IBiometricPromptReceiver;
-import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
@@ -42,7 +41,6 @@
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
import android.os.Build;
-import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
@@ -55,7 +53,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.biometrics.AuthenticationClient;
@@ -109,27 +106,10 @@
public FingerprintAuthClient(Context context,
DaemonWrapper daemon, long halDeviceId, IBinder token,
ServiceListener listener, int targetUserId, int groupId, long opId,
- boolean restricted, String owner, Bundle bundle,
- IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService,
+ boolean restricted, String owner, int cookie,
boolean requireConfirmation) {
super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
- restricted, owner, bundle, dialogReceiver, statusBarService,
- requireConfirmation);
- }
-
- @Override
- public String getErrorString(int error, int vendorCode) {
- return FingerprintManager.getErrorString(getContext(), error, vendorCode);
- }
-
- @Override
- public String getAcquiredString(int acquireInfo, int vendorCode) {
- return FingerprintManager.getAcquiredString(getContext(), acquireInfo, vendorCode);
- }
-
- @Override
- public int getBiometricType() {
- return BiometricAuthenticator.TYPE_FINGERPRINT;
+ restricted, owner, cookie, requireConfirmation);
}
}
@@ -182,38 +162,44 @@
final boolean restricted = isRestricted();
final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
- mCurrentUserId, groupId, opId, restricted, opPackageName, null /* bundle */,
- null /* dialogReceiver */, mStatusBarService, false /* requireConfirmation */);
+ mCurrentUserId, groupId, opId, restricted, opPackageName,
+ 0 /* cookie */, false /* requireConfirmation */);
authenticateInternal(client, opId, opPackageName);
}
@Override // Binder call
- public void authenticateFromService(IBinder token, long opId, int groupId,
- IBiometricServiceReceiver receiver, int flags, String opPackageName,
- Bundle bundle, IBiometricPromptReceiver dialogReceiver,
- int callingUid, int callingPid, int callingUserId) {
+ public void prepareForAuthentication(IBinder token, long opId, int groupId,
+ IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
+ int cookie, int callingUid, int callingPid, int callingUserId) {
checkPermission(MANAGE_BIOMETRIC);
final boolean restricted = true; // BiometricPrompt is always restricted
final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
mDaemonWrapper, mHalDeviceId, token,
- new BiometricPromptServiceListenerImpl(receiver),
- mCurrentUserId, groupId, opId, restricted, opPackageName, bundle,
- dialogReceiver, mStatusBarService, false /* requireConfirmation */);
+ new BiometricPromptServiceListenerImpl(wrapperReceiver),
+ mCurrentUserId, groupId, opId, restricted, opPackageName, cookie,
+ false /* requireConfirmation */);
authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
callingUserId);
}
@Override // Binder call
+ public void startPreparedClient(int cookie) {
+ checkPermission(MANAGE_BIOMETRIC);
+ startCurrentClient(cookie);
+ }
+
+
+ @Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName) {
cancelAuthenticationInternal(token, opPackageName);
}
@Override // Binder call
public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
- int callingUid, int callingPid, int callingUserId) {
+ int callingUid, int callingPid, int callingUserId, boolean fromClient) {
checkPermission(MANAGE_BIOMETRIC);
- cancelAuthenticationInternal(token, opPackageName,
- callingUid, callingPid, callingUserId);
+ cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid,
+ callingUserId, fromClient);
}
@Override // Binder call
@@ -388,43 +374,25 @@
* Receives callbacks from the ClientMonitor implementations. The results are forwarded to
* BiometricPrompt.
*/
- private class BiometricPromptServiceListenerImpl implements ServiceListener {
-
- private IBiometricServiceReceiver mBiometricServiceReceiver;
-
- public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) {
- mBiometricServiceReceiver = receiver;
+ private class BiometricPromptServiceListenerImpl extends BiometricServiceListener {
+ BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) {
+ super(wrapperReceiver);
}
@Override
public void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
throws RemoteException {
- if (mBiometricServiceReceiver != null) {
- mBiometricServiceReceiver.onAcquired(deviceId, acquiredInfo,
- FingerprintManager.getAcquiredString(
+ if (getWrapperReceiver() != null) {
+ getWrapperReceiver().onAcquired(acquiredInfo, FingerprintManager.getAcquiredString(
getContext(), acquiredInfo, vendorCode));
}
}
@Override
- public void onAuthenticationSucceeded(long deviceId,
- BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
- if (mBiometricServiceReceiver != null) {
- mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId);
- }
- }
-
- @Override
- public void onAuthenticationFailed(long deviceId) throws RemoteException {
- if (mBiometricServiceReceiver != null) {
- mBiometricServiceReceiver.onAuthenticationFailed(deviceId);
- }
- }
-
- @Override
- public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
- if (mBiometricServiceReceiver != null) {
- mBiometricServiceReceiver.onError(deviceId, error,
+ public void onError(long deviceId, int error, int vendorCode, int cookie)
+ throws RemoteException {
+ if (getWrapperReceiver() != null) {
+ getWrapperReceiver().onError(cookie, error,
FingerprintManager.getErrorString(getContext(), error, vendorCode));
}
}
@@ -435,7 +403,6 @@
* the FingerprintManager.
*/
private class ServiceListenerImpl implements ServiceListener {
-
private IFingerprintServiceReceiver mFingerprintServiceReceiver;
public ServiceListenerImpl(IFingerprintServiceReceiver receiver) {
@@ -483,7 +450,8 @@
}
@Override
- public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
+ public void onError(long deviceId, int error, int vendorCode, int cookie)
+ throws RemoteException {
if (mFingerprintServiceReceiver != null) {
mFingerprintServiceReceiver.onError(deviceId, error, vendorCode);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b76eaaf..8abb500 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -20135,7 +20135,7 @@
if (Process.isIsolated(uid)) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
- if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+ if (StorageManager.hasIsolatedStorage()) {
return checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED
? Zygote.MOUNT_EXTERNAL_FULL
: Zygote.MOUNT_EXTERNAL_WRITE;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e9b9930..68fe1d8 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -189,7 +189,7 @@
private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
static {
// STOPSHIP(b/112545973): remove once feature enabled by default
- if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+ if (!StorageManager.hasIsolatedStorage()) {
STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
@@ -198,7 +198,7 @@
private static final Set<String> MEDIA_AURAL_PERMISSIONS = new ArraySet<>();
static {
// STOPSHIP(b/112545973): remove once feature enabled by default
- if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+ if (StorageManager.hasIsolatedStorage()) {
MEDIA_AURAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO);
}
}
@@ -206,7 +206,7 @@
private static final Set<String> MEDIA_VISUAL_PERMISSIONS = new ArraySet<>();
static {
// STOPSHIP(b/112545973): remove once feature enabled by default
- if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+ if (StorageManager.hasIsolatedStorage()) {
MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO);
MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES);
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 21adc47..f0ebb75 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -195,10 +195,8 @@
"/system/bin/traced", // Perfetto.
"/system/bin/traced_probes", // Perfetto.
"webview_zygote",
- // Temporarily excluded zygote to investigate its forking consequences in
- // NativeProcessMemoryState.
- // "zygote",
- // "zygote64",
+ "zygote",
+ "zygote64",
};
private static final int CPU_TIME_PER_THREAD_FREQ_NUM_FREQUENCIES = 8;
@@ -1090,6 +1088,7 @@
private void pullNativeProcessMemoryState(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
+ final List<String> processNames = Arrays.asList(MEMORY_INTERESTING_NATIVE_PROCESSES);
int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
for (int i = 0; i < pids.length; i++) {
int pid = pids[i];
@@ -1099,6 +1098,12 @@
}
int uid = getUidForPid(pid);
String processName = readCmdlineFromProcfs(pid);
+ // Sometimes we get here processName that is not included in the whitelist. It comes
+ // from forking the zygote for an app. We can ignore that sample because this process
+ // is collected by ProcessMemoryState.
+ if (!processNames.contains(processName)) {
+ continue;
+ }
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
e.writeInt(uid);
e.writeString(processName);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 0d66a2c8..e645b84 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -24,7 +24,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -598,8 +598,8 @@
}
@Override
- public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
- boolean requireConfirmation, int userId) {
+ public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
+ int type, boolean requireConfirmation, int userId) {
enforceBiometricDialog();
if (mBar != null) {
try {
@@ -654,6 +654,17 @@
}
@Override
+ public void showBiometricTryAgain() {
+ enforceBiometricDialog();
+ if (mBar != null) {
+ try {
+ mBar.showBiometricTryAgain();
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
public void disable(int what, IBinder token, String pkg) {
disableForUser(what, token, pkg, mCurrentUserId);
}