Merge changes from topic "telephony-owners" into sc-dev
* changes:
Update telephony OWNERS file
Update telephony OWNERS file
Update telephony OWNERS file
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index dbaf275..0c64c86 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5654,8 +5654,8 @@
*/
private void scheduleRelaunchActivityIfPossible(@NonNull ActivityClientRecord r,
boolean preserveWindow) {
- if (r.activity.mFinished || r.token instanceof Binder) {
- // Do not schedule relaunch if the activity is finishing or not a local object (e.g.
+ if ((r.activity != null && r.activity.mFinished) || r.token instanceof Binder) {
+ // Do not schedule relaunch if the activity is finishing or is a local object (e.g.
// created by ActivtiyGroup that server side doesn't recognize it).
return;
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index bb7cdfa..d932a29 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7205,6 +7205,34 @@
* @hide
*/
public interface OnOpStartedListener {
+
+ /**
+ * Represents a start operation that was unsuccessful
+ * @hide
+ */
+ public int START_TYPE_FAILED = 0;
+
+ /**
+ * Represents a successful start operation
+ * @hide
+ */
+ public int START_TYPE_STARTED = 1;
+
+ /**
+ * Represents an operation where a restricted operation became unrestricted, and resumed.
+ * @hide
+ */
+ public int START_TYPE_RESUMED = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+ START_TYPE_FAILED,
+ START_TYPE_STARTED,
+ START_TYPE_RESUMED
+ })
+ public @interface StartedType {}
+
/**
* Called when an op was started.
*
@@ -7213,11 +7241,35 @@
* @param uid The UID performing the operation.
* @param packageName The package performing the operation.
* @param attributionTag The attribution tag performing the operation.
- * @param flags The flags of this op
+ * @param flags The flags of this op.
* @param result The result of the start.
*/
void onOpStarted(int op, int uid, String packageName, String attributionTag,
@OpFlags int flags, @Mode int result);
+
+ /**
+ * Called when an op was started.
+ *
+ * Note: This is only for op starts. It is not called when an op is noted or stopped.
+ * By default, unless this method is overridden, no code will be executed for resume
+ * events.
+ * @param op The op code.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The attribution tag performing the operation.
+ * @param flags The flags of this op.
+ * @param result The result of the start.
+ * @param startType The start type of this start event. Either failed, resumed, or started.
+ * @param attributionFlags The location of this started op in an attribution chain.
+ * @param attributionChainId The ID of the attribution chain of this op, if it is in one.
+ */
+ default void onOpStarted(int op, int uid, String packageName, String attributionTag,
+ @OpFlags int flags, @Mode int result, @StartedType int startType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ if (startType != START_TYPE_RESUMED) {
+ onOpStarted(op, uid, packageName, attributionTag, flags, result);
+ }
+ }
}
AppOpsManager(Context context, IAppOpsService service) {
@@ -7858,8 +7910,10 @@
cb = new IAppOpsStartedCallback.Stub() {
@Override
public void opStarted(int op, int uid, String packageName, String attributionTag,
- int flags, int mode) {
- callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode);
+ int flags, int mode, int startType, int attributionFlags,
+ int attributionChainId) {
+ callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode,
+ startType, attributionFlags, attributionChainId);
}
};
mStartedWatchers.put(callback, cb);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index 9acf9bf..afefcbe 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -75,7 +75,7 @@
ImageWriter writer = null;
Image img = null;
SurfaceInfo surfaceInfo = new SurfaceInfo();
- int nativeFormat = SurfaceUtils.getSurfaceFormat(s);
+ int nativeFormat = SurfaceUtils.detectSurfaceFormat(s);
int dataspace = SurfaceUtils.getSurfaceDataspace(s);
Size surfaceSize = SurfaceUtils.getSurfaceSize(s);
surfaceInfo.mFormat = nativeFormat;
diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
index 57d8ded..fd1a331 100644
--- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java
+++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
@@ -160,6 +160,23 @@
}
/**
+ * Detect and retrieve the Surface format without any
+ * additional overrides.
+ *
+ * @param surface The surface to be queried for format.
+ * @return format of the surface.
+ *
+ * @throws IllegalArgumentException if the surface is already abandoned.
+ */
+ public static int detectSurfaceFormat(Surface surface) {
+ checkNotNull(surface);
+ int surfaceType = nativeDetectSurfaceType(surface);
+ if (surfaceType == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
+
+ return surfaceType;
+ }
+
+ /**
* Get the Surface dataspace.
*
* @param surface The surface to be queried for dataspace.
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 03f94c5..19f204b 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -31,7 +31,9 @@
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE;
import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
@@ -63,7 +65,8 @@
*
* @hide
*/
-public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener {
+public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener,
+ AppOpsManager.OnOpStartedListener {
/** Whether to show the mic and camera icons. */
private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled";
@@ -160,9 +163,10 @@
mUserContexts = new ArrayMap<>();
mUserContexts.put(Process.myUserHandle(), mContext);
// TODO ntmyren: make this listen for flag enable/disable changes
- String[] ops = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
- mContext.getSystemService(AppOpsManager.class).startWatchingActive(ops,
- context.getMainExecutor(), this);
+ String[] opStrs = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
+ mAppOpsManager.startWatchingActive(opStrs, context.getMainExecutor(), this);
+ int[] ops = { OP_CAMERA, OP_RECORD_AUDIO };
+ mAppOpsManager.startWatchingStarted(ops, this);
}
private Context getUserContext(UserHandle user) {
@@ -182,25 +186,65 @@
public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
@Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags,
int attributionChainId) {
- if (attributionChainId == ATTRIBUTION_CHAIN_ID_NONE
- || attributionFlags == ATTRIBUTION_FLAGS_NONE
- || (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
- // If this is not a chain, or it is untrusted, return
+ if (active) {
+ // Started callback handles these
return;
}
- if (!active) {
- // if any link in the chain is finished, remove the chain.
- // TODO ntmyren: be smarter about this
- mAttributionChains.remove(attributionChainId);
+ // if any link in the chain is finished, remove the chain. Then, find any other chains that
+ // contain this op/package/uid/tag combination, and remove them, as well.
+ // TODO ntmyren: be smarter about this
+ mAttributionChains.remove(attributionChainId);
+ int numChains = mAttributionChains.size();
+ ArrayList<Integer> toRemove = new ArrayList<>();
+ for (int i = 0; i < numChains; i++) {
+ int chainId = mAttributionChains.keyAt(i);
+ ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i);
+ int chainSize = chain.size();
+ for (int j = 0; j < chainSize; j++) {
+ AccessChainLink link = chain.get(j);
+ if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) {
+ toRemove.add(chainId);
+ break;
+ }
+ }
+ }
+ mAttributionChains.removeAll(toRemove);
+ }
+
+ @Override
+ public void onOpStarted(int op, int uid, String packageName, String attributionTag,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
+ // not part of an attribution chain. Do nothing
+ }
+
+ @Override
+ public void onOpStarted(int op, int uid, String packageName, String attributionTag,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result,
+ @StartedType int startedType, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
+ if (startedType == START_TYPE_FAILED || attributionChainId == ATTRIBUTION_CHAIN_ID_NONE
+ || attributionFlags == ATTRIBUTION_FLAGS_NONE
+ || (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
+ // If this is not a successful start, or it is not a chain, or it is untrusted, return
return;
}
+ addLinkToChainIfNotPresent(AppOpsManager.opToPublicName(op), packageName, uid,
+ attributionTag, attributionFlags, attributionChainId);
+ }
+
+ private void addLinkToChainIfNotPresent(String op, String packageName, int uid,
+ String attributionTag, int attributionFlags, int attributionChainId) {
ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent(
attributionChainId, k -> new ArrayList<>());
AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid,
attributionFlags);
+ if (currentChain.contains(link)) {
+ return;
+ }
+
int currSize = currentChain.size();
if (currSize == 0 || link.isEnd() || !currentChain.get(currSize - 1).isEnd()) {
// if the list is empty, this link is the end, or the last link in the current chain
@@ -613,5 +657,21 @@
public boolean isStart() {
return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AccessChainLink)) {
+ return false;
+ }
+ AccessChainLink other = (AccessChainLink) obj;
+ return other.flags == flags && packageAndOpEquals(other.usage.op,
+ other.usage.packageName, other.usage.attributionTag, other.usage.uid);
+ }
+
+ public boolean packageAndOpEquals(String op, String packageName, String attributionTag,
+ int uid) {
+ return Objects.equals(op, usage.op) && Objects.equals(packageName, usage.packageName)
+ && Objects.equals(attributionTag, usage.attributionTag) && uid == usage.uid;
+ }
}
}
diff --git a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
index 3a108e7..06640cb 100644
--- a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
@@ -18,5 +18,6 @@
// Iterface to observe op starts
oneway interface IAppOpsStartedCallback {
- void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode);
+ void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode,
+ int startedType, int attributionFlags, int attributionChainId);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 95c24ad..bad79eb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -550,6 +550,8 @@
<protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
<protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
<protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
+ <protected-broadcast android:name="com.android.settings.network.DELETE_SUBSCRIPTION" />
+ <protected-broadcast android:name="com.android.settings.network.SWITCH_TO_SUBSCRIPTION" />
<protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
<protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml
index aa050f3..2b9f952 100644
--- a/core/res/res/layout/splash_screen_view.xml
+++ b/core/res/res/layout/splash_screen_view.xml
@@ -26,6 +26,7 @@
android:layout_width="wrap_content"
android:layout_gravity="center"
android:padding="0dp"
+ android:background="@null"
android:contentDescription="@string/splash_screen_view_icon_description"/>
<View android:id="@+id/splashscreen_branding_view"
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 199e82f..865f026 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1937,7 +1937,7 @@
<string name="notification_phishing_alert_content_description" msgid="494227305355958790">"Phishing-Warnung"</string>
<string name="notification_work_profile_content_description" msgid="5296477955677725799">"Arbeitsprofil"</string>
<string name="notification_alerted_content_description" msgid="6139691253611265992">"Gewarnt"</string>
- <string name="notification_verified_content_description" msgid="6401483602782359391">"Bestätigt"</string>
+ <string name="notification_verified_content_description" msgid="6401483602782359391">"Verifiziert"</string>
<string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Maximieren"</string>
<string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Minimieren"</string>
<string name="expand_action_accessibility" msgid="1947657036871746627">"Maximierung ein-/auschalten"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 624b8b3..c2b6ffb 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -31,7 +31,7 @@
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"స్టాచ్"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్స్టాచ్"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్ పని చేయకపోవచ్చు."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"అనువర్తనంలో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
<string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 6802472..5a6db21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -231,7 +231,9 @@
* possible if AOD isn't even enabled or if the flag is disabled.
*/
public boolean canControlUnlockedScreenOff() {
- return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations();
+ return getAlwaysOn()
+ && mFeatureFlags.useNewLockscreenAnimations()
+ && !getDisplayNeedsBlanking();
}
private boolean getBoolean(String propName, int resId) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 07847f1..45e6f1ec 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -102,6 +102,7 @@
import android.appwidget.AppWidgetManagerInternal;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.ComponentName.WithComponentName;
import android.content.Context;
@@ -307,6 +308,7 @@
*/
@ChangeId
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+ @Overridable
static final long FGS_BG_START_RESTRICTION_CHANGE_ID = 170668199L;
/**
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6d7966f..64b9bd9 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -45,6 +45,9 @@
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
import static android.app.AppOpsManager.OpEventProxyInfo;
import static android.app.AppOpsManager.RestrictionBypass;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
@@ -1238,6 +1241,11 @@
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
tag, true, event.getAttributionFlags(), event.getAttributionChainId());
}
+ // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
+ // TODO ntmyren: figure out how to get the real mode.
+ scheduleOpStartedIfNeededLocked(parent.op, parent.uid, parent.packageName,
+ tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
+ event.getAttributionFlags(), event.getAttributionChainId());
}
mPausedInProgressEvents = null;
}
@@ -3945,13 +3953,15 @@
}
boolean isRestricted = false;
+ int startType = START_TYPE_FAILED;
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
if (!dryRun) {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED);
+ flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+ attributionChainId);
}
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + " flags: "
@@ -3977,7 +3987,7 @@
if (!dryRun) {
attributedOp.rejected(uidState.state, flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode);
+ flags, uidMode, startType, attributionFlags, attributionChainId);
}
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
@@ -3993,7 +4003,7 @@
if (!dryRun) {
attributedOp.rejected(uidState.state, flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode);
+ flags, mode, startType, attributionFlags, attributionChainId);
}
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
@@ -4011,12 +4021,14 @@
attributedOp.started(clientId, proxyUid, proxyPackageName,
proxyAttributionTag, uidState.state, flags, attributionFlags,
attributionChainId);
+ startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED);
+ isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ attributionChainId);
}
}
@@ -4187,7 +4199,9 @@
}
private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
- String attributionTag, @OpFlags int flags, @Mode int result) {
+ String attributionTag, @OpFlags int flags, @Mode int result,
+ @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<StartedCallback> dispatchedCallbacks = null;
final int callbackListCount = mStartedWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -4213,12 +4227,13 @@
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpStarted,
this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
- result));
+ result, startedType, attributionFlags, attributionChainId));
}
private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result) {
+ @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
final long identity = Binder.clearCallingIdentity();
try {
final int callbackCount = callbacks.size();
@@ -4226,7 +4241,7 @@
final StartedCallback callback = callbacks.valueAt(i);
try {
callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
- result);
+ result, startedType, attributionFlags, attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
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 f4327e8..6f38ed0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -82,7 +82,7 @@
private long mStartTimeMs;
- protected boolean mAuthAttempted;
+ private boolean mAuthAttempted;
// TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update
// the state. We should think of a way to improve this in the future.
@@ -98,6 +98,12 @@
*/
protected abstract void handleLifecycleAfterAuth(boolean authenticated);
+ /**
+ * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched.
+ * etc)
+ */
+ public abstract boolean wasUserDetected();
+
public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int targetUserId, long operationId, boolean restricted, @NonNull String owner,
@@ -180,7 +186,8 @@
+ ", isBP: " + isBiometricPrompt()
+ ", listener: " + listener
+ ", requireConfirmation: " + mRequireConfirmation
- + ", user: " + getTargetUserId());
+ + ", user: " + getTargetUserId()
+ + ", clientMonitor: " + toString());
final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
if (isCryptoOperation()) {
@@ -304,6 +311,11 @@
public void handleLifecycleAfterAuth() {
AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */);
}
+
+ @Override
+ public void sendAuthenticationCanceled() {
+ sendCancelOnly(listener);
+ }
});
} else {
// Allow system-defined limit of number of attempts before giving up
@@ -338,10 +350,30 @@
public void handleLifecycleAfterAuth() {
AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
}
+
+ @Override
+ public void sendAuthenticationCanceled() {
+ sendCancelOnly(listener);
+ }
});
}
}
+ private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) {
+ if (listener == null) {
+ Slog.e(TAG, "Unable to sendAuthenticationCanceled, listener null");
+ return;
+ }
+ try {
+ listener.onError(getSensorId(),
+ getCookie(),
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ 0 /* vendorCode */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
@Override
public void onAcquired(int acquiredInfo, int vendorCode) {
super.onAcquired(acquiredInfo, vendorCode);
@@ -355,9 +387,11 @@
}
@Override
- public void onError(int errorCode, int vendorCode) {
+ public void onError(@BiometricConstants.Errors int errorCode, int vendorCode) {
super.onError(errorCode, vendorCode);
mState = STATE_STOPPED;
+
+ CoexCoordinator.getInstance().onAuthenticationError(this, errorCode, this::vibrateError);
}
/**
@@ -419,4 +453,8 @@
public boolean interruptsPrecedingClients() {
return true;
}
+
+ public boolean wasAuthAttempted() {
+ return mAuthAttempted;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
index a15ecad..25d4a38 100644
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
import android.os.Looper;
import android.util.Slog;
@@ -54,7 +55,7 @@
/**
* Callback interface notifying the owner of "results" from the CoexCoordinator's business
- * logic.
+ * logic for accept and reject.
*/
interface Callback {
/**
@@ -73,6 +74,22 @@
* from scheduler if auth was successful).
*/
void handleLifecycleAfterAuth();
+
+ /**
+ * Requests the owner to notify the caller that authentication was canceled.
+ */
+ void sendAuthenticationCanceled();
+ }
+
+ /**
+ * Callback interface notifying the owner of "results" from the CoexCoordinator's business
+ * logic for errors.
+ */
+ interface ErrorCallback {
+ /**
+ * Requests the owner to initiate a vibration for this event.
+ */
+ void sendHapticFeedback();
}
private static CoexCoordinator sInstance;
@@ -198,6 +215,9 @@
mClientMap.remove(sensorType);
}
+ /**
+ * Notify the coordinator that authentication succeeded (accepted)
+ */
public void onAuthenticationSucceeded(long currentTimeMillis,
@NonNull AuthenticationClient<?> client,
@NonNull Callback callback) {
@@ -268,6 +288,9 @@
}
}
+ /**
+ * Notify the coordinator that a rejection has occurred.
+ */
public void onAuthenticationRejected(long currentTimeMillis,
@NonNull AuthenticationClient<?> client,
@LockoutTracker.LockoutMode int lockoutMode,
@@ -352,11 +375,63 @@
}
}
+ /**
+ * Notify the coordinator that an error has occurred.
+ */
+ public void onAuthenticationError(@NonNull AuthenticationClient<?> client,
+ @BiometricConstants.Errors int error, @NonNull ErrorCallback callback) {
+ // Figure out non-coex state
+ final boolean shouldUsuallyVibrate;
+ if (isCurrentFaceAuth(client)) {
+ final boolean notDetectedOnKeyguard = client.isKeyguard() && !client.wasUserDetected();
+ final boolean authAttempted = client.wasAuthAttempted();
+
+ switch (error) {
+ case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
+ case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
+ case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
+ shouldUsuallyVibrate = authAttempted && !notDetectedOnKeyguard;
+ break;
+ default:
+ shouldUsuallyVibrate = false;
+ break;
+ }
+ } else {
+ shouldUsuallyVibrate = false;
+ }
+
+ // Figure out coex state
+ final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
+ final boolean hapticSuppressedByCoex;
+
+ if (keyguardAdvancedLogic) {
+ if (isSingleAuthOnly(client)) {
+ hapticSuppressedByCoex = false;
+ } else {
+ hapticSuppressedByCoex = isCurrentFaceAuth(client)
+ && !client.isKeyguardBypassEnabled();
+ }
+ } else {
+ hapticSuppressedByCoex = false;
+ }
+
+ // Combine and send feedback if appropriate
+ Slog.d(TAG, "shouldUsuallyVibrate: " + shouldUsuallyVibrate
+ + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
+ if (shouldUsuallyVibrate && !hapticSuppressedByCoex) {
+ callback.sendHapticFeedback();
+ }
+ }
+
@Nullable
private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) {
for (SuccessfulAuth auth : mSuccessfulAuths) {
if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) {
- Slog.d(TAG, "Removing stale auth: " + auth);
+ // TODO(b/193089985): This removes the auth but does not notify the client with
+ // an appropriate lifecycle event (such as ERROR_CANCELED), and violates the
+ // API contract. However, this might be OK for now since the validity duration
+ // is way longer than the time it takes to auth with fingerprint.
+ Slog.e(TAG, "Removing stale auth: " + auth);
mSuccessfulAuths.remove(auth);
} else if (auth.mSensorType == SENSOR_TYPE_FACE) {
mSuccessfulAuths.remove(auth);
@@ -367,9 +442,13 @@
}
private void removeAndFinishAllFaceFromQueue() {
+ // Note that these auth are all successful, but have never notified the client (e.g.
+ // keyguard). To comply with the authentication lifecycle, we must notify the client that
+ // auth is "done". The safest thing to do is to send ERROR_CANCELED.
for (SuccessfulAuth auth : mSuccessfulAuths) {
if (auth.mSensorType == SENSOR_TYPE_FACE) {
- Slog.d(TAG, "Removing from queue and finishing: " + auth);
+ Slog.d(TAG, "Removing from queue, canceling, and finishing: " + auth);
+ auth.mCallback.sendAuthenticationCanceled();
auth.mCallback.handleLifecycleAfterAuth();
mSuccessfulAuths.remove(auth);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index f7fd8d0..d66a279 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -127,7 +127,8 @@
}
}
- private boolean wasUserDetected() {
+ @Override
+ public boolean wasUserDetected() {
// Do not provide haptic feedback if the user was not detected, and an error (usually
// ERROR_TIMEOUT) is received.
return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
@@ -160,7 +161,7 @@
}
@Override
- public void onError(int error, int vendorCode) {
+ public void onError(@BiometricConstants.Errors int error, int vendorCode) {
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
getStartTimeMs(),
System.currentTimeMillis() - getStartTimeMs() /* latency */,
@@ -169,25 +170,8 @@
vendorCode,
getTargetUserId()));
- switch (error) {
- case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
- if (!wasUserDetected() && !isBiometricPrompt()) {
- // No vibration if user was not detected on keyguard
- break;
- }
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
- if (mAuthAttempted) {
- // Only vibrate if auth was attempted. If the user was already locked out prior
- // to starting authentication, do not vibrate.
- vibrateError();
- }
- break;
- case BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL:
- BiometricNotificationUtils.showReEnrollmentNotification(getContext());
- break;
- default:
- break;
+ if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) {
+ BiometricNotificationUtils.showReEnrollmentNotification(getContext());
}
super.onError(error, vendorCode);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index c33b957..33950af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -115,7 +115,8 @@
}
}
- private boolean wasUserDetected() {
+ @Override
+ public boolean wasUserDetected() {
// Do not provide haptic feedback if the user was not detected, and an error (usually
// ERROR_TIMEOUT) is received.
return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
@@ -147,7 +148,7 @@
}
@Override
- public void onError(int error, int vendorCode) {
+ public void onError(@BiometricConstants.Errors int error, int vendorCode) {
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
getStartTimeMs(),
System.currentTimeMillis() - getStartTimeMs() /* latency */,
@@ -156,24 +157,6 @@
vendorCode,
getTargetUserId()));
- switch (error) {
- case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
- if (!wasUserDetected() && !isBiometricPrompt()) {
- // No vibration if user was not detected on keyguard
- break;
- }
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
- if (mAuthAttempted) {
- // Only vibrate if auth was attempted. If the user was already locked out prior
- // to starting authentication, do not vibrate.
- vibrateError();
- }
- break;
- default:
- break;
- }
-
super.onError(error, vendorCode);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 8835c1e0..37ee76a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -106,6 +106,12 @@
}
@Override
+ public boolean wasUserDetected() {
+ // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
+ return false;
+ }
+
+ @Override
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
boolean authenticated, ArrayList<Byte> token) {
super.onAuthenticated(identifier, authenticated, token);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 83f1480..5060744 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -153,6 +153,12 @@
}
@Override
+ public boolean wasUserDetected() {
+ // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
+ return false;
+ }
+
+ @Override
public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
return super.handleFailedAttempt(userId);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index bf82bd8..88dd033 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -176,6 +176,10 @@
name = orig.name;
realName = orig.realName;
doCopy(orig);
+ // Clone the user states.
+ for (int i = 0; i < mUserState.size(); i++) {
+ mUserState.put(mUserState.keyAt(i), new PackageUserState(mUserState.valueAt(i)));
+ }
}
public void setInstallerPackageName(String packageName) {
@@ -314,6 +318,7 @@
void setInstalled(boolean inst, int userId) {
modifyUserState(userId).installed = inst;
+ onChanged();
}
boolean getInstalled(int userId) {
@@ -326,6 +331,7 @@
void setInstallReason(int installReason, int userId) {
modifyUserState(userId).installReason = installReason;
+ onChanged();
}
int getUninstallReason(int userId) {
@@ -334,10 +340,13 @@
void setUninstallReason(@UninstallReason int uninstallReason, int userId) {
modifyUserState(userId).uninstallReason = uninstallReason;
+ onChanged();
}
boolean setOverlayPaths(OverlayPaths overlayPaths, int userId) {
- return modifyUserState(userId).setOverlayPaths(overlayPaths);
+ boolean returnValue = modifyUserState(userId).setOverlayPaths(overlayPaths);
+ onChanged();
+ return returnValue;
}
OverlayPaths getOverlayPaths(int userId) {
@@ -346,7 +355,10 @@
boolean setOverlayPathsForLibrary(String libName, OverlayPaths overlayPaths,
int userId) {
- return modifyUserState(userId).setSharedLibraryOverlayPaths(libName, overlayPaths);
+ boolean returnValue = modifyUserState(userId)
+ .setSharedLibraryOverlayPaths(libName, overlayPaths);
+ onChanged();
+ return returnValue;
}
Map<String, OverlayPaths> getOverlayPathsForLibrary(int userId) {
@@ -395,6 +407,7 @@
void setCeDataInode(long ceDataInode, int userId) {
modifyUserState(userId).ceDataInode = ceDataInode;
+ onChanged();
}
boolean getStopped(int userId) {
@@ -403,6 +416,7 @@
void setStopped(boolean stop, int userId) {
modifyUserState(userId).stopped = stop;
+ onChanged();
}
boolean getNotLaunched(int userId) {
@@ -411,6 +425,7 @@
void setNotLaunched(boolean stop, int userId) {
modifyUserState(userId).notLaunched = stop;
+ onChanged();
}
boolean getHidden(int userId) {
@@ -419,6 +434,7 @@
void setHidden(boolean hidden, int userId) {
modifyUserState(userId).hidden = hidden;
+ onChanged();
}
int getDistractionFlags(int userId) {
@@ -427,6 +443,7 @@
void setDistractionFlags(int distractionFlags, int userId) {
modifyUserState(userId).distractionFlags = distractionFlags;
+ onChanged();
}
boolean getSuspended(int userId) {
@@ -487,6 +504,7 @@
void setInstantApp(boolean instantApp, int userId) {
modifyUserState(userId).instantApp = instantApp;
+ onChanged();
}
boolean getVirtulalPreload(int userId) {
@@ -495,6 +513,7 @@
void setVirtualPreload(boolean virtualPreload, int userId) {
modifyUserState(userId).virtualPreload = virtualPreload;
+ onChanged();
}
void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
@@ -547,20 +566,24 @@
void setEnabledComponents(ArraySet<String> components, int userId) {
modifyUserState(userId).enabledComponents = components;
+ onChanged();
}
void setDisabledComponents(ArraySet<String> components, int userId) {
modifyUserState(userId).disabledComponents = components;
+ onChanged();
}
void setEnabledComponentsCopy(ArraySet<String> components, int userId) {
modifyUserState(userId).enabledComponents = components != null
? new ArraySet<String>(components) : null;
+ onChanged();
}
void setDisabledComponentsCopy(ArraySet<String> components, int userId) {
modifyUserState(userId).disabledComponents = components != null
? new ArraySet<String>(components) : null;
+ onChanged();
}
PackageUserState modifyUserStateComponents(int userId, boolean disabled, boolean enabled) {
@@ -582,10 +605,12 @@
void addDisabledComponent(String componentClassName, int userId) {
modifyUserStateComponents(userId, true, false).disabledComponents.add(componentClassName);
+ onChanged();
}
void addEnabledComponent(String componentClassName, int userId) {
modifyUserStateComponents(userId, false, true).enabledComponents.add(componentClassName);
+ onChanged();
}
boolean enableComponentLPw(String componentClassName, int userId) {
@@ -593,6 +618,9 @@
boolean changed = state.disabledComponents != null
? state.disabledComponents.remove(componentClassName) : false;
changed |= state.enabledComponents.add(componentClassName);
+ if (changed) {
+ onChanged();
+ }
return changed;
}
@@ -601,6 +629,9 @@
boolean changed = state.enabledComponents != null
? state.enabledComponents.remove(componentClassName) : false;
changed |= state.disabledComponents.add(componentClassName);
+ if (changed) {
+ onChanged();
+ }
return changed;
}
@@ -610,6 +641,9 @@
? state.disabledComponents.remove(componentClassName) : false;
changed |= state.enabledComponents != null
? state.enabledComponents.remove(componentClassName) : false;
+ if (changed) {
+ onChanged();
+ }
return changed;
}
@@ -701,6 +735,7 @@
PackageSettingBase setPath(@NonNull File path) {
this.mPath = path;
this.mPathString = path.toString();
+ onChanged();
return this;
}
@@ -722,7 +757,9 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component,
@Nullable String label, @Nullable Integer icon, @UserIdInt int userId) {
- return modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+ boolean returnValue = modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+ onChanged();
+ return returnValue;
}
/**
@@ -732,6 +769,7 @@
*/
public void resetOverrideComponentLabelIcon(@UserIdInt int userId) {
modifyUserState(userId).resetOverrideComponentLabelIcon();
+ onChanged();
}
/**
@@ -741,6 +779,7 @@
*/
public void setSplashScreenTheme(@UserIdInt int userId, @Nullable String themeName) {
modifyUserState(userId).splashScreenTheme = themeName;
+ onChanged();
}
/**
@@ -776,6 +815,7 @@
*/
public void setStatesOnCommit() {
incrementalStates.onCommit(IncrementalManager.isIncrementalPath(getPathString()));
+ onChanged();
}
/**
@@ -783,6 +823,7 @@
*/
public void setIncrementalStatesCallback(IncrementalStates.Callback callback) {
incrementalStates.setCallback(callback);
+ onChanged();
}
/**
@@ -791,6 +832,7 @@
*/
public void setLoadingProgress(float progress) {
incrementalStates.setProgress(progress);
+ onChanged();
}
public long getFirstInstallTime() {
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index c12eb32..e98a4dd 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -64,12 +64,16 @@
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED));
+ eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED));
+ eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
// Stop watching
appOpsManager.stopWatchingStarted(listener);
@@ -94,7 +98,9 @@
.times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED));
+ eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
verifyNoMoreInteractions(listener);
// Finish up
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 8592166a..f4d1499 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -378,6 +378,11 @@
protected void handleLifecycleAfterAuth(boolean authenticated) {
}
+
+ @Override
+ public boolean wasUserDetected() {
+ return false;
+ }
}
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
@@ -407,6 +412,11 @@
protected void handleLifecycleAfterAuth(boolean authenticated) {
}
+
+ @Override
+ public boolean wasUserDetected() {
+ return false;
+ }
}
private static class TestClientMonitor2 extends TestClientMonitor {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
index 7f5f3c2..bfb0be7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
@@ -32,6 +32,7 @@
import static org.mockito.Mockito.withSettings;
import android.content.Context;
+import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
@@ -61,6 +62,8 @@
private Context mContext;
@Mock
private CoexCoordinator.Callback mCallback;
+ @Mock
+ private CoexCoordinator.ErrorCallback mErrorCallback;
@Before
public void setUp() {
@@ -255,13 +258,16 @@
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+ // For easier reading
+ final CoexCoordinator.Callback faceCallback = mCallback;
+
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
- mCallback);
- verify(mCallback, never()).sendHapticFeedback();
- verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
+ faceCallback);
+ verify(faceCallback, never()).sendHapticFeedback();
+ verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
// CoexCoordinator requests the system to hold onto this AuthenticationClient until
// UDFPS result is known
- verify(mCallback, never()).handleLifecycleAfterAuth();
+ verify(faceCallback, never()).handleLifecycleAfterAuth();
// Reset the mock
CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
@@ -274,6 +280,8 @@
verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */);
verify(udfpsCallback).handleLifecycleAfterAuth();
+ verify(faceCallback).sendAuthenticationCanceled();
+
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
} else {
mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient,
@@ -281,16 +289,16 @@
if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) {
verify(udfpsCallback, never()).sendHapticFeedback();
- verify(mCallback).sendHapticFeedback();
- verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
- verify(mCallback).handleLifecycleAfterAuth();
+ verify(faceCallback).sendHapticFeedback();
+ verify(faceCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
+ verify(faceCallback).handleLifecycleAfterAuth();
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
} else {
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
- verify(mCallback, never()).sendHapticFeedback();
- verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
+ verify(faceCallback, never()).sendHapticFeedback();
+ verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
verify(udfpsCallback).sendHapticFeedback();
verify(udfpsCallback)
@@ -485,4 +493,82 @@
verify(callback).handleLifecycleAfterAuth();
verify(successfulAuths).remove(eq(auth));
}
+
+ @Test
+ public void testBiometricPrompt_FaceError() {
+ mCoexCoordinator.reset();
+
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ when(client.isBiometricPrompt()).thenReturn(true);
+ when(client.wasAuthAttempted()).thenReturn(true);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+
+ mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ mErrorCallback);
+ verify(mErrorCallback).sendHapticFeedback();
+ }
+
+ @Test
+ public void testKeyguard_faceAuthOnly_errorWhenBypassEnabled() {
+ testKeyguard_faceAuthOnly(true /* bypassEnabled */);
+ }
+
+ @Test
+ public void testKeyguard_faceAuthOnly_errorWhenBypassDisabled() {
+ testKeyguard_faceAuthOnly(false /* bypassEnabled */);
+ }
+
+ private void testKeyguard_faceAuthOnly(boolean bypassEnabled) {
+ mCoexCoordinator.reset();
+
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ when(client.isKeyguard()).thenReturn(true);
+ when(client.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(client.wasAuthAttempted()).thenReturn(true);
+ when(client.wasUserDetected()).thenReturn(true);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+
+ mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ mErrorCallback);
+ verify(mErrorCallback).sendHapticFeedback();
+ }
+
+ @Test
+ public void testKeyguard_coex_faceErrorWhenBypassEnabled() {
+ testKeyguard_coex_faceError(true /* bypassEnabled */);
+ }
+
+ @Test
+ public void testKeyguard_coex_faceErrorWhenBypassDisabled() {
+ testKeyguard_coex_faceError(false /* bypassEnabled */);
+ }
+
+ private void testKeyguard_coex_faceError(boolean bypassEnabled) {
+ mCoexCoordinator.reset();
+
+ AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
+ when(faceClient.isKeyguard()).thenReturn(true);
+ when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(faceClient.wasAuthAttempted()).thenReturn(true);
+ when(faceClient.wasUserDetected()).thenReturn(true);
+
+ AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
+ withSettings().extraInterfaces(Udfps.class));
+ when(udfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+
+ mCoexCoordinator.onAuthenticationError(faceClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+
+ if (bypassEnabled) {
+ verify(mErrorCallback).sendHapticFeedback();
+ } else {
+ verify(mErrorCallback, never()).sendHapticFeedback();
+ }
+ }
}