Merge "Fix the illumination dot getting stuck"
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f533760..9c7dc96 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8179,7 +8179,7 @@
private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
sPackageInfoCache =
new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
- 16, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
+ 32, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
@Override
protected PackageInfo recompute(PackageInfoQuery query) {
return getPackageInfoAsUserUncached(
diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java
index cffb0ad..3d35d2f 100644
--- a/core/java/com/android/internal/os/KernelWakelockReader.java
+++ b/core/java/com/android/internal/os/KernelWakelockReader.java
@@ -153,19 +153,32 @@
}
/**
+ * Attempt to wait for suspend_control service if not immediately available.
+ */
+ private ISuspendControlService waitForSuspendControlService() throws ServiceNotFoundException {
+ final String name = "suspend_control";
+ final int numRetries = 5;
+ for (int i = 0; i < numRetries; i++) {
+ mSuspendControlService = ISuspendControlService.Stub.asInterface(
+ ServiceManager.getService(name));
+ if (mSuspendControlService != null) {
+ return mSuspendControlService;
+ }
+ }
+ throw new ServiceNotFoundException(name);
+ }
+
+ /**
* On success, returns the updated stats from SystemSupend, else returns null.
*/
private KernelWakelockStats getWakelockStatsFromSystemSuspend(
final KernelWakelockStats staleStats) {
WakeLockInfo[] wlStats = null;
- if (mSuspendControlService == null) {
- try {
- mSuspendControlService = ISuspendControlService.Stub.asInterface(
- ServiceManager.getServiceOrThrow("suspend_control"));
- } catch (ServiceNotFoundException e) {
- Slog.wtf(TAG, "Required service suspend_control not available", e);
- return null;
- }
+ try {
+ mSuspendControlService = waitForSuspendControlService();
+ } catch (ServiceNotFoundException e) {
+ Slog.wtf(TAG, "Required service suspend_control not available", e);
+ return null;
}
try {
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 3ecb1dd..0382dd3 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -42,4 +42,13 @@
<!-- Allow SystemUI to show the shutdown dialog -->
<bool name="config_showSysuiShutdown">true</bool>
+
+ <!-- Control the behavior when the user long presses the power button.
+ 0 - Nothing
+ 1 - Global actions menu
+ 2 - Power off (with confirmation)
+ 3 - Power off (without confirmation)
+ 4 - Go to voice assist
+ 5 - Go to assistant (Settings.Secure.ASSISTANT -->
+ <integer name="config_longPressOnPowerBehavior">3</integer>
</resources>
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
index ecd7d40..84c1897a2 100644
--- a/data/etc/preinstalled-packages-platform-overlays.xml
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -19,32 +19,250 @@
<config>
<install-in-user-type package="com.android.internal.display.cutout.emulation.corner">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
<install-in-user-type package="com.android.internal.display.cutout.emulation.double">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.display.cutout.emulation.hole">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
<install-in-user-type package="com.android.internal.display.cutout.emulation.tall">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.gestural">
+ <install-in-user-type package="com.android.internal.display.cutout.emulation.waterfall">
<install-in user-type="FULL" />
- </install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.gestural_extra_wide_back">
- <install-in user-type="FULL" />
- </install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.gestural_narrow_back">
- <install-in user-type="FULL" />
- </install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.gestural_wide_back">
- <install-in user-type="FULL" />
- </install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.threebutton">
- <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
<install-in-user-type package="com.android.internal.systemui.navbar.twobutton">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.threebutton">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.gestural">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.gestural_extra_wide_back">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.gestural_narrow_back">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.gestural_wide_back">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
<install-in-user-type package="com.android.internal.systemui.onehanded.gestural">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.amethyst">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.aquamarine">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.black">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.carbon">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.cinnamon">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.green">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.ocean">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.orchid">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.palette">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.purple">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.sand">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.space">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.tangerine">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.font.notoserifsource">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.pebble">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.roundedrect">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.squircle">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.taperedrect">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.teardrop">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.vessel">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
</config>
diff --git a/location/java/android/location/package.html b/location/java/android/location/package.html
index 2355e72..20c5c54 100644
--- a/location/java/android/location/package.html
+++ b/location/java/android/location/package.html
@@ -6,7 +6,7 @@
<p class="warning">
<strong>This API is not the recommended method for accessing Android location.</strong><br>
The
-<a href="{@docRoot}reference/com/google/android/gms/location/package-summary.html">Google Location Services API</a>,
+<a href="https://developers.google.com/android/reference/com/google/android/gms/location/package-summary">Google Location Services API</a>,
part of Google Play services, is the preferred way to add location-awareness to
your app. It offers a simpler API, higher accuracy, low-power geofencing, and
more. If you are currently using the android.location API, you are strongly
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8212d61..a56f6f5 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -92,6 +92,8 @@
<item type="id" name="requires_remeasuring"/>
+ <item type="id" name="secondary_home_handle" />
+
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 332a00d..398a2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.PixelFormat;
import android.provider.Settings;
@@ -23,6 +24,7 @@
import android.view.WindowManager;
import android.widget.ImageView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
/**
@@ -42,23 +44,36 @@
private boolean mIsVisible = false;
MagnificationModeSwitch(Context context) {
+ this(context, createView(context));
+ }
+
+ @VisibleForTesting
+ MagnificationModeSwitch(Context context, @NonNull ImageView imageView) {
mContext = context;
mWindowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
mParams = createLayoutParams();
- mImageView = createView(mContext, mMagnificationMode);
+ mImageView = imageView;
+ applyResourcesValues();
mImageView.setOnClickListener(
view -> {
removeButton();
toggleMagnificationMode();
});
+ mImageView.setImageResource(getIconResId(mMagnificationMode));
+ }
+
+ private void applyResourcesValues() {
+ final int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_switch_button_padding);
+ mImageView.setPadding(padding, padding, padding, padding);
}
void removeButton() {
- mImageView.animate().cancel();
if (!mIsVisible) {
return;
}
+ mImageView.animate().cancel();
mWindowManager.removeView(mImageView);
mIsVisible = false;
}
@@ -72,7 +87,7 @@
mWindowManager.addView(mImageView, mParams);
mIsVisible = true;
}
-
+ mImageView.setAlpha(1.0f);
// TODO(b/143852371): use accessibility timeout as a delay.
// Dismiss the magnification switch button after the button is displayed for a period of
// time.
@@ -82,30 +97,29 @@
.setStartDelay(START_DELAY_MS)
.setDuration(DURATION_MS)
.withEndAction(
- () -> removeButton());
+ () -> removeButton())
+ .start();
}
private void toggleMagnificationMode() {
final int newMode =
mMagnificationMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
mMagnificationMode = newMode;
+ mImageView.setImageResource(getIconResId(newMode));
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, newMode);
}
- private static ImageView createView(Context context, int mode) {
- final int padding = context.getResources().getDimensionPixelSize(
- R.dimen.magnification_switch_button_padding);
+ private static ImageView createView(Context context) {
ImageView imageView = new ImageView(context);
- imageView.setImageResource(getIconResId(mode));
imageView.setClickable(true);
imageView.setFocusable(true);
- imageView.setPadding(padding, padding, padding, padding);
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
return imageView;
}
- private static int getIconResId(int mode) {
+ @VisibleForTesting
+ static int getIconResId(int mode) {
return (mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
? R.drawable.ic_open_in_new_window
: R.drawable.ic_open_in_new_fullscreen;
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index 78d7087..e8dba8f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -217,10 +217,10 @@
private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
pw.print(DATE_FORMAT.format(message.timestamp))
pw.print(" ")
- pw.print(message.level)
+ pw.print(message.level.shortString)
pw.print(" ")
pw.print(message.tag)
- pw.print(" ")
+ pw.print(": ")
pw.println(message.printer(message))
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
index 7b9af0f..53f231c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
@@ -21,11 +21,14 @@
/**
* Enum version of @Log.Level
*/
-enum class LogLevel(@Log.Level val nativeLevel: Int) {
- VERBOSE(Log.VERBOSE),
- DEBUG(Log.DEBUG),
- INFO(Log.INFO),
- WARNING(Log.WARN),
- ERROR(Log.ERROR),
- WTF(Log.ASSERT)
+enum class LogLevel(
+ @Log.Level val nativeLevel: Int,
+ val shortString: String
+) {
+ VERBOSE(Log.VERBOSE, "V"),
+ DEBUG(Log.DEBUG, "D"),
+ INFO(Log.INFO, "I"),
+ WARNING(Log.WARN, "W"),
+ ERROR(Log.ERROR, "E"),
+ WTF(Log.ASSERT, "WTF")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 27daf86..837543c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -591,6 +591,7 @@
.registerDisplayListener(this, new Handler(Looper.getMainLooper()));
mOrientationHandle = new QuickswitchOrientedNavHandle(getContext());
+ mOrientationHandle.setId(R.id.secondary_home_handle);
getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener);
mOrientationParams = new WindowManager.LayoutParams(0, 0,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
new file mode 100644
index 0000000..71f3d5b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
+import static com.android.systemui.accessibility.MagnificationModeSwitch.getIconResId;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MagnificationModeSwitchTest extends SysuiTestCase {
+
+ @Mock
+ private ImageView mMockImageView;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private ViewPropertyAnimator mViewPropertyAnimator;
+ private MagnificationModeSwitch mMagnificationModeSwitch;
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+
+ when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.withEndAction(any(Runnable.class))).thenReturn(
+ mViewPropertyAnimator);
+
+ when(mMockImageView.animate()).thenReturn(mViewPropertyAnimator);
+
+ mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mMockImageView);
+ }
+
+ @Test
+ public void removeButton_removeView() {
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mMagnificationModeSwitch.removeButton();
+
+ verify(mWindowManager).removeView(mMockImageView);
+ // First invocation is in showButton.
+ verify(mViewPropertyAnimator, times(2)).cancel();
+ }
+
+ @Test
+ public void showWindowModeButton_fullscreenMode_addViewAndSetImageResource() {
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ verify(mMockImageView).setAlpha(1.0f);
+ verify(mMockImageView).setImageResource(
+ getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
+ verify(mViewPropertyAnimator).cancel();
+ verify(mViewPropertyAnimator).setDuration(anyLong());
+ verify(mViewPropertyAnimator).setStartDelay(anyLong());
+ verify(mViewPropertyAnimator).alpha(anyFloat());
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mViewPropertyAnimator).withEndAction(captor.capture());
+ verify(mWindowManager).addView(eq(mMockImageView), any(WindowManager.LayoutParams.class));
+
+ captor.getValue().run();
+
+ // First invocation is in showButton.
+ verify(mViewPropertyAnimator, times(2)).cancel();
+ verify(mWindowManager).removeView(mMockImageView);
+ }
+
+ @Test
+ public void performClick_fullscreenMode_removeViewAndChangeSettingsValue() {
+ ArgumentCaptor<View.OnClickListener> captor = ArgumentCaptor.forClass(
+ View.OnClickListener.class);
+ verify(mMockImageView).setOnClickListener(captor.capture());
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+ captor.getValue().onClick(mMockImageView);
+
+ // First invocation is in showButton.
+ verify(mViewPropertyAnimator, times(2)).cancel();
+ verify(mMockImageView).setImageResource(
+ getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
+ verify(mWindowManager).removeView(mMockImageView);
+ final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
+ assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, actualMode);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index dad0612..d50e9d7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -175,7 +175,7 @@
*/
public FullScreenMagnificationGestureHandler(Context context,
FullScreenMagnificationController fullScreenMagnificationController,
- MagnificationGestureHandler.ScaleChangedListener listener,
+ ScaleChangedListener listener,
boolean detectTripleTap,
boolean detectShortcutTrigger,
int displayId) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index f38c38f..d6f53d2 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -23,7 +23,7 @@
*/
public abstract class MagnificationGestureHandler extends BaseEventStreamTransformation {
- protected final MagnificationGestureHandler.ScaleChangedListener mListener;
+ protected final ScaleChangedListener mListener;
protected MagnificationGestureHandler(ScaleChangedListener listener) {
mListener = listener;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 56c0519..bd25f2b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -100,7 +100,7 @@
*/
public WindowMagnificationGestureHandler(Context context,
WindowMagnificationManager windowMagnificationMgr,
- MagnificationGestureHandler.ScaleChangedListener listener, boolean detectTripleTap,
+ ScaleChangedListener listener, boolean detectTripleTap,
boolean detectShortcutTrigger, int displayId) {
super(listener);
if (DEBUG_ALL) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index a3c5d1e..a08c2dd 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -17,7 +17,10 @@
package com.android.server.accessibility.magnification;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
@@ -52,17 +55,28 @@
static final float MAX_SCALE = FullScreenMagnificationController.MAX_SCALE;
static final float MIN_SCALE = FullScreenMagnificationController.MIN_SCALE;
- private final Object mLock = new Object();;
+ private final Object mLock = new Object();
private final Context mContext;
@VisibleForTesting
@GuardedBy("mLock")
- @Nullable WindowMagnificationConnectionWrapper mConnectionWrapper;
+ @Nullable
+ WindowMagnificationConnectionWrapper mConnectionWrapper;
@GuardedBy("mLock")
private ConnectionCallback mConnectionCallback;
@GuardedBy("mLock")
private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>();
private int mUserId;
+ @VisibleForTesting
+ protected final BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int displayId = context.getDisplayId();
+ removeMagnificationButton(displayId);
+ disableWindowMagnification(displayId);
+ }
+ };
+
public WindowMagnificationManager(Context context, int userId) {
mContext = context;
mUserId = userId;
@@ -133,8 +147,12 @@
if (connect == isConnected()) {
return false;
}
- if (!connect) {
+ if (connect) {
+ final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mScreenStateReceiver, intentFilter);
+ } else {
disableAllWindowMagnifiers();
+ mContext.unregisterReceiver(mScreenStateReceiver);
}
}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 29b8493..03ca8fa 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -31,6 +31,7 @@
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -52,6 +53,7 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.server.biometrics.sensors.ClientMonitor;
public class Utils {
@@ -395,4 +397,8 @@
? keyguardComponent.getPackageName() : null;
return hasPermission && keyguardPackage != null && keyguardPackage.equals(clientPackage);
}
+
+ public static String getClientName(@Nullable ClientMonitor<?> client) {
+ return client != null ? client.getClass().getSimpleName() : "null";
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 14baaa7..f8e8dd9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -45,7 +45,7 @@
private final PowerManager mPowerManager;
private final VibrationEffect mSuccessVibrationEffect;
private final VibrationEffect mErrorVibrationEffect;
- private boolean mErrorAlreadySent;
+ private boolean mShouldSendErrorToClient;
/**
* Stops the HAL operation specific to the ClientMonitor subclass.
@@ -84,11 +84,11 @@
// case (success, failure, or error) is received from the HAL (e.g. versions of fingerprint
// that do not handle lockout under the HAL. In these cases, ensure that the framework only
// sends errors once per ClientMonitor.
- if (!mErrorAlreadySent) {
+ if (!mShouldSendErrorToClient) {
logOnError(getContext(), errorCode, vendorCode, getTargetUserId());
try {
if (getListener() != null) {
- mErrorAlreadySent = true;
+ mShouldSendErrorToClient = true;
getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 5392f0f..fdc3def 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.TaskStackListener;
@@ -41,7 +42,7 @@
private final boolean mIsStrongBiometric;
private final boolean mRequireConfirmation;
private final IActivityTaskManager mActivityTaskManager;
- private final TaskStackListener mTaskStackListener;
+ @Nullable private final TaskStackListener mTaskStackListener;
private final LockoutTracker mLockoutTracker;
private final boolean mIsRestricted;
@@ -56,7 +57,7 @@
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int targetUserId, long operationId, boolean restricted, @NonNull String owner,
int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric,
- int statsModality, int statsClient, @NonNull TaskStackListener taskStackListener,
+ int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener,
@NonNull LockoutTracker lockoutTracker) {
super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
@@ -133,10 +134,12 @@
vibrateSuccess();
}
- try {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not unregister task stack listener", e);
+ if (mTaskStackListener != null) {
+ try {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not unregister task stack listener", e);
+ }
}
final byte[] byteToken = new byte[hardwareAuthToken.size()];
@@ -221,10 +224,12 @@
return;
}
- try {
- mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not register task stack listener", e);
+ if (mTaskStackListener != null) {
+ try {
+ mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not register task stack listener", e);
+ }
}
if (DEBUG) Slog.w(TAG, "Requesting auth for " + getOwnerString());
@@ -241,10 +246,12 @@
return;
}
- try {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not unregister task stack listener", e);
+ if (mTaskStackListener != null) {
+ try {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not unregister task stack listener", e);
+ }
}
if (DEBUG) Slog.w(TAG, "Requesting cancel for " + getOwnerString());
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index c9ab313..6bdd783 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -28,7 +28,7 @@
import android.os.ServiceManager;
import android.util.Slog;
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityTracker;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -37,7 +37,6 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
@@ -133,7 +132,7 @@
@Override
public void run() {
if (operation.state != Operation.STATE_FINISHED) {
- Slog.e(tag, "[Watchdog] Running for: " + operation);
+ Slog.e(tag, "[Watchdog Triggered]: " + operation);
operation.clientMonitor.mFinishCallback
.onClientFinished(operation.clientMonitor, false /* success */);
}
@@ -173,7 +172,7 @@
}
@NonNull private final String mBiometricTag;
- @Nullable private final GestureAvailabilityTracker mGestureAvailabilityTracker;
+ @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
@NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull private final InternalFinishCallback mInternalFinishCallback;
@@ -195,6 +194,8 @@
return;
}
+ mCurrentOperation.state = Operation.STATE_FINISHED;
+
if (mCurrentOperation.clientFinishCallback != null) {
mCurrentOperation.clientFinishCallback.onClientFinished(clientMonitor, success);
}
@@ -206,12 +207,11 @@
}
Slog.d(getTag(), "[Finished] " + clientMonitor + ", success: " + success);
- if (mGestureAvailabilityTracker != null) {
- mGestureAvailabilityTracker.markSensorActive(
+ if (mGestureAvailabilityDispatcher != null) {
+ mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.clientMonitor.getSensorId(), false /* active */);
}
- mCurrentOperation.state = Operation.STATE_FINISHED;
mCurrentOperation = null;
startNextOperationIfIdle();
});
@@ -221,15 +221,15 @@
/**
* Creates a new scheduler.
* @param tag for the specific instance of the scheduler. Should be unique.
- * @param gestureAvailabilityTracker may be null if the sensor does not support gestures (such
- * as fingerprint swipe).
+ * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
+ * (such as fingerprint swipe).
*/
public BiometricScheduler(@NonNull String tag,
- @Nullable GestureAvailabilityTracker gestureAvailabilityTracker) {
+ @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
mBiometricTag = tag;
mInternalFinishCallback = new InternalFinishCallback();
- mGestureAvailabilityTracker = gestureAvailabilityTracker;
- mPendingOperations = new LinkedList<>();
+ mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
+ mPendingOperations = new ArrayDeque<>();
mBiometricService = IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE));
mCrashStates = new ArrayDeque<>();
@@ -268,9 +268,9 @@
return;
}
- if (mGestureAvailabilityTracker != null
+ if (mGestureAvailabilityDispatcher != null
&& mCurrentOperation.clientMonitor instanceof AcquisitionClient) {
- mGestureAvailabilityTracker.markSensorActive(
+ mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.clientMonitor.getSensorId(),
true /* active */);
}
@@ -297,15 +297,23 @@
* Starts the {@link #mCurrentOperation} if
* 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and
* 2) its cookie matches this cookie
+ *
+ * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
+ * requests sensors to prepare for authentication with a cookie. Once sensor(s) are ready (e.g.
+ * the BiometricService client becomes the current client in the scheduler), the cookie is
+ * returned to BiometricService. Once BiometricService decides that authentication can start,
+ * it invokes this code path.
+ *
* @param cookie of the operation to be started
*/
public void startPreparedClient(int cookie) {
if (mCurrentOperation == null) {
- Slog.e(getTag(), "Current operation null");
+ Slog.e(getTag(), "Current operation is null");
return;
}
if (mCurrentOperation.state != Operation.STATE_WAITING_FOR_COOKIE) {
- Slog.e(getTag(), "Operation in wrong state: " + mCurrentOperation);
+ Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
+ + ", expected STATE_WAITING_FOR_COOKIE");
return;
}
if (mCurrentOperation.clientMonitor.getCookie() != cookie) {
@@ -354,7 +362,8 @@
// If the current operation is cancellable, start the cancellation process.
if (mCurrentOperation != null && mCurrentOperation.clientMonitor instanceof Interruptable
- && mCurrentOperation.state != Operation.STATE_STARTED_CANCELING) {
+ && mCurrentOperation.state == Operation.STATE_STARTED) {
+ Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
cancelInternal(mCurrentOperation);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java
deleted file mode 100644
index 9aa72a7..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java
+++ /dev/null
@@ -1,681 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.AppOpsManager;
-import android.app.IActivityTaskManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.TaskStackListener;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.IBiometricService;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.SystemService;
-import com.android.server.biometrics.Utils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Abstract base class containing all of the business logic for biometric services, e.g.
- * Fingerprint, Face, Iris.
- *
- * @hide
- */
-public abstract class BiometricServiceBase<T> extends SystemService
- implements IHwBinder.DeathRecipient {
-
- protected static final boolean DEBUG = true;
-
- private static final int MSG_USER_SWITCHING = 10;
- private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms
-
- private final Context mContext;
- protected final IActivityTaskManager mActivityTaskManager;
- protected final BiometricTaskStackListener mTaskStackListener =
- new BiometricTaskStackListener();
- private final ResetClientStateRunnable mResetClientState = new ResetClientStateRunnable();
-
- protected final IStatusBarService mStatusBarService;
- protected final Map<Integer, Long> mAuthenticatorIds =
- Collections.synchronizedMap(new HashMap<>());
- protected final AppOpsManager mAppOps;
-
- /**
- * Handler which all subclasses should post events to.
- */
- protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(android.os.Message msg) {
- switch (msg.what) {
- case MSG_USER_SWITCHING:
- handleUserSwitching(msg.arg1);
- break;
- default:
- Slog.w(getTag(), "Unknown message:" + msg.what);
- }
- }
- };
-
- protected final ClientMonitor.FinishCallback mClientFinishCallback =
- (clientMonitor, success) -> {
- removeClient(clientMonitor);
- // When enrollment finishes, update this group's authenticator id, as the HAL has
- // already generated a new authenticator id when the new biometric is enrolled.
- if (clientMonitor instanceof EnrollClient) {
- updateActiveGroup(clientMonitor.getTargetUserId());
- }
- };
-
- private IBiometricService mBiometricService;
- private ClientMonitor<T> mCurrentClient;
- private ClientMonitor<T> mPendingClient;
- private PerformanceTracker mPerformanceTracker;
- private int mSensorId;
- protected int mCurrentUserId = UserHandle.USER_NULL;
-
- /**
- * @return the log tag.
- */
- protected abstract String getTag();
-
- /**
- * @return a fresh reference to the biometric HAL
- */
- protected abstract T getDaemon();
-
- /**
- * @return the biometric utilities for a specific implementation.
- */
- protected abstract BiometricUtils getBiometricUtils();
-
- /**
- * @param userId
- * @return true if the enrollment limit has been reached.
- */
- protected abstract boolean hasReachedEnrollmentLimit(int userId);
-
- /**
- * Notifies the HAL that the user has changed.
- * @param userId
- */
- protected abstract void updateActiveGroup(int userId);
-
- /**
- * @param userId
- * @return Returns true if the user has any enrolled biometrics.
- */
- protected abstract boolean hasEnrolledBiometrics(int userId);
-
- /**
- * @return Returns the MANAGE_* permission string, which is required for enrollment, removal
- * etc.
- */
- protected abstract String getManageBiometricPermission();
-
- protected abstract List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(
- int userId);
-
- /**
- * Notifies clients of any change in the biometric state (active / idle). This is mainly for
- * Fingerprint navigation gestures.
- * @param isActive
- */
- protected void notifyClientActiveCallbacks(boolean isActive) {}
-
- protected abstract int statsModality();
-
- private final Runnable mOnTaskStackChangedRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- if (!(mCurrentClient instanceof AuthenticationClient)) {
- return;
- }
- final String currentClient = mCurrentClient.getOwnerString();
- if (isKeyguard(currentClient)) {
- return; // Keyguard is always allowed
- }
- List<ActivityManager.RunningTaskInfo> runningTasks =
- mActivityTaskManager.getTasks(1);
- if (!runningTasks.isEmpty()) {
- final String topPackage = runningTasks.get(0).topActivity.getPackageName();
- if (!topPackage.contentEquals(currentClient)
- && !mCurrentClient.isAlreadyDone()) {
- Slog.e(getTag(), "Stopping background authentication, top: "
- + topPackage + " currentClient: " + currentClient);
- ((AuthenticationClient) mCurrentClient).cancel();
- }
- }
- } catch (RemoteException e) {
- Slog.e(getTag(), "Unable to get running tasks", e);
- }
- }
- };
-
- private final class BiometricTaskStackListener extends TaskStackListener {
- @Override
- public void onTaskStackChanged() {
- mHandler.post(mOnTaskStackChangedRunnable);
- }
- }
-
- private final class ResetClientStateRunnable implements Runnable {
- @Override
- public void run() {
- /**
- * Warning: if we get here, the driver never confirmed our call to cancel the current
- * operation (authenticate, enroll, remove, enumerate, etc), which is
- * really bad. The result will be a 3-second delay in starting each new client.
- * If you see this on a device, make certain the driver notifies with
- * {@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} in response to cancel()
- * once it has successfully switched to the IDLE state in the HAL.
- * Additionally,{@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} should only be sent
- * in response to an actual cancel() call.
- */
- Slog.w(getTag(), "Client "
- + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null")
- + " failed to respond to cancel, starting client "
- + (mPendingClient != null ? mPendingClient.getOwnerString() : "null"));
-
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
- statsModality(), BiometricsProtoEnums.ISSUE_CANCEL_TIMED_OUT);
-
- ClientMonitor<T> newClient = mPendingClient;
- mCurrentClient = null;
- mPendingClient = null;
- startClient(newClient, false);
- }
- }
-
- /**
- * Initializes the system service.
- * <p>
- * Subclasses must define a single argument constructor that accepts the context
- * and passes it to super.
- * </p>
- *
- * @param context The system server context.
- */
- public BiometricServiceBase(Context context) {
- super(context);
- mContext = context;
- mStatusBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- mAppOps = context.getSystemService(AppOpsManager.class);
- mActivityTaskManager = ActivityTaskManager.getService();
- mPerformanceTracker = PerformanceTracker.getInstanceForSensorId(getSensorId());
- }
-
- @Override
- public void onStart() {
- listenForUserSwitches();
- }
-
- @Override
- public void serviceDied(long cookie) {
- Slog.e(getTag(), "HAL died");
- mPerformanceTracker.incrementHALDeathCount();
- mCurrentUserId = UserHandle.USER_NULL;
-
- // All client lifecycle must be managed on the handler.
- mHandler.post(() -> {
- Slog.e(getTag(), "Sending BIOMETRIC_ERROR_HW_UNAVAILABLE after HAL crash");
- handleError(BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- });
-
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
- statsModality(), BiometricsProtoEnums.ISSUE_HAL_DEATH);
- }
-
- protected void initializeConfigurationInternal(int sensorId) {
- if (DEBUG) {
- Slog.d(getTag(), "initializeConfigurationInternal(" + sensorId + ")");
- }
- mSensorId = sensorId;
- }
-
- protected ClientMonitor<?> getCurrentClient() {
- return mCurrentClient;
- }
-
- protected boolean isStrongBiometric() {
- return Utils.isStrongBiometric(mSensorId);
- }
-
- protected int getSensorId() {
- return mSensorId;
- }
-
- /**
- * Callback handlers from the daemon. The caller must put this on a handler.
- */
-
- protected void handleAcquired(int acquiredInfo, int vendorCode) {
- final ClientMonitor<?> client = mCurrentClient;
- if (!(client instanceof AcquisitionClient)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleAcquired for non-acquire consumer: " + clientName);
- return;
- }
-
- final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
- acquisitionClient.onAcquired(acquiredInfo, vendorCode);
- }
-
- protected void handleAuthenticated(BiometricAuthenticator.Identifier identifier,
- ArrayList<Byte> token) {
- final ClientMonitor<?> client = mCurrentClient;
- if (!(client instanceof AuthenticationClient)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleAuthenticated for non-authentication client: " + clientName);
- return;
- }
-
- final AuthenticationClient<?> authenticationClient = (AuthenticationClient<?>) client;
- final boolean authenticated = identifier.getBiometricId() != 0;
- authenticationClient.onAuthenticated(identifier, authenticated, token);
- }
-
- protected void handleEnrollResult(BiometricAuthenticator.Identifier identifier,
- int remaining) {
- final ClientMonitor<?> client = mCurrentClient;
- if (!(client instanceof EnrollClient)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleEnrollResult for non-enroll client: " + clientName);
- return;
- }
-
- final EnrollClient<?> enrollClient = (EnrollClient<?>) client;
- enrollClient.onEnrollResult(identifier, remaining);
- }
-
- protected void handleError(int error, int vendorCode) {
- final ClientMonitor<?> client = mCurrentClient;
-
- if (DEBUG) Slog.v(getTag(), "handleError(client="
- + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
-
- if (!(client instanceof Interruptable)) {
- Slog.e(getTag(), "error received for non-ErrorConsumer");
- return;
- }
-
- final Interruptable interruptable = (Interruptable) client;
- interruptable.onError(error, vendorCode);
-
- if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
- mHandler.removeCallbacks(mResetClientState);
- if (mPendingClient != null) {
- if (DEBUG) Slog.v(getTag(), "start pending client " +
- mPendingClient.getOwnerString());
- startClient(mPendingClient, false);
- mPendingClient = null;
- }
- }
- }
-
- protected void handleRemoved(BiometricAuthenticator.Identifier identifier,
- final int remaining) {
- if (DEBUG) Slog.w(getTag(), "Removed: fid=" + identifier.getBiometricId()
- + ", dev=" + identifier.getDeviceId()
- + ", rem=" + remaining);
-
- final ClientMonitor<?> client = mCurrentClient;
- if (!(client instanceof RemovalConsumer)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleRemoved for non-removal consumer: " + clientName);
- return;
- }
-
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- removalConsumer.onRemoved(identifier, remaining);
- }
-
- protected void handleEnumerate(BiometricAuthenticator.Identifier identifier, int remaining) {
- final ClientMonitor<?> client = mCurrentClient;
- if (!(client instanceof EnumerateConsumer)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleEnumerate for non-enumerate consumer: "
- + clientName);
- return;
- }
-
- final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
- enumerateConsumer.onEnumerationResult(identifier, remaining);
- }
-
- /**
- * Calls from the Manager. These are still on the calling binder's thread.
- */
-
- protected void enrollInternal(EnrollClient<T> client, int userId) {
- if (hasReachedEnrollmentLimit(userId)) {
- return;
- }
-
- // Group ID is arbitrarily set to parent profile user ID. It just represents
- // the default biometrics for the user.
- if (!Utils.isCurrentUserOrProfile(mContext, userId)) {
- return;
- }
-
- mHandler.post(() -> {
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- protected void cancelEnrollmentInternal(IBinder token) {
- mHandler.post(() -> {
- ClientMonitor<?> client = mCurrentClient;
- if (client instanceof EnrollClient && client.getToken() == token) {
- if (DEBUG) Slog.v(getTag(), "Cancelling enrollment");
- ((EnrollClient<?>) client).cancel();
- }
- });
- }
-
- protected void generateChallengeInternal(GenerateChallengeClient<T> client) {
- mHandler.post(() -> {
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- protected void revokeChallengeInternal(RevokeChallengeClient<T> client) {
- mHandler.post(() -> {
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- protected void authenticateInternal(AuthenticationClient<T> client, String opPackageName) {
- mHandler.post(() -> {
- startAuthentication(client, opPackageName);
- });
- }
-
- protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName,
- boolean fromClient) {
-
- if (DEBUG) Slog.v(getTag(), "cancelAuthentication(" + opPackageName + ")");
-
- mHandler.post(() -> {
- ClientMonitor<?> client = mCurrentClient;
- if (client instanceof AuthenticationClient) {
- 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.
- ((AuthenticationClient<?>) client).cancel();
- } else {
- 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 "
- + client.getOwnerString());
- }
- });
- }
-
- protected void removeInternal(RemovalClient<T> client) {
- mHandler.post(() -> {
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- protected void cleanupInternal(
- InternalCleanupClient<? extends BiometricAuthenticator.Identifier, T> client) {
- mHandler.post(() -> {
- if (DEBUG) {
- Slog.v(getTag(), "Cleaning up templates for user("
- + client.getTargetUserId() + ")");
- }
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- // Should be done on a handler thread - not on the Binder's thread.
- private void startAuthentication(AuthenticationClient<T> client, String opPackageName) {
- if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")");
-
- startClient(client, true /* initiatedByClient */);
- }
-
- /**
- * Helper methods.
- */
-
- /**
- * @return true if this is keyguard package
- */
- public boolean isKeyguard(String clientPackage) {
- return Utils.isKeyguard(mContext, clientPackage);
- }
-
- /**
- * Calls the HAL to switch states to the new task. If there's already a current task,
- * it calls cancel() and sets mPendingClient to begin when the current task finishes
- * ({@link BiometricConstants#BIOMETRIC_ERROR_CANCELED}).
- *
- * @param newClient the new client that wants to connect
- * @param initiatedByClient true for authenticate, remove and enroll
- */
- @VisibleForTesting
- protected void startClient(ClientMonitor<T> newClient, boolean initiatedByClient) {
- ClientMonitor<?> currentClient = mCurrentClient;
- if (currentClient != null) {
- if (DEBUG) Slog.v(getTag(), "request stop current client " +
- currentClient.getOwnerString());
- if (currentClient instanceof InternalCleanupClient) {
- // This condition means we're currently running internal diagnostics to
- // remove extra templates in the hardware and/or the software
- // TODO: design an escape hatch in case client never finishes
- if (newClient != null) {
- Slog.w(getTag(), "Internal cleanup in progress but trying to start client "
- + newClient.getClass().getSuperclass().getSimpleName()
- + "(" + newClient.getOwnerString() + ")"
- + ", initiatedByClient = " + initiatedByClient);
- }
- } else if (currentClient instanceof Interruptable) {
- ((Interruptable) currentClient).cancel();
-
- // Only post the reset runnable for non-cleanup clients. Cleanup clients should
- // never be forcibly stopped since they ensure synchronization between HAL and
- // framework. Thus, we should instead just start the pending client once cleanup
- // finishes instead of using the reset runnable.
- mHandler.removeCallbacks(mResetClientState);
- mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
- }
- mPendingClient = newClient;
- } else if (newClient != null) {
- // 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());
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception", e);
- }
- return;
- }
- }
-
- // 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().getSimpleName()
- + "(" + mCurrentClient.getOwnerString() + ")"
- + " targetUserId: " + mCurrentClient.getTargetUserId()
- + " currentUserId: " + mCurrentUserId
- + " cookie: " + cookie + "/" + mCurrentClient.getCookie());
-
- if (cookie != mCurrentClient.getCookie()) {
- Slog.e(getTag(), "Mismatched cookie");
- return;
- }
-
- final T daemon = mCurrentClient.getFreshDaemon();
- if (daemon == null) {
- Slog.e(getTag(), "Daemon null, unable to start: "
- + mCurrentClient.getClass().getSimpleName());
- mCurrentClient.unableToStart();
- mCurrentClient = null;
- return;
- }
-
- mCurrentClient.start(mClientFinishCallback);
- notifyClientActiveCallbacks(true);
- }
-
- protected void removeClient(ClientMonitor<?> client) {
- if (client != null) {
- client.destroy();
- if (client != mCurrentClient && mCurrentClient != null) {
- Slog.w(getTag(), "Unexpected client: " + client.getOwnerString() + "expected: "
- + mCurrentClient.getOwnerString());
- }
- }
- if (mCurrentClient != null) {
- if (DEBUG) Slog.v(getTag(), "Done with client: "
- + mCurrentClient.getClass().getSimpleName()
- + "(" + mCurrentClient.getOwnerString() + ")");
- mCurrentClient = null;
- }
- if (mPendingClient == null) {
- notifyClientActiveCallbacks(false);
- }
- }
-
- /**
- * Populates existing authenticator ids. To be used only during the start of the service.
- */
- protected void loadAuthenticatorIds() {
- // This operation can be expensive, so keep track of the elapsed time. Might need to move to
- // background if it takes too long.
- long t = System.currentTimeMillis();
- mAuthenticatorIds.clear();
- for (UserInfo user : UserManager.get(getContext()).getUsers(true /* excludeDying */)) {
- int userId = user.id;
- if (!mAuthenticatorIds.containsKey(userId)) {
- updateActiveGroup(userId);
- }
- }
-
- t = System.currentTimeMillis() - t;
- if (t > 1000) {
- Slog.w(getTag(), "loadAuthenticatorIds() taking too long: " + t + "ms");
- }
- }
-
- protected boolean isRestricted() {
- // Only give privileged apps (like Settings) access to biometric info
- final boolean restricted = !hasPermission(getManageBiometricPermission());
- return restricted;
- }
-
- protected boolean hasPermission(String permission) {
- return getContext().checkCallingOrSelfPermission(permission)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- protected void checkPermission(String permission) {
- getContext().enforceCallingOrSelfPermission(permission,
- "Must have " + permission + " permission.");
- }
-
- /**
- * @return authenticator id for the calling user
- */
- protected long getAuthenticatorId(int callingUserId) {
- return mAuthenticatorIds.getOrDefault(callingUserId, 0L);
- }
-
- /**
- * This method should be called upon connection to the daemon, and when user switches.
- */
- protected abstract void doTemplateCleanupForUser(int userId);
-
- /**
- * This method is called when the user switches. Implementations should probably notify the
- * HAL.
- */
- protected void handleUserSwitching(int userId) {
- if (getCurrentClient() instanceof InternalCleanupClient) {
- Slog.w(getTag(), "User switched while performing cleanup");
- }
- updateActiveGroup(userId);
- doTemplateCleanupForUser(userId);
- }
-
- private void listenForUserSwitches() {
- try {
- ActivityManager.getService().registerUserSwitchObserver(
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
- .sendToTarget();
- }
- }, getTag());
- } catch (RemoteException e) {
- Slog.w(getTag(), "Failed to listen for user switching event" ,e);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
index 8cabdf5..8b27781 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
@@ -34,7 +34,7 @@
public abstract class ClientMonitor<T> extends LoggableMonitor implements IBinder.DeathRecipient {
private static final String TAG = "Biometrics/ClientMonitor";
- protected static final boolean DEBUG = BiometricServiceBase.DEBUG;
+ protected static final boolean DEBUG = true;
// Counter used to distinguish between ClientMonitor instances to help debugging.
private static int sCount = 0;
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index a3d9677..163f29e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -35,12 +35,17 @@
protected final byte[] mHardwareAuthToken;
protected final int mTimeoutSec;
- private final BiometricUtils mBiometricUtils;
+ protected final BiometricUtils mBiometricUtils;
private final boolean mShouldVibrate;
private long mEnrollmentStartTimeMs;
private boolean mAlreadyCancelled;
+ /**
+ * @return true if the user has already enrolled the maximum number of templates.
+ */
+ protected abstract boolean hasReachedEnrollmentLimit();
+
public EnrollClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
@@ -82,6 +87,12 @@
public void start(@NonNull FinishCallback finishCallback) {
super.start(finishCallback);
+ if (hasReachedEnrollmentLimit()) {
+ Slog.e(TAG, "Reached enrollment limit");
+ finishCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
mEnrollmentStartTimeMs = System.currentTimeMillis();
startHalOperation();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
similarity index 96%
rename from services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java
rename to services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
index 22c10b3..f4997d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
@@ -32,7 +32,7 @@
* ends. This class keeps track of all client callbacks. Individual sensors should notify this
* when lockout for a specific sensor has been reset.
*/
-public class LockoutResetTracker implements IBinder.DeathRecipient {
+public class LockoutResetDispatcher implements IBinder.DeathRecipient {
private static final String TAG = "LockoutResetTracker";
@@ -79,7 +79,7 @@
}
}
- public LockoutResetTracker(Context context) {
+ public LockoutResetDispatcher(Context context) {
mContext = context;
mClientCallbacks = new ArrayList<>();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
index a7c63f7..1a4216f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
@@ -39,10 +39,6 @@
private final int mStatsClient;
private long mFirstAcquireTimeMs;
- protected long getFirstAcquireTimeMs() {
- return mFirstAcquireTimeMs;
- }
-
/**
* Only valid for AuthenticationClient.
* @return true if the client is authenticating for a crypto operation.
@@ -62,6 +58,12 @@
mStatsClient = statsClient;
}
+ private boolean isAnyFieldUnknown() {
+ return mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN
+ || mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN
+ || mStatsClient == BiometricsProtoEnums.CLIENT_UNKNOWN;
+ }
+
protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode,
int targetUserId) {
@@ -86,6 +88,11 @@
+ ", AcquiredInfo: " + acquiredInfo
+ ", VendorCode: " + vendorCode);
}
+
+ if (isAnyFieldUnknown()) {
+ return;
+ }
+
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED,
mStatsModality,
targetUserId,
@@ -114,6 +121,11 @@
} else {
Slog.v(TAG, "Error latency: " + latency);
}
+
+ if (isAnyFieldUnknown()) {
+ return;
+ }
+
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
mStatsModality,
targetUserId,
@@ -157,6 +169,10 @@
Slog.v(TAG, "Authentication latency: " + latency);
}
+ if (isAnyFieldUnknown()) {
+ return;
+ }
+
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
mStatsModality,
targetUserId,
@@ -179,6 +195,10 @@
Slog.v(TAG, "Enroll latency: " + latency);
}
+ if (isAnyFieldUnknown()) {
+ return;
+ }
+
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED,
mStatsModality,
targetUserId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
new file mode 100644
index 0000000..c4ea0a8
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.NotificationManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.app.UserSwitchObserver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
+import android.hardware.face.Face;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IHwBinder;
+import android.os.Looper;
+import android.os.NativeHandle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.Interruptable;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUpdateActiveUserClient;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or
+ * its extended minor versions.
+ */
+class Face10 implements IHwBinder.DeathRecipient {
+
+ private static final String TAG = "Face10";
+ private static final int ENROLL_TIMEOUT_SEC = 75;
+ static final String NOTIFICATION_TAG = "FaceService";
+ static final int NOTIFICATION_ID = 1;
+
+ @NonNull private final Context mContext;
+ @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final Handler mHandler;
+ @NonNull private final ClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon;
+ @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
+ @NonNull private final LockoutHalImpl mLockoutTracker;
+ @NonNull private final UsageStats mUsageStats;
+ @NonNull private NotificationManager mNotificationManager;
+ private final int mSensorId;
+ @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+
+ @Nullable private IBiometricsFace mDaemon;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+
+ private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(newUserId);
+ }
+ };
+
+ private final IBiometricsFaceClientCallback mDaemonCallback =
+ new IBiometricsFaceClientCallback.Stub() {
+ @Override
+ public void onEnrollResult(long deviceId, int faceId, int userId, int remaining) {
+ mHandler.post(() -> {
+ final CharSequence name = FaceUtils.getInstance()
+ .getUniqueName(mContext, userId);
+ final Face face = new Face(name, faceId, deviceId);
+
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FaceEnrollClient)) {
+ Slog.e(TAG, "onEnrollResult for non-enroll client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
+ enrollClient.onEnrollResult(face, remaining);
+ });
+ }
+
+ @Override
+ public void onAuthenticated(long deviceId, int faceId, int userId, ArrayList<Byte> token) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FaceAuthenticationClient)) {
+ Slog.e(TAG, "onAuthenticated for non-authentication client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FaceAuthenticationClient authenticationClient =
+ (FaceAuthenticationClient) client;
+ final boolean authenticated = faceId != 0;
+ final Face face = new Face("", faceId, deviceId);
+ authenticationClient.onAuthenticated(face, authenticated, token);
+ });
+ }
+
+ @Override
+ public void onAcquired(long deviceId, int userId, int acquiredInfo, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AcquisitionClient)) {
+ Slog.e(TAG, "onAcquired for non-acquire client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
+ acquisitionClient.onAcquired(acquiredInfo, vendorCode);
+ });
+ }
+
+ @Override
+ public void onError(long deviceId, int userId, int error, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ Slog.d(TAG, "handleError"
+ + ", client: " + (client != null ? client.getOwnerString() : null)
+ + ", error: " + error
+ + ", vendorCode: " + vendorCode);
+ if (!(client instanceof Interruptable)) {
+ Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client));
+ return;
+ }
+
+ final Interruptable interruptable = (Interruptable) client;
+ interruptable.onError(error, vendorCode);
+
+ if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
+ Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ });
+ }
+
+ @Override
+ public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof RemovalConsumer)) {
+ Slog.e(TAG, "onRemoved for non-removal consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final RemovalConsumer removalConsumer = (RemovalConsumer) client;
+
+ if (!removed.isEmpty()) {
+ // Convert to old fingerprint-like behavior, where remove() receives one removal
+ // at a time. This way, remove can share some more common code.
+ for (int i = 0; i < removed.size(); i++) {
+ final int id = removed.get(i);
+ final Face face = new Face("", id, deviceId);
+ final int remaining = removed.size() - i - 1;
+ Slog.d(TAG, "Removed, faceId: " + id + ", remaining: " + remaining);
+ removalConsumer.onRemoved(face, remaining);
+ }
+ } else {
+ final Face face = new Face("", 0 /* identifier */, deviceId);
+ removalConsumer.onRemoved(face, 0 /* remaining */);
+ }
+
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
+ });
+ }
+
+ @Override
+ public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof EnumerateConsumer)) {
+ Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
+
+ if (!faceIds.isEmpty()) {
+ // Convert to old fingerprint-like behavior, where enumerate() receives one
+ // template at a time. This way, enumerate can share some more common code.
+ for (int i = 0; i < faceIds.size(); i++) {
+ final Face face = new Face("", faceIds.get(i), deviceId);
+ enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1);
+ }
+ } else {
+ // For face, the HIDL contract is to receive an empty list when there are no
+ // templates enrolled. Send a null identifier since we don't consume them
+ // anywhere, and send remaining == 0 so this code can be shared with
+ // Fingerprint@2.1
+ enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
+ }
+ });
+ }
+
+ @Override
+ public void onLockoutChanged(long duration) {
+ mHandler.post(() -> {
+ Slog.d(TAG, "onLockoutChanged: " + duration);
+ final @LockoutTracker.LockoutMode int lockoutMode;
+ if (duration == 0) {
+ lockoutMode = LockoutTracker.LOCKOUT_NONE;
+ } else if (duration == -1 || duration == Long.MAX_VALUE) {
+ lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
+ } else {
+ lockoutMode = LockoutTracker.LOCKOUT_TIMED;
+ }
+
+ mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
+
+ if (duration == 0) {
+ mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
+ }
+ });
+ }
+ };
+
+ Face10(@NonNull Context context, int sensorId,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ mContext = context;
+ mSensorId = sensorId;
+ mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */);
+ mHandler = new Handler(Looper.getMainLooper());
+ mUsageStats = new UsageStats(context);
+ mAuthenticatorIds = new HashMap<>();
+ mLazyDaemon = Face10.this::getDaemon;
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+ mLockoutTracker = new LockoutHalImpl();
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+
+ try {
+ ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to register user switch observer");
+ }
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.e(TAG, "HAL died");
+ mHandler.post(() -> {
+ PerformanceTracker.getInstanceForSensorId(mSensorId)
+ .incrementHALDeathCount();
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (client instanceof Interruptable) {
+ Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
+ final Interruptable interruptable = (Interruptable) client;
+ interruptable.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+
+ mScheduler.recordCrashState();
+
+ FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
+ BiometricsProtoEnums.MODALITY_FACE,
+ BiometricsProtoEnums.ISSUE_HAL_DEATH);
+ }
+ });
+ }
+
+ private synchronized IBiometricsFace getDaemon() {
+ if (mDaemon != null) {
+ return mDaemon;
+ }
+
+ Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+ + mScheduler.getCurrentClient());
+
+ try {
+ mDaemon = IBiometricsFace.getService();
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened.
+ Slog.w(TAG, "NoSuchElementException", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get face HAL", e);
+ }
+
+ if (mDaemon == null) {
+ Slog.w(TAG, "Face HAL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+ // HAL ID for these HIDL versions are only used to determine if callbacks have been
+ // successfully set.
+ long halId = 0;
+ try {
+ halId = mDaemon.setCallback(mDaemonCallback).value;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set callback for face HAL", e);
+ mDaemon = null;
+ }
+
+ Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
+ if (halId != 0) {
+ scheduleLoadAuthenticatorIds();
+ scheduleInternalCleanup(ActivityManager.getCurrentUser());
+ } else {
+ Slog.e(TAG, "Unable to set callback");
+ mDaemon = null;
+ }
+
+ return mDaemon;
+ }
+
+ @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ // Note that this can be performed on the scheduler (as opposed to being done immediately
+ // when the HAL is (re)loaded, since
+ // 1) If this is truly the first time it's being performed (e.g. system has just started),
+ // this will be run very early and way before any applications need to generate keys.
+ // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+ // just been reloaded), the framework already has a cache of the authenticatorIds. This
+ // is safe because authenticatorIds only change when A) new template has been enrolled,
+ // or B) all templates are removed.
+ mHandler.post(() -> {
+ for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+ final int targetUserId = user.id;
+ if (!mAuthenticatorIds.containsKey(targetUserId)) {
+ scheduleUpdateActiveUserWithoutHandler(targetUserId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
+ * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
+ * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
+ * this operation on the same lambda/runnable as those operations so that the ordering is
+ * correct.
+ */
+ private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
+ final boolean hasEnrolled = !getEnrolledFaces(targetUserId).isEmpty();
+ final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
+ mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId, mCurrentUserId,
+ hasEnrolled, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, (clientMonitor, success) -> {
+ if (success) {
+ mCurrentUserId = targetUserId;
+ }
+ });
+ }
+
+ void scheduleResetLockout(int userId, @NonNull byte[] hardwareAuthToken) {
+ mHandler.post(() -> {
+ if (getEnrolledFaces(userId).isEmpty()) {
+ Slog.w(TAG, "Ignoring lockout reset, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+ hardwareAuthToken);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleSetFeature(@NonNull IBinder token, int userId, int feature, boolean enabled,
+ @NonNull byte[] hardwareAuthToken, @NonNull IFaceServiceReceiver receiver,
+ @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ final List<Face> faces = getEnrolledFaces(userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+ opPackageName, mSensorId, feature, enabled, hardwareAuthToken, faceId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleGetFeature(@NonNull IBinder token, int userId, int feature,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ final List<Face> faces = getEnrolledFaces(userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+ opPackageName, mSensorId, feature, faceId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleGenerateChallenge(@NonNull IBinder token, @NonNull IFaceServiceReceiver receiver,
+ @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
+ mSensorId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) {
+ mHandler.post(() -> {
+ final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+ mLazyDaemon, token, owner, mSensorId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleEnroll(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
+ @NonNull int[] disabledFeatures, @Nullable NativeHandle surfaceHandle) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
+ UserHandle.CURRENT);
+
+ final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+ opPackageName, FaceUtils.getInstance(), disabledFeatures, ENROLL_TIMEOUT_SEC,
+ surfaceHandle, mSensorId);
+
+ mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+ }
+ }));
+ });
+ }
+
+ void cancelEnrollment(@NonNull IBinder token) {
+ mHandler.post(() -> {
+ mScheduler.cancelEnrollment(token);
+ });
+ }
+
+ void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie,
+ @NonNull ClientMonitorCallbackConverter receiver, @NonNull String opPackageName,
+ boolean restricted, int statsClient) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
+ final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
+ mLazyDaemon, token, receiver, userId, operationId, restricted, opPackageName,
+ cookie, false /* requireConfirmation */, mSensorId, isStrongBiometric,
+ statsClient, mLockoutTracker, mUsageStats);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void startPreparedClient(int cookie) {
+ mHandler.post(() -> {
+ mScheduler.startPreparedClient(cookie);
+ });
+ }
+
+ void cancelAuthentication(@NonNull IBinder token) {
+ mHandler.post(() -> {
+ mScheduler.cancelAuthentication(token);
+ });
+ }
+
+ void scheduleRemove(@NonNull IBinder token, int faceId, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
+ FaceUtils.getInstance(), mSensorId, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ private void scheduleInternalCleanup(int userId) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final List<Face> enrolledList = getEnrolledFaces(userId);
+ final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList,
+ FaceUtils.getInstance(), mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ boolean isHardwareDetected() {
+ final IBiometricsFace daemon = getDaemon();
+ return daemon != null;
+ }
+
+ List<Face> getEnrolledFaces(int userId) {
+ return FaceUtils.getInstance().getBiometricsForUser(mContext, userId);
+ }
+
+ long getAuthenticatorId(int userId) {
+ return mAuthenticatorIds.get(userId);
+ }
+
+ public void dump(@NonNull PrintWriter pw) {
+ PerformanceTracker performanceTracker =
+ PerformanceTracker.getInstanceForSensorId(mSensorId);
+
+ JSONObject dump = new JSONObject();
+ try {
+ dump.put("service", "Face Manager");
+
+ JSONArray sets = new JSONArray();
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+ final int N = FaceUtils.getInstance().getBiometricsForUser(mContext, userId).size();
+ JSONObject set = new JSONObject();
+ set.put("id", userId);
+ set.put("count", N);
+ set.put("accept", performanceTracker.getAcceptForUser(userId));
+ set.put("reject", performanceTracker.getRejectForUser(userId));
+ set.put("acquire", performanceTracker.getAcquireForUser(userId));
+ set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
+ set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
+ // cryptoStats measures statistics about secure face transactions
+ // (e.g. to unlock password storage, make secure purchases, etc.)
+ set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
+ set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
+ set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
+ sets.put(set);
+ }
+
+ dump.put("prints", sets);
+ } catch (JSONException e) {
+ Slog.e(TAG, "dump formatting failure", e);
+ }
+ pw.println(dump);
+ pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
+
+ mUsageStats.print(pw);
+ }
+
+ public void dumpHal(@NonNull FileDescriptor fd, @NonNull String[] args) {
+ // WARNING: CDD restricts image data from leaving TEE unencrypted on
+ // production devices:
+ // [C-1-10] MUST not allow unencrypted access to identifiable biometric
+ // data or any data derived from it (such as embeddings) to the
+ // Application Processor outside the context of the TEE.
+ // As such, this API should only be enabled for testing purposes on
+ // engineering and userdebug builds. All modules in the software stack
+ // MUST enforce final build products do NOT have this functionality.
+ // Additionally, the following check MUST NOT be removed.
+ if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
+ return;
+ }
+
+ // Additionally, this flag allows turning off face for a device
+ // (either permanently through the build or on an individual device).
+ if (SystemProperties.getBoolean("ro.face.disable_debug_data", false)
+ || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) {
+ return;
+ }
+
+ // The debug method takes two file descriptors. The first is for text
+ // output, which we will drop. The second is for binary data, which
+ // will be the protobuf data.
+ final IBiometricsFace daemon = getDaemon();
+ if (daemon != null) {
+ FileOutputStream devnull = null;
+ try {
+ devnull = new FileOutputStream("/dev/null");
+ final NativeHandle handle = new NativeHandle(
+ new FileDescriptor[] { devnull.getFD(), fd },
+ new int[0], false);
+ daemon.debug(handle, new ArrayList<String>(Arrays.asList(args)));
+ } catch (IOException | RemoteException ex) {
+ Slog.d(TAG, "error while reading face debugging data", ex);
+ } finally {
+ if (devnull != null) {
+ try {
+ devnull.close();
+ } catch (IOException ex) {
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
index 118cadc..21bda74 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
@@ -21,7 +21,6 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.TaskStackListener;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -67,11 +66,10 @@
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
boolean isStrongBiometric, int statsClient,
- @NonNull TaskStackListener taskStackListener,
@NonNull LockoutTracker lockoutTracker, @NonNull UsageStats usageStats) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FACE, statsClient, taskStackListener,
+ BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
lockoutTracker);
mNotificationManager = context.getSystemService(NotificationManager.class);
mUsageStats = usageStats;
@@ -221,8 +219,8 @@
.build();
mNotificationManager.createNotificationChannel(channel);
- mNotificationManager.notifyAsUser(FaceService.NOTIFICATION_TAG,
- FaceService.NOTIFICATION_ID, notification,
+ mNotificationManager.notifyAsUser(Face10.NOTIFICATION_TAG,
+ Face10.NOTIFICATION_ID, notification,
UserHandle.CURRENT);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
index 4f9f46a..52a8226 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
@@ -68,6 +68,19 @@
}
@Override
+ protected boolean hasReachedEnrollmentLimit() {
+ final int limit = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
+ final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
+ .size();
+ if (enrolled >= limit) {
+ Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public void onAcquired(int acquireInfo, int vendorCode) {
final boolean shouldSend;
if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 7a05192..3c597a9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -16,64 +16,31 @@
package com.android.server.biometrics.sensors.face;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
-import android.app.ActivityManager;
-import android.app.NotificationManager;
+import android.annotation.NonNull;
import android.content.Context;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
import android.hardware.face.Face;
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
-import android.os.Build;
-import android.os.Environment;
import android.os.IBinder;
import android.os.NativeHandle;
-import android.os.RemoteException;
-import android.os.SELinux;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Slog;
import android.view.Surface;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.DumpUtils;
-import com.android.server.SystemServerInitThreadPool;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.BiometricServiceBase;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.SystemService;
+import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.EnrollClient;
-import com.android.server.biometrics.sensors.GenerateChallengeClient;
-import com.android.server.biometrics.sensors.LockoutResetTracker;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.RemovalClient;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -82,177 +49,115 @@
* The service is responsible for maintaining a list of clients and dispatching all
* face-related events.
*/
-public class FaceService extends BiometricServiceBase<IBiometricsFace> {
+public class FaceService extends SystemService {
protected static final String TAG = "FaceService";
- private static final boolean DEBUG = true;
- private static final String FACE_DATA_DIR = "facedata";
- private static final String ACTION_LOCKOUT_RESET =
- "com.android.server.biometrics.face.ACTION_LOCKOUT_RESET";
-
- static final String NOTIFICATION_TAG = "FaceService";
- static final int NOTIFICATION_ID = 1;
-
- private final LockoutResetTracker mLockoutResetTracker;
- private final ClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon;
+ private Face10 mFace10;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
/**
* Receives the incoming binder calls from FaceManager.
*/
private final class FaceServiceWrapper extends IFaceService.Stub {
- private static final int ENROLL_TIMEOUT_SEC = 75;
-
- /**
- * The following methods contain common code which is shared in biometrics/common.
- */
-
@Override // Binder call
public void generateChallenge(IBinder token, IFaceServiceReceiver receiver,
String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
-
- final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
- getSensorId());
- generateChallengeInternal(client);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.scheduleGenerateChallenge(token, receiver, opPackageName);
}
@Override // Binder call
public void revokeChallenge(IBinder token, String owner) {
- checkPermission(MANAGE_BIOMETRIC);
-
- final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(getContext(),
- mLazyDaemon, token, owner, getSensorId());
-
- // TODO(b/137106905): Schedule binder calls in FaceService to avoid deadlocks.
- if (getCurrentClient() == null) {
- // if we aren't handling any other HIDL calls (mCurrentClient == null), revoke
- // the challenge right away.
- revokeChallengeInternal(client);
- } else {
- // postpone revoking the challenge until we finish processing the current HIDL
- // call.
- mPendingRevokeChallenge = client;
- }
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.scheduleRevokeChallenge(token, owner);
}
@Override // Binder call
public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures, Surface surface) {
- checkPermission(MANAGE_BIOMETRIC);
- updateActiveGroup(userId);
-
- mHandler.post(() -> {
- mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
- UserHandle.CURRENT);
- });
-
- final FaceEnrollClient client = new FaceEnrollClient(getContext(), mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, getBiometricUtils(), disabledFeatures, ENROLL_TIMEOUT_SEC,
- convertSurfaceToNativeHandle(surface), getSensorId());
-
- enrollInternal(client, userId);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.scheduleEnroll(token, hardwareAuthToken, userId, receiver, opPackageName,
+ disabledFeatures, convertSurfaceToNativeHandle(surface));
}
@Override // Binder call
public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures) {
- checkPermission(MANAGE_BIOMETRIC);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
// TODO(b/145027036): Implement this.
}
@Override // Binder call
public void cancelEnrollment(final IBinder token) {
- checkPermission(MANAGE_BIOMETRIC);
- cancelEnrollmentInternal(token);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.cancelEnrollment(token);
}
@Override // Binder call
- public void authenticate(final IBinder token, final long opId, int userId,
+ public void authenticate(final IBinder token, final long operationId, int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- updateActiveGroup(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- final boolean restricted = isRestricted();
- final int statsClient = isKeyguard(opPackageName) ? BiometricsProtoEnums.CLIENT_KEYGUARD
+ final boolean restricted = false; // Face APIs are private
+ final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
+ ? BiometricsProtoEnums.CLIENT_KEYGUARD
: BiometricsProtoEnums.CLIENT_UNKNOWN;
- final FaceAuthenticationClient client = new FaceAuthenticationClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, opId,
- restricted, opPackageName, 0 /* cookie */, false /* requireConfirmation */,
- getSensorId(), isStrongBiometric(), statsClient, mTaskStackListener,
- mLockoutTracker, mUsageStats);
- authenticateInternal(client, opPackageName);
+ mFace10.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */,
+ new ClientMonitorCallbackConverter(receiver), opPackageName, restricted,
+ statsClient);
}
@Override // Binder call
- public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId,
- int userId, IBiometricSensorReceiver sensorReceiver,
+ public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
+ long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
String opPackageName, int cookie, int callingUid, int callingPid,
int callingUserId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- updateActiveGroup(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final boolean restricted = true; // BiometricPrompt is always restricted
- final FaceAuthenticationClient client = new FaceAuthenticationClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(sensorReceiver), userId,
- opId, restricted, opPackageName, cookie, requireConfirmation, getSensorId(),
- isStrongBiometric(), BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
- mTaskStackListener, mLockoutTracker, mUsageStats);
- authenticateInternal(client, opPackageName);
+ mFace10.scheduleAuthenticate(token, operationId, userId, cookie,
+ new ClientMonitorCallbackConverter(sensorReceiver), opPackageName,
+ restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT);
}
@Override // Binder call
public void startPreparedClient(int cookie) {
- checkPermission(MANAGE_BIOMETRIC);
- startCurrentClient(cookie);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.startPreparedClient(cookie);
}
@Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- cancelAuthenticationInternal(token, opPackageName, true /* fromClient */);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.cancelAuthentication(token);
}
@Override // Binder call
public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
int callingUid, int callingPid, int callingUserId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- // Cancellation is from system server in this case.
- cancelAuthenticationInternal(token, opPackageName, false /* fromClient */);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.cancelAuthentication(token);
}
@Override // Binder call
public void remove(final IBinder token, final int faceId, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
- updateActiveGroup(userId);
-
- if (token == null) {
- Slog.w(TAG, "remove(): token is null");
- return;
- }
-
- final FaceRemovalClient client = new FaceRemovalClient(getContext(), mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
- getBiometricUtils(), getSensorId(), mAuthenticatorIds);
- removeInternal(client);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.scheduleRemove(token, faceId, userId, receiver, opPackageName);
}
@Override
public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
final String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- mHandler.post(() -> {
- mLockoutResetTracker.addCallback(callback, opPackageName);
- });
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mLockoutResetDispatcher.addCallback(callback, opPackageName);
}
@Override // Binder call
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
}
@@ -260,28 +165,21 @@
final long ident = Binder.clearCallingIdentity();
try {
if (args.length > 1 && "--hal".equals(args[0])) {
- dumpHal(fd, Arrays.copyOfRange(args, 1, args.length, args.getClass()));
+ mFace10.dumpHal(fd, Arrays.copyOfRange(args, 1, args.length, args.getClass()));
} else {
- dumpInternal(pw);
+ mFace10.dump(pw);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- /**
- * The following methods don't use any common code from BiometricService
- */
-
- // TODO: refactor out common code here
@Override // Binder call
public boolean isHardwareDetected(String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
-
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final long token = Binder.clearCallingIdentity();
try {
- IBiometricsFace daemon = getFaceDaemon();
- return daemon != null;
+ return mFace10.isHardwareDetected();
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -289,512 +187,66 @@
@Override // Binder call
public List<Face> getEnrolledFaces(int userId, String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
- return FaceService.this.getEnrolledTemplates(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return mFace10.getEnrolledFaces(userId);
}
@Override // Binder call
public boolean hasEnrolledFaces(int userId, String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- return FaceService.this.hasEnrolledBiometrics(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return !mFace10.getEnrolledFaces(userId).isEmpty();
}
@Override
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- return mLockoutTracker.getLockoutModeForUser(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return mFace10.getLockoutModeForUser(userId);
}
@Override // Binder call
- public long getAuthenticatorId(int callingUserId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- return FaceService.this.getAuthenticatorId(callingUserId);
+ public long getAuthenticatorId(int userId) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return mFace10.getAuthenticatorId(userId);
}
@Override // Binder call
public void resetLockout(int userId, byte[] hardwareAuthToken) {
- checkPermission(MANAGE_BIOMETRIC);
- mHandler.post(() -> {
- if (!FaceService.this.hasEnrolledBiometrics(userId)) {
- Slog.w(TAG, "Ignoring lockout reset, no templates enrolled");
- return;
- }
-
- Slog.d(TAG, "Resetting lockout for user: " + userId);
-
- updateActiveGroup(userId);
- final FaceResetLockoutClient client = new FaceResetLockoutClient(getContext(),
- mLazyDaemon, userId, getContext().getOpPackageName(), getSensorId(),
- hardwareAuthToken);
- startClient(client, true /* initiatedByClient */);
- });
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.scheduleResetLockout(userId, hardwareAuthToken);
}
@Override
public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
final String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
-
- mHandler.post(() -> {
- if (DEBUG) {
- Slog.d(TAG, "setFeature for user(" + userId + ")");
- }
- updateActiveGroup(userId);
- if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
- Slog.e(TAG, "No enrolled biometrics while setting feature: " + feature);
- return;
- }
-
- final int faceId = getFirstTemplateForUser(mCurrentUserId);
-
- final FaceSetFeatureClient client = new FaceSetFeatureClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, getSensorId(), feature, enabled, hardwareAuthToken, faceId);
- startClient(client, true /* initiatedByClient */);
- });
-
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.scheduleSetFeature(token, userId, feature, enabled, hardwareAuthToken, receiver,
+ opPackageName);
}
@Override
public void getFeature(final IBinder token, int userId, int feature,
IFaceServiceReceiver receiver, final String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
-
- mHandler.post(() -> {
- if (DEBUG) {
- Slog.d(TAG, "getFeature for user(" + userId + ")");
- }
- updateActiveGroup(userId);
- // This should ideally return tri-state, but the user isn't shown settings unless
- // they are enrolled so it's fine for now.
- if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
- Slog.e(TAG, "No enrolled biometrics while getting feature: " + feature);
- return;
- }
-
- // TODO: Support multiple faces
- final int faceId = getFirstTemplateForUser(mCurrentUserId);
-
- final FaceGetFeatureClient client = new FaceGetFeatureClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, getSensorId(), feature, faceId);
- startClient(client, true /* initiatedByClient */);
- });
-
- }
-
- // TODO: Support multiple faces
- private int getFirstTemplateForUser(int user) {
- final List<Face> faces = FaceService.this.getEnrolledTemplates(user);
- if (!faces.isEmpty()) {
- return faces.get(0).getBiometricId();
- }
- return 0;
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.scheduleGetFeature(token, userId, feature, receiver, opPackageName);
}
@Override // Binder call
public void initializeConfiguration(int sensorId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- initializeConfigurationInternal(sensorId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10 = new Face10(getContext(), sensorId, mLockoutResetDispatcher);
}
}
- private final LockoutHalImpl mLockoutTracker;
-
- @GuardedBy("this")
- private IBiometricsFace mDaemon;
- private UsageStats mUsageStats;
- private FaceRevokeChallengeClient mPendingRevokeChallenge;
-
- private NotificationManager mNotificationManager;
-
- /**
- * Receives callbacks from the HAL.
- */
- private IBiometricsFaceClientCallback mDaemonCallback =
- new IBiometricsFaceClientCallback.Stub() {
- @Override
- public void onEnrollResult(final long deviceId, int faceId, int userId,
- int remaining) {
- mHandler.post(() -> {
- final Face face = new Face(getBiometricUtils()
- .getUniqueName(getContext(), userId), faceId, deviceId);
- FaceService.super.handleEnrollResult(face, remaining);
-
- // Enrollment changes the authenticatorId, so update it here.
- IBiometricsFace daemon = getFaceDaemon();
- if (remaining == 0 && daemon != null) {
- try {
- mAuthenticatorIds.put(userId,
- hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId().value
- : 0L);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to get authenticatorId", e);
- }
- }
- });
- }
-
- @Override
- public void onAcquired(final long deviceId, final int userId, final int acquiredInfo,
- final int vendorCode) {
- mHandler.post(() -> {
- FaceService.super.handleAcquired(acquiredInfo, vendorCode);
- });
- }
-
- @Override
- public void onAuthenticated(final long deviceId, final int faceId, final int userId,
- ArrayList<Byte> token) {
- mHandler.post(() -> {
- Face face = new Face("", faceId, deviceId);
- FaceService.super.handleAuthenticated(face, token);
- });
- }
-
- @Override
- public void onError(final long deviceId, final int userId, final int error,
- final int vendorCode) {
- mHandler.post(() -> {
- FaceService.super.handleError(error, vendorCode);
-
- // TODO: this chunk of code should be common to all biometric services
- if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
- // If we get HW_UNAVAILABLE, try to connect again later...
- Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client.");
- synchronized (this) {
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
- }
- }
- });
- }
-
- @Override
- public void onRemoved(final long deviceId, ArrayList<Integer> faceIds, final int userId) {
- mHandler.post(() -> {
- if (!faceIds.isEmpty()) {
- for (int i = 0; i < faceIds.size(); i++) {
- final Face face = new Face("", faceIds.get(i), deviceId);
- // Convert to old behavior
- FaceService.super.handleRemoved(face, faceIds.size() - i - 1);
- }
- } else {
- final Face face = new Face("", 0 /* identifier */, deviceId);
- FaceService.super.handleRemoved(face, 0 /* remaining */);
- }
- Settings.Secure.putIntForUser(getContext().getContentResolver(),
- Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
- });
- }
-
- @Override
- public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId)
- throws RemoteException {
- mHandler.post(() -> {
- if (!faceIds.isEmpty()) {
- for (int i = 0; i < faceIds.size(); i++) {
- final Face face = new Face("", faceIds.get(i), deviceId);
- // Convert to old old behavior
- FaceService.super.handleEnumerate(face, faceIds.size() - i - 1);
- }
- } else {
- // For face, the HIDL contract is to receive an empty list when there are no
- // templates enrolled. Send a null identifier since we don't consume them
- // anywhere, and send remaining == 0 to plumb this with existing common code.
- FaceService.super.handleEnumerate(null /* identifier */, 0);
- }
- });
- }
-
- @Override
- public void onLockoutChanged(long duration) {
- Slog.d(TAG, "onLockoutChanged: " + duration);
-
- final @LockoutTracker.LockoutMode int lockoutMode;
- if (duration == 0) {
- lockoutMode = LockoutTracker.LOCKOUT_NONE;
- } else if (duration == -1 || duration == Long.MAX_VALUE) {
- lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
- } else {
- lockoutMode = LockoutTracker.LOCKOUT_TIMED;
- }
- mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
-
- mHandler.post(() -> {
- if (duration == 0) {
- mLockoutResetTracker.notifyLockoutResetCallbacks(getSensorId());
- }
- });
- }
- };
-
public FaceService(Context context) {
super(context);
- mLazyDaemon = FaceService.this::getFaceDaemon;
- mLockoutResetTracker = new LockoutResetTracker(context);
- mLockoutTracker = new LockoutHalImpl();
- mUsageStats = new UsageStats(context);
- mNotificationManager = getContext().getSystemService(NotificationManager.class);
- }
-
- @Override
- protected void removeClient(ClientMonitor<?> client) {
- super.removeClient(client);
- if (mPendingRevokeChallenge != null) {
- revokeChallengeInternal(mPendingRevokeChallenge);
- mPendingRevokeChallenge = null;
- }
+ mLockoutResetDispatcher = new LockoutResetDispatcher(context);
}
@Override
public void onStart() {
- super.onStart();
publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());
- // Get the face daemon on FaceService's on thread so SystemServerInitThreadPool isn't
- // blocked
- SystemServerInitThreadPool.submit(() -> mHandler.post(this::getFaceDaemon),
- TAG + ".onStart");
- }
-
- @Override
- public String getTag() {
- return TAG;
- }
-
- @Override
- protected IBiometricsFace getDaemon() {
- return getFaceDaemon();
- }
-
- @Override
- protected BiometricUtils getBiometricUtils() {
- return FaceUtils.getInstance();
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit(int userId) {
- final int limit = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
- final int enrolled = FaceService.this.getEnrolledTemplates(userId).size();
- if (enrolled >= limit) {
- Slog.w(TAG, "Too many faces registered, user: " + userId);
- return true;
- }
- return false;
- }
-
- @Override
- public void serviceDied(long cookie) {
- super.serviceDied(cookie);
- mDaemon = null;
-
- mCurrentUserId = UserHandle.USER_NULL; // Force updateActiveGroup() to re-evaluate
- }
-
- @Override
- protected void updateActiveGroup(int userId) {
- IBiometricsFace daemon = getFaceDaemon();
-
- if (daemon != null) {
- try {
- if (userId != mCurrentUserId) {
- final File baseDir = Environment.getDataVendorDeDirectory(userId);
- final File faceDir = new File(baseDir, FACE_DATA_DIR);
- if (!faceDir.exists()) {
- if (!faceDir.mkdir()) {
- Slog.v(TAG, "Cannot make directory: " + faceDir.getAbsolutePath());
- return;
- }
- // Calling mkdir() from this process will create a directory with our
- // permissions (inherited from the containing dir). This command fixes
- // the label.
- if (!SELinux.restorecon(faceDir)) {
- Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
- return;
- }
- }
-
- daemon.setActiveUser(userId, faceDir.getAbsolutePath());
- mCurrentUserId = userId;
- mAuthenticatorIds.put(userId,
- hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId().value : 0L);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to setActiveUser():", e);
- }
- }
- }
-
- @Override
- protected void handleUserSwitching(int userId) {
- super.handleUserSwitching(userId);
- // Will be updated when we get the callback from HAL
- mLockoutTracker.setCurrentUserLockoutMode(LockoutTracker.LOCKOUT_NONE);
- }
-
- @Override
- protected boolean hasEnrolledBiometrics(int userId) {
- if (userId != UserHandle.getCallingUserId()) {
- checkPermission(INTERACT_ACROSS_USERS);
- }
- return getBiometricUtils().getBiometricsForUser(getContext(), userId).size() > 0;
- }
-
- @Override
- protected String getManageBiometricPermission() {
- return MANAGE_BIOMETRIC;
- }
-
- @Override
- protected List<Face> getEnrolledTemplates(int userId) {
- return FaceUtils.getInstance().getBiometricsForUser(getContext(), userId);
- }
-
- @Override
- protected void notifyClientActiveCallbacks(boolean isActive) {
- // noop for Face.
- }
-
- @Override
- protected int statsModality() {
- return BiometricsProtoEnums.MODALITY_FACE;
- }
-
- @Override
- protected void doTemplateCleanupForUser(int userId) {
- final List<Face> enrolledList = getEnrolledTemplates(userId);
- final FaceInternalCleanupClient client = new FaceInternalCleanupClient(getContext(),
- mLazyDaemon, userId, getContext().getOpPackageName(), getSensorId(), enrolledList,
- getBiometricUtils(), mAuthenticatorIds);
- cleanupInternal(client);
- }
-
-
- /** Gets the face daemon */
- private synchronized IBiometricsFace getFaceDaemon() {
- if (mDaemon == null) {
- Slog.v(TAG, "mDaemon was null, reconnect to face");
- try {
- mDaemon = IBiometricsFace.getService();
- } catch (java.util.NoSuchElementException e) {
- // Service doesn't exist or cannot be opened. Logged below.
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get biometric interface", e);
- }
- if (mDaemon == null) {
- Slog.w(TAG, "face HIDL not available");
- return null;
- }
-
- mDaemon.asBinder().linkToDeath(this, 0);
-
- long halId = 0;
- try {
- halId = mDaemon.setCallback(mDaemonCallback).value;
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to open face HAL", e);
- mDaemon = null; // try again later!
- }
-
- if (DEBUG) Slog.v(TAG, "Face HAL id: " + halId);
- if (halId != 0) {
- loadAuthenticatorIds();
- updateActiveGroup(ActivityManager.getCurrentUser());
- doTemplateCleanupForUser(ActivityManager.getCurrentUser());
- } else {
- Slog.w(TAG, "Failed to open Face HAL!");
- MetricsLogger.count(getContext(), "faced_openhal_error", 1);
- mDaemon = null;
- }
- }
- return mDaemon;
}
private native NativeHandle convertSurfaceToNativeHandle(Surface surface);
-
- private void dumpInternal(PrintWriter pw) {
- PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(getSensorId());
-
- JSONObject dump = new JSONObject();
- try {
- dump.put("service", "Face Manager");
-
- JSONArray sets = new JSONArray();
- for (UserInfo user : UserManager.get(getContext()).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
- final int N = getBiometricUtils().getBiometricsForUser(getContext(), userId).size();
- JSONObject set = new JSONObject();
- set.put("id", userId);
- set.put("count", N);
- set.put("accept", performanceTracker.getAcceptForUser(userId));
- set.put("reject", performanceTracker.getRejectForUser(userId));
- set.put("acquire", performanceTracker.getAcquireForUser(userId));
- set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
- set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
- // cryptoStats measures statistics about secure face transactions
- // (e.g. to unlock password storage, make secure purchases, etc.)
- set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
- set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
- set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
- sets.put(set);
- }
-
- dump.put("prints", sets);
- } catch (JSONException e) {
- Slog.e(TAG, "dump formatting failure", e);
- }
- pw.println(dump);
- pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
-
- mUsageStats.print(pw);
- }
-
- private void dumpHal(FileDescriptor fd, String[] args) {
- // WARNING: CDD restricts image data from leaving TEE unencrypted on
- // production devices:
- // [C-1-10] MUST not allow unencrypted access to identifiable biometric
- // data or any data derived from it (such as embeddings) to the
- // Application Processor outside the context of the TEE.
- // As such, this API should only be enabled for testing purposes on
- // engineering and userdebug builds. All modules in the software stack
- // MUST enforce final build products do NOT have this functionality.
- // Additionally, the following check MUST NOT be removed.
- if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
- return;
- }
-
- // Additionally, this flag allows turning off face for a device
- // (either permanently through the build or on an individual device).
- if (SystemProperties.getBoolean("ro.face.disable_debug_data", false)
- || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) {
- return;
- }
-
- // The debug method takes two file descriptors. The first is for text
- // output, which we will drop. The second is for binary data, which
- // will be the protobuf data.
- final IBiometricsFace daemon = getFaceDaemon();
- if (daemon != null) {
- FileOutputStream devnull = null;
- try {
- devnull = new FileOutputStream("/dev/null");
- final NativeHandle handle = new NativeHandle(
- new FileDescriptor[] { devnull.getFD(), fd },
- new int[0], false);
- daemon.debug(handle, new ArrayList<String>(Arrays.asList(args)));
- } catch (IOException | RemoteException ex) {
- Slog.d(TAG, "error while reading face debugging data", ex);
- } finally {
- if (devnull != null) {
- try {
- devnull.close();
- } catch (IOException ex) {
- }
- }
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
new file mode 100644
index 0000000..bcf304e
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.ClientMonitor;
+
+import java.io.File;
+import java.util.Map;
+
+public class FaceUpdateActiveUserClient extends ClientMonitor<IBiometricsFace> {
+ private static final String TAG = "FaceUpdateActiveUserClient";
+ private static final String FACE_DATA_DIR = "facedata";
+
+ private final int mCurrentUserId;
+ private final boolean mHasEnrolledBiometrics;
+ @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+
+ FaceUpdateActiveUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
+ int sensorId, int currentUserId, boolean hasEnrolledBIometrics,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+ 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mCurrentUserId = currentUserId;
+ mHasEnrolledBiometrics = hasEnrolledBIometrics;
+ mAuthenticatorIds = authenticatorIds;
+ }
+
+ @Override
+ public void start(@NonNull FinishCallback finishCallback) {
+ super.start(finishCallback);
+
+ if (mCurrentUserId == getTargetUserId()) {
+ Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId");
+ try {
+ mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
+ ? getFreshDaemon().getAuthenticatorId().value : 0L);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to refresh authenticatorId", e);
+ }
+ finishCallback.onClientFinished(this, true /* success */);
+ return;
+ }
+
+ startHalOperation();
+ }
+
+ @Override
+ public void unableToStart() {
+ // Nothing to do here
+ }
+
+ @Override
+ protected void startHalOperation() {
+ final File storePath = new File(Environment.getDataVendorDeDirectory(getTargetUserId()),
+ FACE_DATA_DIR);
+ if (!storePath.exists()) {
+ Slog.e(TAG, "vold has not created the directory?");
+ mFinishCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
+ try {
+ getFreshDaemon().setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
+ mFinishCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to setActiveUser: " + e);
+ mFinishCallback.onClientFinished(this, false /* success */);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
index 0a94972..9f94c88 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
@@ -56,7 +56,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.Interruptable;
-import com.android.server.biometrics.sensors.LockoutResetTracker;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.RemovalConsumer;
@@ -83,17 +83,17 @@
private static final int ENROLL_TIMEOUT_SEC = 60;
private final Context mContext;
- private final IActivityTaskManager mActivityTaskManager;;
+ private final IActivityTaskManager mActivityTaskManager;
private final SensorProperties mSensorProperties;
private final BiometricScheduler mScheduler;
private final Handler mHandler;
- private final LockoutResetTracker mLockoutResetTracker;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
private final LockoutFrameworkImpl mLockoutTracker;
private final BiometricTaskStackListener mTaskStackListener;
private final ClientMonitor.LazyDaemon<IBiometricsFingerprint> mLazyDaemon;
private final Map<Integer, Long> mAuthenticatorIds;
- private IBiometricsFingerprint mDaemon;
+ @Nullable private IBiometricsFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -102,9 +102,9 @@
*/
private static final class SensorProperties {
final int sensorId;
- final Boolean isUdfps;
+ final boolean isUdfps;
- SensorProperties(int sensorId, Boolean isUdfps) {
+ SensorProperties(int sensorId, boolean isUdfps) {
this.sensorId = sensorId;
this.isUdfps = isUdfps;
}
@@ -146,7 +146,7 @@
new LockoutFrameworkImpl.LockoutResetCallback() {
@Override
public void onLockoutReset(int userId) {
- mLockoutResetTracker.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
+ mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
}
};
@@ -157,9 +157,8 @@
}
};
- private IBiometricsFingerprintClientCallback mDaemonCallback =
+ private final IBiometricsFingerprintClientCallback mDaemonCallback =
new IBiometricsFingerprintClientCallback.Stub() {
-
@Override
public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
mHandler.post(() -> {
@@ -169,19 +168,13 @@
final ClientMonitor<?> client = mScheduler.getCurrentClient();
if (!(client instanceof FingerprintEnrollClient)) {
- final String clientName = client != null
- ? client.getClass().getSimpleName(): "null";
- Slog.e(TAG, "onEnrollResult for non-enroll client: " + clientName);
+ Slog.e(TAG, "onEnrollResult for non-enroll client: "
+ + Utils.getClientName(client));
return;
}
final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
enrollClient.onEnrollResult(fingerprint, remaining);
-
- if (remaining == 0) {
- // Update the framework's authenticatorId cache for this user
- scheduleUpdateActiveUserWithoutHandler(mCurrentUserId);
- }
});
}
@@ -195,9 +188,8 @@
mHandler.post(() -> {
final ClientMonitor<?> client = mScheduler.getCurrentClient();
if (!(client instanceof AcquisitionClient)) {
- final String clientName = client != null
- ? client.getClass().getSimpleName(): "null";
- Slog.e(TAG, "onAcquired for non-acquisition client: " + clientName);
+ Slog.e(TAG, "onAcquired for non-acquisition client: "
+ + Utils.getClientName(client));
return;
}
@@ -212,9 +204,8 @@
mHandler.post(() -> {
final ClientMonitor<?> client = mScheduler.getCurrentClient();
if (!(client instanceof FingerprintAuthenticationClient)) {
- final String clientName = client != null
- ? client.getClass().getSimpleName(): "null";
- Slog.e(TAG, "onAuthenticated for non-authentication client: " + clientName);
+ Slog.e(TAG, "onAuthenticated for non-authentication client: "
+ + Utils.getClientName(client));
return;
}
@@ -235,9 +226,7 @@
+ ", error: " + error
+ ", vendorCode: " + vendorCode);
if (!(client instanceof Interruptable)) {
- final String clientName = client != null
- ? client.getClass().getSimpleName(): "null";
- Slog.e(TAG, "onError for non-error consumer: " + clientName);
+ Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client));
return;
}
@@ -258,9 +247,8 @@
Slog.d(TAG, "Removed, fingerId: " + fingerId + ", remaining: " + remaining);
final ClientMonitor<?> client = mScheduler.getCurrentClient();
if (!(client instanceof RemovalConsumer)) {
- final String clientName = client != null
- ? client.getClass().getSimpleName(): "null";
- Slog.e(TAG, "onRemoved for non-removal consumer: " + clientName);
+ Slog.e(TAG, "onRemoved for non-removal consumer: "
+ + Utils.getClientName(client));
return;
}
@@ -275,9 +263,8 @@
mHandler.post(() -> {
final ClientMonitor<?> client = mScheduler.getCurrentClient();
if (!(client instanceof EnumerateConsumer)) {
- final String clientName = client != null
- ? client.getClass().getSimpleName(): "null";
- Slog.e(TAG, "onEnumerate for non-enumerate consumer: " + clientName);
+ Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
+ + Utils.getClientName(client));
return;
}
@@ -289,17 +276,17 @@
};
Fingerprint21(@NonNull Context context, int sensorId,
- @NonNull LockoutResetTracker lockoutResetTracker,
- @NonNull GestureAvailabilityTracker gestureAvailabilityTracker) {
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
mContext = context;
mActivityTaskManager = ActivityTaskManager.getService();
mHandler = new Handler(Looper.getMainLooper());
mTaskStackListener = new BiometricTaskStackListener();
mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>());
mLazyDaemon = Fingerprint21.this::getDaemon;
- mLockoutResetTracker = lockoutResetTracker;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback);
- mScheduler = new BiometricScheduler(TAG, gestureAvailabilityTracker);
+ mScheduler = new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
@@ -308,7 +295,7 @@
}
final IBiometricsFingerprint daemon = getDaemon();
- Boolean isUdfps = false;
+ boolean isUdfps = false;
android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
daemon);
@@ -317,7 +304,7 @@
isUdfps = extension.isUdfps(sensorId);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception while quering udfps", e);
- isUdfps = null;
+ isUdfps = false;
}
}
mSensorProperties = new SensorProperties(sensorId, isUdfps);
@@ -414,9 +401,15 @@
}
}
});
-
}
+ /**
+ * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
+ * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
+ * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
+ * this operation on the same lambda/runnable as those operations so that the ordering is
+ * correct.
+ */
private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
final boolean hasEnrolled = !getEnrolledFingerprints(targetUserId).isEmpty();
final FingerprintUpdateActiveUserClient client =
@@ -467,9 +460,13 @@
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(),
ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+ }
+ }));
});
-
}
void cancelEnrollment(@NonNull IBinder token) {
@@ -533,7 +530,7 @@
}
boolean isHardwareDetected() {
- IBiometricsFingerprint daemon = getDaemon();
+ final IBiometricsFingerprint daemon = getDaemon();
return daemon != null;
}
@@ -619,7 +616,7 @@
tracker.clear();
}
- void dumpInternal(PrintWriter pw) {
+ void dumpInternal(@NonNull PrintWriter pw) {
PerformanceTracker performanceTracker =
PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
index c26dbfd..25e949a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
@@ -76,6 +76,19 @@
}
@Override
+ protected boolean hasReachedEnrollmentLimit() {
+ final int limit = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
+ final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
+ .size();
+ if (enrolled >= limit) {
+ Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
protected void startHalOperation() {
showUdfpsOverlay();
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index d092bfd..e0cb7ec9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -25,6 +25,7 @@
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
@@ -50,7 +51,7 @@
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutResetTracker;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import java.io.FileDescriptor;
@@ -68,8 +69,8 @@
protected static final String TAG = "FingerprintService";
private final AppOpsManager mAppOps;
- private final LockoutResetTracker mLockoutResetTracker;
- private final GestureAvailabilityTracker mGestureAvailabilityTracker;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
private Fingerprint21 mFingerprint21;
/**
@@ -181,11 +182,11 @@
public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
final String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- mLockoutResetTracker.addCallback(callback, opPackageName);
+ mLockoutResetDispatcher.addCallback(callback, opPackageName);
}
@Override // Binder call
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
}
@@ -277,26 +278,26 @@
@Override
public boolean isClientActive() {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
- return mGestureAvailabilityTracker.isAnySensorActive();
+ return mGestureAvailabilityDispatcher.isAnySensorActive();
}
@Override
public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
- mGestureAvailabilityTracker.registerCallback(callback);
+ mGestureAvailabilityDispatcher.registerCallback(callback);
}
@Override
public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
- mGestureAvailabilityTracker.removeCallback(callback);
+ mGestureAvailabilityDispatcher.removeCallback(callback);
}
@Override // Binder call
public void initializeConfiguration(int sensorId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetTracker,
- mGestureAvailabilityTracker);
+ mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher);
}
@Override
@@ -327,8 +328,8 @@
public FingerprintService(Context context) {
super(context);
mAppOps = context.getSystemService(AppOpsManager.class);
- mGestureAvailabilityTracker = new GestureAvailabilityTracker();
- mLockoutResetTracker = new LockoutResetTracker(context);
+ mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
+ mLockoutResetDispatcher = new LockoutResetDispatcher(context);
}
@Override
@@ -336,6 +337,9 @@
publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
}
+ /**
+ * Checks for public API invocations to ensure that permissions, etc are granted/correct.
+ */
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean canUseFingerprint(String opPackageName, boolean requireForeground, int uid,
int pid, int userId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java
similarity index 96%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java
index 5023c83..7bda33d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java
@@ -28,7 +28,7 @@
* Keeps track of sensor gesture availability (e.g. swipe), and notifies clients when its
* availability changes
*/
-public class GestureAvailabilityTracker {
+public class GestureAvailabilityDispatcher {
private static final String TAG = "GestureAvailabilityTracker";
private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks;
@@ -36,7 +36,7 @@
private boolean mIsActive;
- GestureAvailabilityTracker() {
+ GestureAvailabilityDispatcher() {
mClientActiveCallbacks = new CopyOnWriteArrayList<>();
mActiveSensors = new HashMap<>();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index 58ab20d..bcf63dc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -18,17 +18,12 @@
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.iris.IIrisService;
-import com.android.server.biometrics.sensors.BiometricServiceBase;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
-
-import java.util.List;
+import com.android.server.SystemService;
+import com.android.server.biometrics.Utils;
/**
* A service to manage multiple clients that want to access the Iris HAL API.
@@ -36,11 +31,9 @@
* iris-related events.
*
* TODO: The vendor is expected to fill in the service. See
- * {@link FingerprintService}
- *
- * @hide
+ * {@link com.android.server.biometrics.sensors.face.FaceService}
*/
-public class IrisService extends BiometricServiceBase {
+public class IrisService extends SystemService {
private static final String TAG = "IrisService";
@@ -50,77 +43,16 @@
private final class IrisServiceWrapper extends IIrisService.Stub {
@Override // Binder call
public void initializeConfiguration(int sensorId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- initializeConfigurationInternal(sensorId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
}
}
- /**
- * Initializes the system service.
- * <p>
- * Subclasses must define a single argument constructor that accepts the context
- * and passes it to super.
- * </p>
- *
- * @param context The system server context.
- */
- public IrisService(Context context) {
+ public IrisService(@NonNull Context context) {
super(context);
}
@Override
public void onStart() {
- super.onStart();
publishBinderService(Context.IRIS_SERVICE, new IrisServiceWrapper());
}
-
- @Override
- protected void doTemplateCleanupForUser(int userId) {
-
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected Object getDaemon() {
- return null;
- }
-
- @Override
- protected BiometricUtils getBiometricUtils() {
- return null;
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit(int userId) {
- return false;
- }
-
- @Override
- protected void updateActiveGroup(int userId) {
-
- }
-
- @Override
- protected boolean hasEnrolledBiometrics(int userId) {
- return false;
- }
-
- @Override
- protected String getManageBiometricPermission() {
- return null;
- }
-
- @Override
- protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(int userId) {
- return null;
- }
-
- @Override
- protected int statsModality() {
- return BiometricsProtoEnums.MODALITY_IRIS;
- }
}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index b279b37..ed3a223 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -400,7 +400,7 @@
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId)
+ intendingUid, intendingUserId, false)
|| isDeviceLocked(intendingUserId)) {
return null;
}
@@ -416,7 +416,7 @@
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId)
+ intendingUid, intendingUserId, false)
|| isDeviceLocked(intendingUserId)) {
return false;
}
@@ -450,7 +450,7 @@
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId)
+ intendingUid, intendingUserId, false)
|| isDeviceLocked(intendingUserId)) {
return false;
}
@@ -740,14 +740,21 @@
private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
@UserIdInt int userId) {
- // Check the AppOp.
- if (mAppOps.noteOp(op, uid, callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return false;
- }
+ return clipboardAccessAllowed(op, callingPackage, uid, userId, true);
+ }
+
+ private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
+ @UserIdInt int userId, boolean shouldNoteOp) {
+
+ boolean allowed = false;
+
+ // First, verify package ownership to ensure use below is safe.
+ mAppOps.checkPackage(uid, callingPackage);
+
// Shell can access the clipboard for testing purposes.
if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
callingPackage) == PackageManager.PERMISSION_GRANTED) {
- return true;
+ allowed = true;
}
// The default IME is always allowed to access the clipboard.
String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
@@ -755,7 +762,7 @@
if (!TextUtils.isEmpty(defaultIme)) {
final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
if (imePkg.equals(callingPackage)) {
- return true;
+ allowed = true;
}
}
@@ -766,8 +773,10 @@
// at the same time. e.x. SystemUI. It needs to check the window focus of
// Binder.getCallingUid(). Without checking, the user X can't copy any thing from
// INTERNAL_SYSTEM_WINDOW to the other applications.
- boolean allowed = mWm.isUidFocused(uid)
- || isInternalSysWindowAppWithWindowFocus(callingPackage);
+ if (!allowed) {
+ allowed = mWm.isUidFocused(uid)
+ || isInternalSysWindowAppWithWindowFocus(callingPackage);
+ }
if (!allowed && mContentCaptureInternal != null) {
// ...or the Content Capture Service
// The uid parameter of mContentCaptureInternal.isContentCaptureServiceForUser
@@ -786,17 +795,28 @@
// userId must pass intending userId. i.e. user#10.
allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
}
- if (!allowed) {
- Slog.e(TAG, "Denying clipboard access to " + callingPackage
- + ", application is not in focus neither is a system service for "
- + "user " + userId);
- }
- return allowed;
+ break;
case AppOpsManager.OP_WRITE_CLIPBOARD:
// Writing is allowed without focus.
- return true;
+ allowed = true;
+ break;
default:
throw new IllegalArgumentException("Unknown clipboard appop " + op);
}
+ if (!allowed) {
+ Slog.e(TAG, "Denying clipboard access to " + callingPackage
+ + ", application is not in focus nor is it a system service for "
+ + "user " + userId);
+ return false;
+ }
+ // Finally, check the app op.
+ int appOpsResult;
+ if (shouldNoteOp) {
+ appOpsResult = mAppOps.noteOp(op, uid, callingPackage);
+ } else {
+ appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
+ }
+
+ return appOpsResult == AppOpsManager.MODE_ALLOWED;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ca16d57..a864aa6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -403,6 +403,7 @@
@GuardedBy("mLock")
private boolean mVerityFound;
+ @GuardedBy("mLock")
private boolean mDataLoaderFinished = false;
@GuardedBy("mLock")
@@ -1118,7 +1119,7 @@
if (hasParentSessionId()) {
throw new IllegalStateException(
"Session " + sessionId + " is a child of multi-package session "
- + mParentSessionId + " and may not be committed directly.");
+ + getParentSessionId() + " and may not be committed directly.");
}
if (!markAsSealed(statusReceiver, forTransfer)) {
@@ -1589,12 +1590,12 @@
}
private void onStorageUnhealthy() {
- if (TextUtils.isEmpty(mPackageName)) {
+ final String packageName = getPackageName();
+ if (TextUtils.isEmpty(packageName)) {
// The package has not been installed.
return;
}
final PackageManagerService packageManagerService = mPm;
- final String packageName = mPackageName;
mHandler.post(() -> {
if (packageManagerService.deletePackageX(packageName,
PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
@@ -1686,7 +1687,11 @@
}
private void handleInstall() {
- if (isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()) {
+ final boolean needsLogging;
+ synchronized (mLock) {
+ needsLogging = isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked();
+ }
+ if (needsLogging) {
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.INSTALL_PACKAGE)
.setAdmin(mInstallSource.installerPackageName)
@@ -1929,19 +1934,20 @@
// Skip logging the side-loaded app installations, as those are private and aren't reported
// anywhere; app stores already have a record of the installation and that's why reporting
// it here is fine
+ final String packageName = getPackageName();
final String packageNameToLog =
- (params.installFlags & PackageManager.INSTALL_FROM_ADB) == 0 ? mPackageName : "";
+ (params.installFlags & PackageManager.INSTALL_FROM_ADB) == 0 ? packageName : "";
final long currentTimestamp = System.currentTimeMillis();
FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLER_V2_REPORTED,
isIncrementalInstallation(),
packageNameToLog,
currentTimestamp - createdMillis,
returnCode,
- getApksSize());
+ getApksSize(packageName));
}
- private long getApksSize() {
- final PackageSetting ps = mPm.getPackageSetting(mPackageName);
+ private long getApksSize(String packageName) {
+ final PackageSetting ps = mPm.getPackageSetting(packageName);
if (ps == null) {
return 0;
}
@@ -2577,7 +2583,7 @@
}
void setPermissionsResult(boolean accepted) {
- if (!mSealed) {
+ if (!isSealed()) {
throw new SecurityException("Must be sealed to accept permissions");
}
@@ -2653,7 +2659,7 @@
if (hasParentSessionId()) {
throw new IllegalStateException(
"Session " + sessionId + " is a child of multi-package session "
- + mParentSessionId + " and may not be abandoned directly.");
+ + getParentSessionId() + " and may not be abandoned directly.");
}
List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
@@ -2823,7 +2829,11 @@
return;
}
- if (mDestroyed || mDataLoaderFinished) {
+ final boolean isDestroyedOrDataLoaderFinished;
+ synchronized (mLock) {
+ isDestroyedOrDataLoaderFinished = mDestroyed || mDataLoaderFinished;
+ }
+ if (isDestroyedOrDataLoaderFinished) {
switch (status) {
case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
onStorageUnhealthy();
@@ -2835,7 +2845,9 @@
try {
IDataLoader dataLoader = dataLoaderManager.getDataLoader(dataLoaderId);
if (dataLoader == null) {
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"Failure to obtain data loader");
return;
@@ -2868,10 +2880,12 @@
break;
}
case IDataLoaderStatusListener.DATA_LOADER_IMAGE_READY: {
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
if (hasParentSessionId()) {
mSessionProvider.getSession(
- mParentSessionId).dispatchStreamValidateAndCommit();
+ getParentSessionId()).dispatchStreamValidateAndCommit();
} else {
dispatchStreamValidateAndCommit();
}
@@ -2881,7 +2895,9 @@
break;
}
case IDataLoaderStatusListener.DATA_LOADER_IMAGE_NOT_READY: {
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"Failed to prepare image.");
if (manualStartAndDestroy) {
@@ -2895,7 +2911,9 @@
break;
}
case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"DataLoader reported unrecoverable failure.");
break;
@@ -2919,7 +2937,11 @@
final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() {
@Override
public void onHealthStatus(int storageId, int status) {
- if (mDestroyed || mDataLoaderFinished) {
+ final boolean isDestroyedOrDataLoaderFinished;
+ synchronized (mLock) {
+ isDestroyedOrDataLoaderFinished = mDestroyed || mDataLoaderFinished;
+ }
+ if (isDestroyedOrDataLoaderFinished) {
// App's installed.
switch (status) {
case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
@@ -2941,7 +2963,9 @@
// fallthrough
case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
// Even ADB installation can't wait for missing pages for too long.
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"Image is missing pages required for installation.");
break;
@@ -2984,13 +3008,17 @@
return EMPTY_CHILD_SESSION_ARRAY;
}
+ private boolean canBeAddedAsChild(int parentCandidate) {
+ synchronized (mLock) {
+ return (!hasParentSessionId() || mParentSessionId == parentCandidate)
+ && !mCommitted && !mDestroyed;
+ }
+ }
+
@Override
public void addChildSessionId(int childSessionId) {
final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId);
- if (childSession == null
- || (childSession.hasParentSessionId() && childSession.mParentSessionId != sessionId)
- || childSession.mCommitted
- || childSession.mDestroyed) {
+ if (childSession == null || !childSession.canBeAddedAsChild(sessionId)) {
throw new IllegalStateException("Unable to add child session " + childSessionId
+ " as it does not exist or is in an invalid state.");
}
@@ -3039,12 +3067,16 @@
}
boolean hasParentSessionId() {
- return mParentSessionId != SessionInfo.INVALID_ID;
+ synchronized (mLock) {
+ return mParentSessionId != SessionInfo.INVALID_ID;
+ }
}
@Override
public int getParentSessionId() {
- return mParentSessionId;
+ synchronized (mLock) {
+ return mParentSessionId;
+ }
}
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
@@ -3134,27 +3166,37 @@
/** {@hide} */
boolean isStagedSessionReady() {
- return mStagedSessionReady;
+ synchronized (mLock) {
+ return mStagedSessionReady;
+ }
}
/** {@hide} */
boolean isStagedSessionApplied() {
- return mStagedSessionApplied;
+ synchronized (mLock) {
+ return mStagedSessionApplied;
+ }
}
/** {@hide} */
boolean isStagedSessionFailed() {
- return mStagedSessionFailed;
+ synchronized (mLock) {
+ return mStagedSessionFailed;
+ }
}
/** {@hide} */
@StagedSessionErrorCode int getStagedSessionErrorCode() {
- return mStagedSessionErrorCode;
+ synchronized (mLock) {
+ return mStagedSessionErrorCode;
+ }
}
/** {@hide} */
String getStagedSessionErrorMessage() {
- return mStagedSessionErrorMessage;
+ synchronized (mLock) {
+ return mStagedSessionErrorMessage;
+ }
}
private void destroyInternal() {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
index 72f8027..761858c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
@@ -192,10 +192,7 @@
}
private static RuntimeException handleException(RuntimeException e) {
- // TODO(b/160169016): There is currently no other way to distinguish dead object from other
- // exceptions.
- if (e.getCause() instanceof RemoteException &&
- e.getCause().getMessage().equals("HwBinder Error: (-32)")) {
+ if (e.getCause() instanceof DeadObjectException) {
// Server is dead, no need to reboot.
Log.e(TAG, "HAL died");
} else {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1d1c31b..de91af7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3459,9 +3459,9 @@
InsetsControlTarget getImeFallback() {
// host is in non-default display that doesn't support system decor, default to
// default display's StatusBar to control IME (when available), else let system control it.
- WindowState statusBar =
- mWmService.getDefaultDisplayContentLocked().getDisplayPolicy().getStatusBar();
- return statusBar != null ? statusBar : mRemoteInsetsControlTarget;
+ final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked();
+ WindowState statusBar = defaultDc.getDisplayPolicy().getStatusBar();
+ return statusBar != null ? statusBar : defaultDc.mRemoteInsetsControlTarget;
}
boolean canShowIme() {
@@ -3521,7 +3521,10 @@
*/
@VisibleForTesting
InsetsControlTarget computeImeControlTarget() {
- if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null) {
+ if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null
+ || (mInputMethodInputTarget != null
+ && getImeHostOrFallback(mInputMethodInputTarget.getWindow())
+ == mRemoteInsetsControlTarget)) {
return mRemoteInsetsControlTarget;
} else {
// Now, a special case -- if the last target's window is in the process of exiting, but
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 99ee5e1..e7fbc334 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -32,7 +32,7 @@
*/
class ImeInsetsSourceProvider extends InsetsSourceProvider {
- private WindowState mImeTargetFromIme;
+ private InsetsControlTarget mImeTargetFromIme;
private Runnable mShowImeRunner;
private boolean mIsImeLayoutDrawn;
@@ -47,10 +47,12 @@
*
* @param imeTarget imeTarget on which IME request is coming from.
*/
- void scheduleShowImePostLayout(WindowState imeTarget) {
+ void scheduleShowImePostLayout(InsetsControlTarget imeTarget) {
boolean targetChanged = mImeTargetFromIme != imeTarget
&& mImeTargetFromIme != null && imeTarget != null && mShowImeRunner != null
- && mImeTargetFromIme.mActivityRecord == imeTarget.mActivityRecord;
+ && imeTarget.getWindow() != null && mImeTargetFromIme.getWindow() != null
+ && mImeTargetFromIme.getWindow().mActivityRecord
+ == imeTarget.getWindow().mActivityRecord;
mImeTargetFromIme = imeTarget;
if (targetChanged) {
// target changed, check if new target can show IME.
@@ -62,7 +64,8 @@
return;
}
- ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeTargetFromIme.getName());
+ ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeTargetFromIme.getWindow() == null
+ ? mImeTargetFromIme : mImeTargetFromIme.getWindow().getName());
mShowImeRunner = () -> {
ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
// Target should still be the same.
@@ -127,13 +130,17 @@
return false;
}
ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeTargetFromIme: %s",
- dcTarget.getName(), mImeTargetFromIme.getName());
+ dcTarget.getName(), mImeTargetFromIme.getWindow() == null
+ ? mImeTargetFromIme : mImeTargetFromIme.getWindow().getName());
return (!dcTarget.isClosing() && mImeTargetFromIme == dcTarget)
- || (mImeTargetFromIme != null && dcTarget.getParentWindow() == mImeTargetFromIme
- && dcTarget.mSubLayer > mImeTargetFromIme.mSubLayer)
+ || (mImeTargetFromIme != null && mImeTargetFromIme.getWindow() != null
+ && dcTarget.getParentWindow() == mImeTargetFromIme
+ && dcTarget.mSubLayer > mImeTargetFromIme.getWindow().mSubLayer)
|| mImeTargetFromIme == mDisplayContent.getImeFallback()
- || (!mImeTargetFromIme.isClosing() && controlTarget == mImeTargetFromIme);
+ || controlTarget == mImeTargetFromIme
+ && (mImeTargetFromIme.getWindow() == null
+ || !mImeTargetFromIme.getWindow().isClosing());
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 85972bf..4df48dc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7599,13 +7599,14 @@
if (imeTarget == null) {
return;
}
- imeTarget = imeTarget.getImeControlTarget().getWindow();
+ final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
+ imeTarget = controlTarget.getWindow();
// If InsetsControlTarget doesn't have a window, its using remoteControlTarget which
// is controlled by default display
final DisplayContent dc = imeTarget != null
? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked();
dc.getInsetsStateController().getImeSourceProvider()
- .scheduleShowImePostLayout(imeTarget);
+ .scheduleShowImePostLayout(controlTarget);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index c9e6284..70e6a34 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -32,7 +32,10 @@
import static java.lang.Float.NaN;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
@@ -300,6 +303,26 @@
assertFalse(mWindowMagnificationManager.isConnected());
}
+ @Test
+ public void requestConnection_registerAndUnregisterBroadcastReceiver() {
+ assertTrue(mWindowMagnificationManager.requestConnection(true));
+ verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+
+ assertTrue(mWindowMagnificationManager.requestConnection(false));
+ verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+ }
+
+ @Test
+ public void onReceiveScreenOff_removeMagnificationButtonAndDisableWindowMagnification()
+ throws RemoteException {
+ mWindowMagnificationManager.requestConnection(true);
+ mWindowMagnificationManager.mScreenStateReceiver.onReceive(mContext,
+ new Intent(Intent.ACTION_SCREEN_OFF));
+
+ verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY);
+ verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY);
+ }
+
private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
final int len = pointersLocation.length;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java
deleted file mode 100644
index 0c66ee7..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@Presubmit
-@SmallTest
-public class BiometricServiceBaseTest {
- private static class TestableBiometricServiceBase extends BiometricServiceBase {
- TestableBiometricServiceBase(Context context) {
- super(context);
- }
-
- @Override
- protected void doTemplateCleanupForUser(int userId) {
- }
-
- @Override
- protected String getTag() {
- return null;
- }
-
- @Override
- protected Object getDaemon() {
- return null;
- }
-
- @Override
- protected BiometricUtils getBiometricUtils() {
- return null;
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit(int userId) {
- return false;
- }
-
- @Override
- protected void updateActiveGroup(int userId) {
- }
-
- @Override
- protected boolean hasEnrolledBiometrics(int userId) {
- return false;
- }
-
- @Override
- protected String getManageBiometricPermission() {
- return null;
- }
-
- @Override
- protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(
- int userId) {
- return null;
- }
-
- @Override
- protected int statsModality() {
- return 0;
- }
- }
-
- private static final int CLIENT_COOKIE = 0xc00c1e;
-
- private BiometricServiceBase mBiometricServiceBase;
-
- @Mock
- private Context mContext;
- @Mock
- private Resources mResources;
- @Mock
- private BiometricAuthenticator.Identifier mIdentifier;
- @Mock
- private ClientMonitor mClient;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getString(anyInt())).thenReturn("");
- when(mClient.getCookie()).thenReturn(CLIENT_COOKIE);
-
- mBiometricServiceBase = new TestableBiometricServiceBase(mContext);
- }
-
- @Test
- public void testHandleEnumerate_doesNotCrash_withNullClient() {
- mBiometricServiceBase.handleEnumerate(mIdentifier, 0 /* remaining */);
- }
-}
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index bc987a6..71a1964 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -623,6 +623,10 @@
}
private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) {
+ if (uid == Process.SYSTEM_UID || uid == Process.PHONE_UID) {
+ // Skip the check if it's one of these special uids
+ return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ }
final long identity = Binder.clearCallingIdentity();
try {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp
index 819e75b..61ce44c 100644
--- a/tools/validatekeymaps/Android.bp
+++ b/tools/validatekeymaps/Android.bp
@@ -20,6 +20,7 @@
"libutils",
"libcutils",
"liblog",
+ "libui-types",
],
// This tool is prebuilt if we're doing an app-only build.