Merge "Don't collapse the panel post launch if we went back to sleep." into tm-dev
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 5b7c125..7f5d4a3 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -94,7 +94,7 @@
":framework-scheduling-sources",
":framework-sdkextensions-sources",
":framework-statsd-sources",
- ":framework-supplementalprocess-sources",
+ ":framework-sdksandbox-sources",
":framework-tethering-srcs",
":framework-uwb-updatable-sources",
":framework-wifi-updatable-sources",
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index ffa534e..c83ca8c 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -1377,7 +1377,7 @@
}
private boolean isAllowedBlobAccess(int uid, String packageName) {
- return (!Process.isSupplemental(uid) && !Process.isIsolated(uid)
+ return (!Process.isSdkSandboxUid(uid) && !Process.isIsolated(uid)
&& !mPackageManagerInternal.isInstantApp(packageName, UserHandle.getUserId(uid)));
}
diff --git a/api/Android.bp b/api/Android.bp
index 9e377a0..464c163 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -123,7 +123,7 @@
"framework-scheduling",
"framework-sdkextensions",
"framework-statsd",
- "framework-supplementalprocess",
+ "framework-sdksandbox",
"framework-tethering",
"framework-uwb",
"framework-wifi",
@@ -136,7 +136,7 @@
system_server_classpath: [
"service-media-s",
"service-permission",
- "service-supplementalprocess",
+ "service-sdksandbox",
],
}
diff --git a/cmds/svc/src/com/android/commands/svc/BluetoothCommand.java b/cmds/svc/src/com/android/commands/svc/BluetoothCommand.java
deleted file mode 100644
index b572ce2..0000000
--- a/cmds/svc/src/com/android/commands/svc/BluetoothCommand.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 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.commands.svc;
-
-import android.bluetooth.BluetoothAdapter;
-import android.os.RemoteException;
-
-public class BluetoothCommand extends Svc.Command {
-
- public BluetoothCommand() {
- super("bluetooth");
- }
-
- @Override
- public String shortHelp() {
- return "Control Bluetooth service";
- }
-
- @Override
- public String longHelp() {
- return shortHelp() + "\n"
- + "\n"
- + "usage: svc bluetooth [enable|disable]\n"
- + " Turn Bluetooth on or off.\n\n";
- }
-
- @Override
- public void run(String[] args) {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-
- if (adapter == null) {
- System.err.println("Got a null BluetoothAdapter, is the system running?");
- return;
- }
-
- if (args.length == 2 && "enable".equals(args[1])) {
- adapter.enable();
- } else if (args.length == 2 && "disable".equals(args[1])) {
- adapter.disable();
- } else {
- System.err.println(longHelp());
- }
- }
-}
diff --git a/cmds/svc/src/com/android/commands/svc/Svc.java b/cmds/svc/src/com/android/commands/svc/Svc.java
index 2ed2678..bbad984 100644
--- a/cmds/svc/src/com/android/commands/svc/Svc.java
+++ b/cmds/svc/src/com/android/commands/svc/Svc.java
@@ -96,7 +96,7 @@
// `svc wifi` has been migrated to WifiShellCommand
new UsbCommand(),
new NfcCommand(),
- new BluetoothCommand(),
+ // `svc bluetooth` has been migrated to BluetoothShellCommand
new SystemServerCommand(),
};
}
diff --git a/cmds/svc/svc b/cmds/svc/svc
index 95265e8..a2c9de3 100755
--- a/cmds/svc/svc
+++ b/cmds/svc/svc
@@ -33,6 +33,25 @@
exit 1
fi
+# `svc bluetooth` has been migrated to BluetoothShellCommand,
+# simply perform translation to `cmd bluetooth set-bluetooth-enabled` here.
+if [ "x$1" == "xbluetooth" ]; then
+ # `cmd wifi` by convention uses enabled/disabled
+ # instead of enable/disable
+ if [ "x$2" == "xenable" ]; then
+ exec cmd bluetooth_manager enable
+ elif [ "x$2" == "xdisable" ]; then
+ exec cmd bluetooth_manager disable
+ else
+ echo "Control the Bluetooth manager"
+ echo ""
+ echo "usage: svc bluetooth [enable|disable]"
+ echo " Turn Bluetooth on or off."
+ echo ""
+ fi
+ exit 1
+fi
+
export CLASSPATH=/system/framework/svc.jar
exec app_process /system/bin com.android.commands.svc.Svc "$@"
diff --git a/core/api/current.txt b/core/api/current.txt
index 27a45ac..d8ea767 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7388,8 +7388,8 @@
method public int getCurrentFailedPasswordAttempts();
method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
- method @Nullable public String getDeviceManagerRoleHolderPackageName();
method public CharSequence getDeviceOwnerLockScreenInfo();
+ method @Nullable public String getDevicePolicyManagementRoleHolderPackage();
method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.function.Supplier<android.graphics.drawable.Drawable>);
method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.function.Supplier<android.graphics.drawable.Drawable>);
method @Nullable public android.graphics.drawable.Icon getDrawableAsIcon(@NonNull String, @NonNull String, @NonNull String, @Nullable android.graphics.drawable.Icon);
@@ -30867,6 +30867,7 @@
field public static final int PREVIEW_SDK_INT;
field public static final String RELEASE;
field @NonNull public static final String RELEASE_OR_CODENAME;
+ field @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY;
field @Deprecated public static final String SDK;
field public static final int SDK_INT;
field public static final String SECURITY_PATCH;
@@ -31858,7 +31859,7 @@
method public static final boolean is64Bit();
method public static boolean isApplicationUid(int);
method public static final boolean isIsolated();
- method public static final boolean isSupplemental();
+ method public static final boolean isSdkSandbox();
method public static final void killProcess(int);
method public static final int myPid();
method @NonNull public static String myProcessName();
@@ -40754,8 +40755,9 @@
method public String getDefaultDialerPackage();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS) public java.util.List<android.telecom.PhoneAccountHandle> getOwnSelfManagedPhoneAccounts();
method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
- method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_OWN_CALLS}) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
method public android.telecom.PhoneAccountHandle getSimCallManager();
method @Nullable public android.telecom.PhoneAccountHandle getSimCallManagerForSubscription(int);
method @Nullable public String getSystemDialerPackage();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 7ec239d..a268f85 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -120,7 +120,7 @@
public abstract class PackageManager {
method @NonNull public String getPermissionControllerPackageName();
- method @NonNull public String getSupplementalProcessPackageName();
+ method @NonNull public String getSdkSandboxPackageName();
field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000
}
@@ -362,9 +362,9 @@
}
public class Process {
- method public static final boolean isSupplemental(int);
+ method public static final boolean isSdkSandboxUid(int);
method public static final int toAppUid(int);
- method public static final int toSupplementalUid(int);
+ method public static final int toSdkSandboxUid(int);
field public static final int NFC_UID = 1027; // 0x403
field public static final int VPN_UID = 1016; // 0x3f8
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e611d6b..1c373b2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -416,7 +416,7 @@
field public static final int config_defaultCallScreening = 17039398; // 0x1040026
field public static final int config_defaultDialer = 17039395; // 0x1040023
field public static final int config_defaultSms = 17039396; // 0x1040024
- field public static final int config_deviceManager;
+ field public static final int config_devicePolicyManagement;
field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
field public static final int config_feedbackIntentNameKey = 17039392; // 0x1040020
field public static final int config_helpIntentExtraKey = 17039389; // 0x104001d
@@ -6329,7 +6329,6 @@
method public int describeContents();
method @NonNull public String getMediaRoute2Id();
method public int getRangeZone();
- method @NonNull public static String rangeZoneToString(int);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.NearbyDevice> CREATOR;
field public static final int RANGE_CLOSE = 3; // 0x3
@@ -10524,6 +10523,7 @@
field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
+ field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -15701,6 +15701,11 @@
package android.view {
+ @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method @NonNull public final java.util.List<android.graphics.Rect> getUnrestrictedPreferKeepClearRects();
+ method @RequiresPermission(android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS) public final void setUnrestrictedPreferKeepClearRects(@NonNull java.util.List<android.graphics.Rect>);
+ }
+
public abstract class Window {
method public void addSystemFlags(@android.view.WindowManager.LayoutParams.SystemFlags int);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 22637ca..c36b94b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -155,8 +155,10 @@
public class ActivityOptions {
method @NonNull public static android.app.ActivityOptions fromBundle(@NonNull android.os.Bundle);
+ method public boolean isEligibleForLegacyPermissionPrompt();
method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
+ method public void setEligibleForLegacyPermissionPrompt(boolean);
method public static void setExitTransitionTimeout(long);
method public void setLaunchActivityType(int);
method public void setLaunchWindowingMode(int);
@@ -514,6 +516,7 @@
method public boolean isCurrentInputMethodSetByOwner();
method public boolean isFactoryResetProtectionPolicySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged();
+ method public boolean isRemovingAdmin(@NonNull android.content.ComponentName, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
method @NonNull public static String operationSafetyReasonToString(int);
method @NonNull public static String operationToString(int);
@@ -826,9 +829,9 @@
method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags, int);
method @Nullable public abstract String[] getNamesForUids(int[]);
method @NonNull public String getPermissionControllerPackageName();
+ method @NonNull public String getSdkSandboxPackageName();
method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
- method @NonNull public String getSupplementalProcessPackageName();
method @Nullable public String getSystemTextClassifierPackageName();
method @Nullable public String getWellbeingPackageName();
method public void holdLock(android.os.IBinder, int);
@@ -1774,7 +1777,7 @@
public class Process {
method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException;
- method public static final int toSupplementalUid(int);
+ method public static final int toSdkSandboxUid(int);
field public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; // 0x15f90
field public static final int FIRST_ISOLATED_UID = 99000; // 0x182b8
field public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999; // 0x182b7
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 5ddaa80..1d14307 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -174,6 +174,13 @@
public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
/**
+ * Indicates that this activity launch is eligible to show a legacy permission prompt
+ * @hide
+ */
+ public static final String KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE =
+ "android:activity.legacyPermissionPromptEligible";
+
+ /**
* Callback for when the last frame of the animation is played.
* @hide
*/
@@ -445,6 +452,7 @@
private String mSplashScreenThemeResName;
@SplashScreen.SplashScreenStyle
private int mSplashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
+ private boolean mIsEligibleForLegacyPermissionPrompt;
private boolean mRemoveWithTaskOrganizer;
private boolean mLaunchedFromBubble;
private boolean mTransientLaunch;
@@ -1243,6 +1251,8 @@
mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
mSplashScreenStyle = opts.getInt(KEY_SPLASH_SCREEN_STYLE);
mLaunchIntoPipParams = opts.getParcelable(KEY_LAUNCH_INTO_PIP_PARAMS);
+ mIsEligibleForLegacyPermissionPrompt =
+ opts.getBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE);
}
/**
@@ -1474,6 +1484,24 @@
}
/**
+ * Whether the activity is eligible to show a legacy permission prompt
+ * @hide
+ */
+ @TestApi
+ public boolean isEligibleForLegacyPermissionPrompt() {
+ return mIsEligibleForLegacyPermissionPrompt;
+ }
+
+ /**
+ * Sets whether the activity is eligible to show a legacy permission prompt
+ * @hide
+ */
+ @TestApi
+ public void setEligibleForLegacyPermissionPrompt(boolean eligible) {
+ mIsEligibleForLegacyPermissionPrompt = eligible;
+ }
+
+ /**
* Sets whether the activity is to be launched into LockTask mode.
*
* Use this option to start an activity in LockTask mode. Note that only apps permitted by
@@ -1909,6 +1937,7 @@
mSpecsFuture = otherOptions.mSpecsFuture;
mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams;
+ mIsEligibleForLegacyPermissionPrompt = otherOptions.mIsEligibleForLegacyPermissionPrompt;
}
/**
@@ -2084,6 +2113,10 @@
if (mLaunchIntoPipParams != null) {
b.putParcelable(KEY_LAUNCH_INTO_PIP_PARAMS, mLaunchIntoPipParams);
}
+ if (mIsEligibleForLegacyPermissionPrompt) {
+ b.putBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE,
+ mIsEligibleForLegacyPermissionPrompt);
+ }
return b;
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 20ffa25..dca5c54 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -165,50 +165,35 @@
public static final String PERMISSION_CONTROLLER_RESOURCE_PACKAGE =
"com.android.permissioncontroller";
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private UserManager mUserManager;
- @GuardedBy("mLock")
- private PermissionManager mPermissionManager;
- @GuardedBy("mLock")
- private PackageInstaller mInstaller;
- @GuardedBy("mLock")
- private ArtManager mArtManager;
- @GuardedBy("mLock")
- private DevicePolicyManager mDevicePolicyManager;
+ private volatile UserManager mUserManager;
+ private volatile PermissionManager mPermissionManager;
+ private volatile PackageInstaller mInstaller;
+ private volatile ArtManager mArtManager;
+ private volatile DevicePolicyManager mDevicePolicyManager;
+ private volatile String mPermissionsControllerPackageName;
@GuardedBy("mDelegates")
private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
- @GuardedBy("mLock")
- private String mPermissionsControllerPackageName;
-
UserManager getUserManager() {
- synchronized (mLock) {
- if (mUserManager == null) {
- mUserManager = UserManager.get(mContext);
- }
- return mUserManager;
+ if (mUserManager == null) {
+ mUserManager = UserManager.get(mContext);
}
+ return mUserManager;
}
DevicePolicyManager getDevicePolicyManager() {
- synchronized (mLock) {
- if (mDevicePolicyManager == null) {
- mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
- }
- return mDevicePolicyManager;
+ if (mDevicePolicyManager == null) {
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
}
+ return mDevicePolicyManager;
}
private PermissionManager getPermissionManager() {
- synchronized (mLock) {
- if (mPermissionManager == null) {
- mPermissionManager = mContext.getSystemService(PermissionManager.class);
- }
- return mPermissionManager;
+ if (mPermissionManager == null) {
+ mPermissionManager = mContext.getSystemService(PermissionManager.class);
}
+ return mPermissionManager;
}
@Override
@@ -851,25 +836,23 @@
*/
@Override
public String getPermissionControllerPackageName() {
- synchronized (mLock) {
- if (mPermissionsControllerPackageName == null) {
- try {
- mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (mPermissionsControllerPackageName == null) {
+ try {
+ mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- return mPermissionsControllerPackageName;
}
+ return mPermissionsControllerPackageName;
}
/**
* @hide
*/
@Override
- public String getSupplementalProcessPackageName() {
+ public String getSdkSandboxPackageName() {
try {
- return mPM.getSupplementalProcessPackageName();
+ return mPM.getSdkSandboxPackageName();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3235,17 +3218,15 @@
@Override
public PackageInstaller getPackageInstaller() {
- synchronized (mLock) {
- if (mInstaller == null) {
- try {
- mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
- mContext.getPackageName(), mContext.getAttributionTag(), getUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (mInstaller == null) {
+ try {
+ mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
+ mContext.getPackageName(), mContext.getAttributionTag(), getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- return mInstaller;
}
+ return mInstaller;
}
@Override
@@ -3583,16 +3564,14 @@
@Override
public ArtManager getArtManager() {
- synchronized (mLock) {
- if (mArtManager == null) {
- try {
- mArtManager = new ArtManager(mContext, mPM.getArtManager());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ if (mArtManager == null) {
+ try {
+ mArtManager = new ArtManager(mContext, mPM.getArtManager());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- return mArtManager;
}
+ return mArtManager;
}
@Override
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 87ba197..cedf483e 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -184,8 +184,17 @@
})
@interface LockTypes {}
- // TODO(b/220379118): register only one binder listener and keep a map of listener to executor.
- private final ArrayMap<KeyguardLockedStateListener, IKeyguardLockedStateListener>
+ private final IKeyguardLockedStateListener mIKeyguardLockedStateListener =
+ new IKeyguardLockedStateListener.Stub() {
+ @Override
+ public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
+ mKeyguardLockedStateListeners.forEach((listener, executor) -> {
+ executor.execute(
+ () -> listener.onKeyguardLockedStateChanged(isKeyguardLocked));
+ });
+ }
+ };
+ private final ArrayMap<KeyguardLockedStateListener, Executor>
mKeyguardLockedStateListeners = new ArrayMap<>();
/**
@@ -1102,17 +1111,12 @@
public void addKeyguardLockedStateListener(@NonNull @CallbackExecutor Executor executor,
@NonNull KeyguardLockedStateListener listener) {
synchronized (mKeyguardLockedStateListeners) {
+ mKeyguardLockedStateListeners.put(listener, executor);
+ if (mKeyguardLockedStateListeners.size() > 1) {
+ return;
+ }
try {
- final IKeyguardLockedStateListener innerListener =
- new IKeyguardLockedStateListener.Stub() {
- @Override
- public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
- executor.execute(
- () -> listener.onKeyguardLockedStateChanged(isKeyguardLocked));
- }
- };
- mWM.addKeyguardLockedStateListener(innerListener);
- mKeyguardLockedStateListeners.put(listener, innerListener);
+ mWM.addKeyguardLockedStateListener(mIKeyguardLockedStateListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1125,17 +1129,15 @@
@RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
public void removeKeyguardLockedStateListener(@NonNull KeyguardLockedStateListener listener) {
synchronized (mKeyguardLockedStateListeners) {
- IKeyguardLockedStateListener innerListener = mKeyguardLockedStateListeners.get(
- listener);
- if (innerListener == null) {
+ mKeyguardLockedStateListeners.remove(listener);
+ if (!mKeyguardLockedStateListeners.isEmpty()) {
return;
}
try {
- mWM.removeKeyguardLockedStateListener(innerListener);
+ mWM.removeKeyguardLockedStateListener(mIKeyguardLockedStateListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- mKeyguardLockedStateListeners.remove(listener);
}
}
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index e460638..c6e36a3 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -335,7 +335,7 @@
* Constant for {@link #setNavBarMode(int)} indicating kids navbar mode.
*
* <p>When used, back and home icons will change drawables and layout, recents will be hidden,
- * and the navbar will remain visible when apps are in immersive mode.
+ * and enables the setting to force navbar visible, even when apps are in immersive mode.
*
* @hide
*/
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index eeb4705..58db93c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -18,6 +18,7 @@
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
+import android.adservices.AdServicesFrameworkInitializer;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -36,6 +37,7 @@
import android.app.people.PeopleManager;
import android.app.prediction.AppPredictionManager;
import android.app.role.RoleFrameworkInitializer;
+import android.app.sdksandbox.SdkSandboxManagerFrameworkInitializer;
import android.app.search.SearchUiManager;
import android.app.slice.SliceManager;
import android.app.smartspace.SmartspaceManager;
@@ -208,7 +210,6 @@
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
import android.service.vr.IVrManager;
-import android.supplementalprocess.SupplementalProcessFrameworkInitializer;
import android.telecom.TelecomManager;
import android.telephony.MmsManager;
import android.telephony.TelephonyFrameworkInitializer;
@@ -1564,7 +1565,8 @@
MediaFrameworkInitializer.registerServiceWrappers();
RoleFrameworkInitializer.registerServiceWrappers();
SchedulingFrameworkInitializer.registerServiceWrappers();
- SupplementalProcessFrameworkInitializer.registerServiceWrappers();
+ SdkSandboxManagerFrameworkInitializer.registerServiceWrappers();
+ AdServicesFrameworkInitializer.registerServiceWrappers();
UwbFrameworkInitializer.registerServiceWrappers();
SafetyCenterFrameworkInitializer.registerServiceWrappers();
ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers();
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index b791f05..5c1ab38 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -841,15 +841,6 @@
}
/**
- * Returns true if the windowingMode represents a split window.
- * @hide
- */
- public static boolean isSplitScreenWindowingMode(int windowingMode) {
- return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
-
- /**
* Returns true if the windows associated with this window configuration can receive input keys.
* @hide
*/
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b944468..a2df2d0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -545,15 +545,16 @@
= "android.app.action.PROVISION_FINALIZATION";
/**
- * Activity action: starts the managed profile provisioning flow inside the device management
- * role holder.
+ * Activity action: starts the managed profile provisioning flow inside the device policy
+ * management role holder.
*
* <p>During the managed profile provisioning flow, the platform-provided provisioning handler
- * will delegate provisioning to the device management role holder, by firing this intent.
- * Third-party mobile device management applications attempting to fire this intent will
+ * will delegate provisioning to the device policy management role holder, by firing this
+ * intent. Third-party mobile device management applications attempting to fire this intent will
* receive a {@link SecurityException}.
*
- * <p>Device management role holders are required to have a handler for this intent action.
+ * <p>Device policy management role holders are required to have a handler for this intent
+ * action.
*
* <p>If {@link #EXTRA_ROLE_HOLDER_STATE} is supplied to this intent, it is the responsibility
* of the role holder to restore its state from this extra. This is the same {@link Bundle}
@@ -596,15 +597,16 @@
public static final int RESULT_DEVICE_OWNER_SET = 123;
/**
- * Activity action: starts the trusted source provisioning flow inside the device management
- * role holder.
+ * Activity action: starts the trusted source provisioning flow inside the device policy
+ * management role holder.
*
* <p>During the trusted source provisioning flow, the platform-provided provisioning handler
- * will delegate provisioning to the device management role holder, by firing this intent.
- * Third-party mobile device management applications attempting to fire this intent will
+ * will delegate provisioning to the device policy management role holder, by firing this
+ * intent. Third-party mobile device management applications attempting to fire this intent will
* receive a {@link SecurityException}.
*
- * <p>Device management role holders are required to have a handler for this intent action.
+ * <p>Device policy management role holders are required to have a handler for this intent
+ * action.
*
* <p>If {@link #EXTRA_ROLE_HOLDER_STATE} is supplied to this intent, it is the responsibility
* of the role holder to restore its state from this extra. This is the same {@link Bundle}
@@ -624,15 +626,16 @@
"android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
/**
- * Activity action: starts the provisioning finalization flow inside the device management
- * role holder.
+ * Activity action: starts the provisioning finalization flow inside the device policy
+ * management role holder.
*
* <p>During the provisioning finalization flow, the platform-provided provisioning handler
- * will delegate provisioning to the device management role holder, by firing this intent.
- * Third-party mobile device management applications attempting to fire this intent will
+ * will delegate provisioning to the device policy management role holder, by firing this
+ * intent. Third-party mobile device management applications attempting to fire this intent will
* receive a {@link SecurityException}.
*
- * <p>Device management role holders are required to have a handler for this intent action.
+ * <p>Device policy management role holders are required to have a handler for this intent
+ * action.
*
* <p>This handler forwards the result from the admin app's {@link
* #ACTION_ADMIN_POLICY_COMPLIANCE} handler. Result code {@link Activity#RESULT_CANCELED}
@@ -697,9 +700,9 @@
* A boolean extra indicating whether offline provisioning is allowed.
*
* <p>For the online provisioning flow, there will be an attempt to download and install
- * the latest version of the device management role holder. The platform will then delegate
- * provisioning to the device management role holder via role holder-specific provisioning
- * actions.
+ * the latest version of the device policy management role holder. The platform will then
+ * delegate provisioning to the device policy management role holder via role holder-specific
+ * provisioning actions.
*
* <p>For the offline provisioning flow, the provisioning flow will always be handled by
* the platform.
@@ -720,8 +723,8 @@
"android.app.extra.PROVISIONING_ALLOW_OFFLINE";
/**
- * A String extra holding a url that specifies the download location of the device manager
- * role holder package.
+ * A String extra holding a url that specifies the download location of the device policy
+ * management role holder package.
*
* <p>This is only meant to be used in cases when a specific variant of the role holder package
* is needed (such as a debug variant). If not provided, the default variant of the device
@@ -777,13 +780,13 @@
/**
* An extra of type {@link android.os.PersistableBundle} that allows the provisioning initiator
- * to pass data to the device manager role holder.
+ * to pass data to the device policy management role holder.
*
- * <p>The device manager role holder will receive this extra via the {@link
+ * <p>The device policy management role holder will receive this extra via the {@link
* #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent.
*
* <p>The contents of this extra are up to the contract between the provisioning initiator
- * and the device manager role holder.
+ * and the device policy management role holder.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
* or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
@@ -814,18 +817,19 @@
* <p>If {@link #EXTRA_PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT} is set to {@code false},
* this result will be supplied as part of the result {@link Intent} for provisioning actions
* such as {@link #ACTION_PROVISION_MANAGED_PROFILE}. This result will also be supplied as
- * part of the result {@link Intent} for the device manager role holder provisioning actions.
+ * part of the result {@link Intent} for the device policy management role holder provisioning
+ * actions.
*/
public static final String EXTRA_RESULT_LAUNCH_INTENT =
"android.app.extra.RESULT_LAUNCH_INTENT";
/**
* A boolean extra that determines whether the provisioning flow should launch the resulting
- * launch intent, if one is supplied by the device manager role holder via {@link
+ * launch intent, if one is supplied by the device policy management role holder via {@link
* #EXTRA_RESULT_LAUNCH_INTENT}. Default value is {@code false}.
*
* <p>If {@code true}, the resulting intent will be launched by the provisioning flow, if one
- * is supplied by the device manager role holder.
+ * is supplied by the device policy management role holder.
*
* <p>If {@code false}, the resulting intent will be returned as {@link
* #EXTRA_RESULT_LAUNCH_INTENT} to the provisioning initiator, if one is supplied by the device
@@ -3220,10 +3224,10 @@
"android.app.action.ADMIN_POLICY_COMPLIANCE";
/**
- * Activity action: Starts the device management role holder updater.
+ * Activity action: Starts the device policy management role holder updater.
*
- * <p>The activity must handle the device management role holder update and set the intent
- * result to either {@link Activity#RESULT_OK} if the update was successful, {@link
+ * <p>The activity must handle the device policy management role holder update and set the
+ * intent result to either {@link Activity#RESULT_OK} if the update was successful, {@link
* #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if it encounters a problem
* that may be solved by relaunching it again, or {@link
* #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters a problem
@@ -3261,9 +3265,9 @@
/**
* An {@link Intent} extra which resolves to a custom user consent screen.
*
- * <p>If this extra is provided to the device management role holder via either {@link
+ * <p>If this extra is provided to the device policy management role holder via either {@link
* #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
- * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE}, the device management role holder must
+ * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE}, the device policy management role holder must
* launch this intent which shows the custom user consent screen, replacing its own standard
* consent screen.
*
@@ -3279,9 +3283,9 @@
* </ul>
*
* <p>If the custom consent screens are granted by the user {@link Activity#RESULT_OK} is
- * returned, otherwise {@link Activity#RESULT_CANCELED} is returned. The device management
- * role holder should ensure that the provisioning flow terminates immediately if consent
- * is not granted by the user.
+ * returned, otherwise {@link Activity#RESULT_CANCELED} is returned. The device policy
+ * management role holder should ensure that the provisioning flow terminates immediately if
+ * consent is not granted by the user.
*
* @hide
*/
@@ -3762,6 +3766,7 @@
* for the user.
* @hide
*/
+ @TestApi
public boolean isRemovingAdmin(@NonNull ComponentName admin, int userId) {
if (mService != null) {
try {
@@ -15647,14 +15652,15 @@
}
/**
- * Returns the package name of the device manager role holder.
+ * Returns the package name of the device policy management role holder.
*
- * <p>If the device manager role holder is not configured for this device, returns {@code null}.
+ * <p>If the device policy management role holder is not configured for this device, returns
+ * {@code null}.
*/
@Nullable
- public String getDeviceManagerRoleHolderPackageName() {
- String deviceManagerConfig =
- mContext.getString(com.android.internal.R.string.config_deviceManager);
+ public String getDevicePolicyManagementRoleHolderPackage() {
+ String deviceManagerConfig = mContext.getString(
+ com.android.internal.R.string.config_devicePolicyManagement);
return extractPackageNameFromDeviceManagerConfig(deviceManagerConfig);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 30aed8b..e9e6cd3 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -653,7 +653,7 @@
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
String getPermissionControllerPackageName();
- String getSupplementalProcessPackageName();
+ String getSdkSandboxPackageName();
ParceledListSlice getInstantApps(int userId);
byte[] getInstantAppCookie(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a162c41..f4bc161 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5772,16 +5772,16 @@
}
/**
- * Returns the package name of the component implementing supplemental process service.
+ * Returns the package name of the component implementing sdk sandbox service.
*
- * @return the package name of the component implementing supplemental process service
+ * @return the package name of the component implementing sdk sandbox service
*
* @hide
*/
@NonNull
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@TestApi
- public String getSupplementalProcessPackageName() {
+ public String getSdkSandboxPackageName() {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ebef053..a03286d3 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2674,9 +2674,8 @@
// Putting into a map keyed on the apk assets to deduplicate resources that are different
// objects but ultimately represent the same assets
Map<List<ApkAssets>, Resources> history = new ArrayMap<>();
- for (Resources r : sResourcesHistory) {
- history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r);
- }
+ sResourcesHistory.forEach(
+ r -> history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r));
int i = 0;
for (Resources r : history.values()) {
if (r != null) {
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index ada5155..3f139f0 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -104,16 +104,16 @@
public static final int BIOMETRIC_MULTI_SENSOR_DEFAULT = 0;
/**
- * Prefer the face sensor and fall back to fingerprint when needed.
+ * Use face and fingerprint sensors together.
* @hide
*/
- public static final int BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT = 1;
+ public static final int BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE = 1;
/**
* @hide
*/
@IntDef({BIOMETRIC_MULTI_SENSOR_DEFAULT,
- BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT})
+ BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE})
@Retention(RetentionPolicy.SOURCE)
public @interface BiometricMultiSensorMode {}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 2c3c8c3..42aad36 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -63,7 +63,7 @@
// Notify BiometricService when <Biometric>Service is ready to start the prepared client.
// Client lifecycle is still managed in <Biometric>Service.
- void onReadyForAuthentication(int cookie);
+ void onReadyForAuthentication(long requestId, int cookie);
// Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the
// specified user. This happens when enrollments have been added on devices with multiple
diff --git a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
index 5d9b5f3..450c5ce 100644
--- a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
@@ -30,6 +30,4 @@
void onSystemEvent(int event);
// Notifies that the dialog has finished animating.
void onDialogAnimatedIn();
- // For multi-sensor devices, notifies that the fingerprint should start now.
- void onStartFingerprintNow();
}
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index c8b4226e..07fbe4a 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -17,9 +17,11 @@
package android.os;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ProcessInfo;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.Zygote;
import dalvik.system.VMRuntime;
@@ -45,8 +47,6 @@
// Last UID/GID of the range the AppZygote can setuid()/setgid() to
private final int mZygoteUidGidMax;
- private final int mZygoteRuntimeFlags;
-
private final Object mLock = new Object();
/**
@@ -57,14 +57,15 @@
private ChildZygoteProcess mZygote;
private final ApplicationInfo mAppInfo;
+ private final ProcessInfo mProcessInfo;
- public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax,
- int runtimeFlags) {
+ public AppZygote(ApplicationInfo appInfo, ProcessInfo processInfo, int zygoteUid, int uidGidMin,
+ int uidGidMax) {
mAppInfo = appInfo;
+ mProcessInfo = processInfo;
mZygoteUid = zygoteUid;
mZygoteUidGidMin = uidGidMin;
mZygoteUidGidMax = uidGidMax;
- mZygoteRuntimeFlags = runtimeFlags;
}
/**
@@ -108,13 +109,15 @@
String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
Build.SUPPORTED_ABIS[0];
try {
+ int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
+ mAppInfo, mProcessInfo);
mZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.AppZygoteInit",
mAppInfo.processName + "_zygote",
mZygoteUid,
mZygoteUid,
null, // gids
- mZygoteRuntimeFlags, // runtimeFlags
+ runtimeFlags,
"app_zygote", // seInfo
abi, // abi
abi, // acceptedAbiList
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1d1f17d..1c85f69 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -285,13 +285,20 @@
public static final String RELEASE = getString("ro.build.version.release");
/**
- * The version string we show to the user; may be {@link #RELEASE} or
- * {@link #CODENAME} if not a final release build.
+ * The version string. May be {@link #RELEASE} or {@link #CODENAME} if
+ * not a final release build.
*/
@NonNull public static final String RELEASE_OR_CODENAME = getString(
"ro.build.version.release_or_codename");
/**
+ * The version string we show to the user; may be {@link #RELEASE} or
+ * a descriptive string if not a final release build.
+ */
+ @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY = getString(
+ "ro.build.version.release_or_preview_display");
+
+ /**
* The base OS build the product is based on.
*/
public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", "");
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 3d12941..79fa4fb 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -29,7 +29,6 @@
import android.compat.annotation.Disabled;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.storage.StorageManager;
@@ -1333,7 +1332,7 @@
final Context context = AppGlobals.getInitialApplication();
final int uid = context.getApplicationInfo().uid;
// Isolated processes and Instant apps are never allowed to be in scoped storage
- if (Process.isIsolated(uid) || Process.isSupplemental(uid)) {
+ if (Process.isIsolated(uid) || Process.isSdkSandboxUid(uid)) {
return false;
}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 5d9f2189..8e5ed8f 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -52,6 +52,9 @@
per-file *Telephony* = file:/telephony/OWNERS
per-file *Zygote* = file:/ZYGOTE_OWNERS
+# Time
+per-file *Clock* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
+
# RecoverySystem
per-file *Recovery* = file:/services/core/java/com/android/server/recoverysystem/OWNERS
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 17b5ec5..f069158c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -281,23 +281,23 @@
/**
* Defines the start of a range of UIDs going from this number to
- * {@link #LAST_SUPPLEMENTAL_UID} that are reserved for assigning to
- * supplemental processes. There is a 1-1 mapping between a supplemental
+ * {@link #LAST_SDK_SANDBOX_UID} that are reserved for assigning to
+ * sdk sandbox processes. There is a 1-1 mapping between a sdk sandbox
* process UID and the app that it belongs to, which can be computed by
- * subtracting (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID) from the
- * uid of a supplemental process.
+ * subtracting (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID) from the
+ * uid of a sdk sandbox process.
*
* Note that there are no GIDs associated with these processes; storage
* attribution for them will be done using project IDs.
* @hide
*/
- public static final int FIRST_SUPPLEMENTAL_UID = 20000;
+ public static final int FIRST_SDK_SANDBOX_UID = 20000;
/**
- * Last UID that is used for supplemental processes.
+ * Last UID that is used for sdk sandbox processes.
* @hide
*/
- public static final int LAST_SUPPLEMENTAL_UID = 29999;
+ public static final int LAST_SDK_SANDBOX_UID = 29999;
/**
* First uid used for fully isolated sandboxed processes spawned from an app zygote
@@ -901,44 +901,44 @@
}
/**
- * Returns whether the provided UID belongs to a supplemental process.
+ * Returns whether the provided UID belongs to a SDK sandbox process.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
- public static final boolean isSupplemental(int uid) {
+ public static final boolean isSdkSandboxUid(int uid) {
uid = UserHandle.getAppId(uid);
- return (uid >= FIRST_SUPPLEMENTAL_UID && uid <= LAST_SUPPLEMENTAL_UID);
+ return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
}
/**
*
- * Returns the app process corresponding to a supplemental process.
+ * Returns the app process corresponding to a sdk sandbox process.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public static final int toAppUid(int uid) {
- return uid - (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID);
+ return uid - (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
}
/**
*
- * Returns the supplemental process corresponding to an app process.
+ * Returns the sdk sandbox process corresponding to an app process.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
@TestApi
- public static final int toSupplementalUid(int uid) {
- return uid + (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID);
+ public static final int toSdkSandboxUid(int uid) {
+ return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
}
/**
- * Returns whether the current process is a supplemental process.
+ * Returns whether the current process is a sdk sandbox process.
*/
- public static final boolean isSupplemental() {
- return isSupplemental(myUid());
+ public static final boolean isSdkSandbox() {
+ return isSdkSandboxUid(myUid());
}
/**
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index 58ca978..2d287e9 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -108,7 +108,7 @@
Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP,
VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId");
Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale");
- Preconditions.checkArgumentNonnegative(mDelay, "primitive delay should be >= 0");
+ VibrationEffectSegment.checkDurationArgument(mDelay, "delay");
}
@Override
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index 9e1f636..d7d8c49 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -108,14 +108,9 @@
/** @hide */
@Override
public void validate() {
- Preconditions.checkArgumentNonNegative(mStartFrequencyHz,
- "Frequencies must all be >= 0, got start frequency of " + mStartFrequencyHz);
- Preconditions.checkArgumentFinite(mStartFrequencyHz, "startFrequencyHz");
- Preconditions.checkArgumentNonNegative(mEndFrequencyHz,
- "Frequencies must all be >= 0, got end frequency of " + mEndFrequencyHz);
- Preconditions.checkArgumentFinite(mEndFrequencyHz, "endFrequencyHz");
- Preconditions.checkArgumentNonnegative(mDuration,
- "Durations must all be >= 0, got " + mDuration);
+ VibrationEffectSegment.checkFrequencyArgument(mStartFrequencyHz, "startFrequencyHz");
+ VibrationEffectSegment.checkFrequencyArgument(mEndFrequencyHz, "endFrequencyHz");
+ VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude");
Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude");
}
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index c679511..5a0bbf7 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -95,11 +95,8 @@
/** @hide */
@Override
public void validate() {
- Preconditions.checkArgumentNonNegative(mFrequencyHz,
- "Frequencies must all be >= 0, got " + mFrequencyHz);
- Preconditions.checkArgumentFinite(mFrequencyHz, "frequencyHz");
- Preconditions.checkArgumentNonnegative(mDuration,
- "Durations must all be >= 0, got " + mDuration);
+ VibrationEffectSegment.checkFrequencyArgument(mFrequencyHz, "frequencyHz");
+ VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude");
}
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 979c447..be10553 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -112,6 +112,43 @@
@NonNull
public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
+ /**
+ * Checks the given frequency argument is valid to represent a vibration effect frequency in
+ * hertz, i.e. a finite non-negative value.
+ *
+ * @param value the frequency argument value to be checked
+ * @param name the argument name for the error message.
+ *
+ * @hide
+ */
+ public static void checkFrequencyArgument(float value, @NonNull String name) {
+ // Similar to combining Preconditions checkArgumentFinite + checkArgumentNonnegative,
+ // but this implementation doesn't create the error message unless a check fail.
+ if (Float.isNaN(value)) {
+ throw new IllegalArgumentException(name + " must not be NaN");
+ }
+ if (Float.isInfinite(value)) {
+ throw new IllegalArgumentException(name + " must not be infinite");
+ }
+ if (value < 0) {
+ throw new IllegalArgumentException(name + " must be >= 0, got " + value);
+ }
+ }
+
+ /**
+ * Checks the given duration argument is valid, i.e. a non-negative value.
+ *
+ * @param value the duration value to be checked
+ * @param name the argument name for the error message.
+ *
+ * @hide
+ */
+ public static void checkDurationArgument(long value, @NonNull String name) {
+ if (value < 0) {
+ throw new IllegalArgumentException(name + " must be >= 0, got " + value);
+ }
+ }
+
@NonNull
public static final Creator<VibrationEffectSegment> CREATOR =
new Creator<VibrationEffectSegment>() {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8feff16..f2137b3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -240,6 +240,20 @@
"android.settings.TETHER_PROVISIONING_UI";
/**
+ * Activity Action: Show a dialog activity to notify tethering is NOT supported by carrier.
+ *
+ * When {@link android.telephony.CarrierConfigManager#KEY_CARRIER_SUPPORTS_TETHERING_BOOL}
+ * is false, and tethering is started by Settings, this dialog activity will be started to
+ * tell the user that tethering is not supported by carrier.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI =
+ "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
+
+ /**
* Activity Action: Show settings to allow entering/exiting airplane mode.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -10274,6 +10288,14 @@
"theme_customization_overlay_packages";
/**
+ * Indicates whether the nav bar is forced to always be visible, even in immersive mode.
+ * <p>Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String NAV_BAR_FORCE_VISIBLE = "nav_bar_force_visible";
+
+ /**
* Indicates whether the device is in kids nav mode.
* <p>Type: int (0 for false, 1 for true)
*
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 188bc3f..4541f3a 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -95,7 +95,7 @@
DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
- DEFAULT_FLAGS.put("settings_search_always_expand", "false");
+ DEFAULT_FLAGS.put("settings_search_always_expand", "true");
DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "false");
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 8604078..40beab3 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -265,6 +265,13 @@
return mTimeResult;
}
+ /** Clears the last received NTP. Intended for use during tests. */
+ public void clearCachedTimeResult() {
+ synchronized (this) {
+ mTimeResult = null;
+ }
+ }
+
private static class NtpConnectionInfo {
@NonNull private final String mServer;
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index a266a28..5c7c844 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -290,7 +290,8 @@
/**
* Called when the keep-clear areas for this window have changed.
*/
- oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> keepClearRects);
+ oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> restricted,
+ in List<Rect> unrestricted);
/**
* Request the server to call setInputWindowInfo on a given Surface, and return
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index b1582cf..6aab635 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -98,6 +98,13 @@
*/
private boolean mIsAnimationPending;
+ /**
+ * @param type The {@link InternalInsetsType} of the consumed insets.
+ * @param state The current {@link InsetsState} of the consumed insets.
+ * @param transactionSupplier The source of new {@link Transaction} instances. The supplier
+ * must provide *new* instances, which will be explicitly closed by this class.
+ * @param controller The {@link InsetsController} to use for insets interaction.
+ */
public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state,
Supplier<Transaction> transactionSupplier, InsetsController controller) {
mType = type;
@@ -390,16 +397,17 @@
return;
}
- final Transaction t = mTransactionSupplier.get();
- if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
- if (mRequestedVisible) {
- t.show(mSourceControl.getLeash());
- } else {
- t.hide(mSourceControl.getLeash());
+ try (Transaction t = mTransactionSupplier.get()) {
+ if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
+ if (mRequestedVisible) {
+ t.show(mSourceControl.getLeash());
+ } else {
+ t.hide(mSourceControl.getLeash());
+ }
+ // Ensure the alpha value is aligned with the actual requested visibility.
+ t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
+ t.apply();
}
- // Ensure the alpha value is aligned with the actual requested visibility.
- t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
- t.apply();
onPerceptible(mRequestedVisible);
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 91d5b20..9aa243b 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -133,8 +133,9 @@
private boolean mDisableBackgroundLayer = false;
/**
- * We use this lock to protect access to mSurfaceControl. Both are accessed on the UI
- * thread and the render thread via RenderNode.PositionUpdateListener#positionLost.
+ * We use this lock to protect access to mSurfaceControl and
+ * SurfaceViewPositionUpdateListener#mPositionChangedTransaction. Both are accessed on the UI
+ * thread and the render thread.
*/
final Object mSurfaceControlLock = new Object();
final Rect mTmpRect = new Rect();
@@ -223,6 +224,12 @@
private final SurfaceControl.Transaction mFrameCallbackTransaction =
new SurfaceControl.Transaction();
+ /**
+ * A temporary transaction holder that should only be used when applying right away. There
+ * should be no assumption about thread safety for this transaction.
+ */
+ private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+
private int mParentSurfaceSequenceId;
private RemoteAccessibilityController mRemoteAccessibilityController =
@@ -753,7 +760,7 @@
mBlastBufferQueue = null;
}
- final Transaction transaction = new Transaction();
+ Transaction transaction = new Transaction();
if (mSurfaceControl != null) {
transaction.remove(mSurfaceControl);
mSurfaceControl = null;
@@ -783,18 +790,22 @@
// synchronously otherwise we may see flickers.
// When the listener is updated, we will get at least a single position update call so we can
// guarantee any changes we post will be applied.
- private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight) {
+ private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight,
+ Transaction geometryTransaction) {
if (mPositionListener != null) {
mRenderNode.removePositionUpdateListener(mPositionListener);
+ synchronized (mSurfaceControlLock) {
+ geometryTransaction = mPositionListener.getTransaction().merge(geometryTransaction);
+ }
}
mPositionListener = new SurfaceViewPositionUpdateListener(surfaceWidth, surfaceHeight,
- mSurfaceControl);
+ geometryTransaction);
mRenderNode.addPositionUpdateListener(mPositionListener);
}
private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
boolean creating, boolean sizeChanged, boolean hintChanged,
- Transaction surfaceUpdateTransaction) {
+ Transaction geometryTransaction) {
boolean realSizeChanged = false;
mSurfaceLock.lock();
@@ -809,60 +820,59 @@
// SurfaceChangedCallback to update the relative z. This is needed so that
// we do not change the relative z before the server is ready to swap the
// parent surface.
- if (creating) {
- updateRelativeZ(surfaceUpdateTransaction);
- if (mSurfacePackage != null) {
- reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
- }
+ if (creating || (mParentSurfaceSequenceId == viewRoot.getSurfaceSequenceId())) {
+ updateRelativeZ(mTmpTransaction);
}
mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
if (mViewVisibility) {
- surfaceUpdateTransaction.show(mSurfaceControl);
+ geometryTransaction.show(mSurfaceControl);
} else {
- surfaceUpdateTransaction.hide(mSurfaceControl);
+ geometryTransaction.hide(mSurfaceControl);
}
+ if (mSurfacePackage != null) {
+ reparentSurfacePackage(mTmpTransaction, mSurfacePackage);
+ }
-
- updateBackgroundVisibility(surfaceUpdateTransaction);
- updateBackgroundColor(surfaceUpdateTransaction);
+ updateBackgroundVisibility(mTmpTransaction);
+ updateBackgroundColor(mTmpTransaction);
if (mUseAlpha) {
float alpha = getFixedAlpha();
- surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
+ mTmpTransaction.setAlpha(mSurfaceControl, alpha);
mSurfaceAlpha = alpha;
}
- surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+ geometryTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
if ((sizeChanged || hintChanged) && !creating) {
- setBufferSize(surfaceUpdateTransaction);
+ setBufferSize(geometryTransaction);
}
if (sizeChanged || creating || !isHardwareAccelerated()) {
+ onSetSurfacePositionAndScaleRT(geometryTransaction, mSurfaceControl,
+ mScreenRect.left, /*positionLeft*/
+ mScreenRect.top /*positionTop*/ ,
+ mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+ mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
// Set a window crop when creating the surface or changing its size to
// crop the buffer to the surface size since the buffer producer may
// use SCALING_MODE_SCALE and submit a larger size than the surface
// size.
if (mClipSurfaceToBounds && mClipBounds != null) {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+ geometryTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
} else {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+ geometryTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
mSurfaceHeight);
}
- surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+ geometryTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
mSurfaceHeight);
if (isHardwareAccelerated()) {
// This will consume the passed in transaction and the transaction will be
// applied on a render worker thread.
- replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
- } else {
- onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
- mScreenRect.left /*positionLeft*/,
- mScreenRect.top /*positionTop*/,
- mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
- mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+ replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight,
+ geometryTransaction);
}
if (DEBUG_POSITION) {
Log.d(TAG, String.format(
@@ -874,7 +884,8 @@
mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
}
}
- applyTransactionOnVriDraw(surfaceUpdateTransaction);
+ mTmpTransaction.merge(geometryTransaction);
+ mTmpTransaction.apply();
updateEmbeddedAccessibilityMatrix();
mSurfaceFrame.left = 0;
@@ -982,17 +993,17 @@
mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
// Collect all geometry changes and apply these changes on the RenderThread worker
// via the RenderNode.PositionUpdateListener.
- final Transaction surfaceUpdateTransaction = new Transaction();
+ final Transaction geometryTransaction = new Transaction();
if (creating) {
updateOpaqueFlag();
final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
- createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
+ createBlastSurfaceControls(viewRoot, name, geometryTransaction);
} else if (mSurfaceControl == null) {
return;
}
final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
- translator, creating, sizeChanged, hintChanged, surfaceUpdateTransaction);
+ translator, creating, sizeChanged, hintChanged, geometryTransaction);
final boolean redrawNeeded = sizeChanged || creating || hintChanged
|| (mVisible && !mDrawFinished);
@@ -1128,7 +1139,7 @@
*
*/
private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name,
- Transaction surfaceUpdateTransaction) {
+ Transaction geometryTransaction) {
if (mSurfaceControl == null) {
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name)
@@ -1151,10 +1162,11 @@
.build();
} else {
// update blast layer
- surfaceUpdateTransaction
+ mTmpTransaction
.setOpaque(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
.setSecure(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.SECURE) != 0)
- .show(mBlastSurfaceControl);
+ .show(mBlastSurfaceControl)
+ .apply();
}
if (mBackgroundControl == null) {
@@ -1201,7 +1213,7 @@
*
* @hide
*/
- protected void onSetSurfacePositionAndScale(@NonNull Transaction transaction,
+ protected void onSetSurfacePositionAndScaleRT(@NonNull Transaction transaction,
@NonNull SurfaceControl surface, int positionLeft, int positionTop,
float postScaleX, float postScaleY) {
transaction.setPosition(surface, positionLeft, positionTop);
@@ -1214,14 +1226,12 @@
if (mSurfaceControl == null) {
return;
}
- final Transaction transaction = new Transaction();
- onSetSurfacePositionAndScale(transaction, mSurfaceControl,
+ onSetSurfacePositionAndScaleRT(mTmpTransaction, mSurfaceControl,
mScreenRect.left, /*positionLeft*/
mScreenRect.top/*positionTop*/ ,
mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
- applyTransactionOnVriDraw(transaction);
- invalidate();
+ mTmpTransaction.apply();
}
/**
@@ -1243,57 +1253,66 @@
}
}
- private final Rect mRTLastReportedPosition = new Rect();
- private final Point mRTLastReportedSurfaceSize = new Point();
+ private Rect mRTLastReportedPosition = new Rect();
+ private Point mRTLastReportedSurfaceSize = new Point();
private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener {
- private final int mRtSurfaceWidth;
- private final int mRtSurfaceHeight;
+ int mRtSurfaceWidth = -1;
+ int mRtSurfaceHeight = -1;
private final SurfaceControl.Transaction mPositionChangedTransaction =
new SurfaceControl.Transaction();
- private final SurfaceControl mRtSurfaceControl = new SurfaceControl();
+ boolean mPendingTransaction = false;
SurfaceViewPositionUpdateListener(int surfaceWidth, int surfaceHeight,
- SurfaceControl surfaceControl) {
+ @Nullable Transaction t) {
mRtSurfaceWidth = surfaceWidth;
mRtSurfaceHeight = surfaceHeight;
- mRtSurfaceControl.copyFrom(surfaceControl, "SurfaceViewPositionUpdateListener");
+ if (t != null) {
+ mPositionChangedTransaction.merge(t);
+ mPendingTransaction = true;
+ }
}
@Override
public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
- if (mRTLastReportedPosition.left == left
- && mRTLastReportedPosition.top == top
- && mRTLastReportedPosition.right == right
- && mRTLastReportedPosition.bottom == bottom
- && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth
- && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight) {
- return;
- }
- try {
- if (DEBUG_POSITION) {
- Log.d(TAG, String.format(
- "%d updateSurfacePosition RenderWorker, frameNr = %d, "
- + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
- System.identityHashCode(SurfaceView.this), frameNumber,
- left, top, right, bottom, mRtSurfaceWidth, mRtSurfaceHeight));
+ synchronized(mSurfaceControlLock) {
+ if (mSurfaceControl == null) {
+ return;
}
- mRTLastReportedPosition.set(left, top, right, bottom);
- mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight);
- onSetSurfacePositionAndScale(mPositionChangedTransaction, mRtSurfaceControl,
- mRTLastReportedPosition.left /*positionLeft*/,
- mRTLastReportedPosition.top /*positionTop*/,
- mRTLastReportedPosition.width()
- / (float) mRtSurfaceWidth /*postScaleX*/,
- mRTLastReportedPosition.height()
- / (float) mRtSurfaceHeight /*postScaleY*/);
- if (mViewVisibility) {
- // b/131239825
- mPositionChangedTransaction.show(mRtSurfaceControl);
+ if (mRTLastReportedPosition.left == left
+ && mRTLastReportedPosition.top == top
+ && mRTLastReportedPosition.right == right
+ && mRTLastReportedPosition.bottom == bottom
+ && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth
+ && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight
+ && !mPendingTransaction) {
+ return;
}
- applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
- } catch (Exception ex) {
- Log.e(TAG, "Exception from repositionChild", ex);
+ try {
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format(
+ "%d updateSurfacePosition RenderWorker, frameNr = %d, "
+ + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
+ System.identityHashCode(SurfaceView.this), frameNumber,
+ left, top, right, bottom, mRtSurfaceWidth, mRtSurfaceHeight));
+ }
+ mRTLastReportedPosition.set(left, top, right, bottom);
+ mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight);
+ onSetSurfacePositionAndScaleRT(mPositionChangedTransaction, mSurfaceControl,
+ mRTLastReportedPosition.left /*positionLeft*/,
+ mRTLastReportedPosition.top /*positionTop*/,
+ mRTLastReportedPosition.width()
+ / (float) mRtSurfaceWidth /*postScaleX*/,
+ mRTLastReportedPosition.height()
+ / (float) mRtSurfaceHeight /*postScaleY*/);
+ if (mViewVisibility) {
+ mPositionChangedTransaction.show(mSurfaceControl);
+ }
+ applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
+ mPendingTransaction = false;
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception from repositionChild", ex);
+ }
}
}
@@ -1302,7 +1321,7 @@
float vecX, float vecY, float maxStretchX, float maxStretchY,
float childRelativeLeft, float childRelativeTop, float childRelativeRight,
float childRelativeBottom) {
- mRtTransaction.setStretchEffect(mRtSurfaceControl, width, height, vecX, vecY,
+ mRtTransaction.setStretchEffect(mSurfaceControl, width, height, vecX, vecY,
maxStretchX, maxStretchY, childRelativeLeft, childRelativeTop,
childRelativeRight, childRelativeBottom);
applyOrMergeTransaction(mRtTransaction, frameNumber);
@@ -1317,14 +1336,28 @@
mRTLastReportedPosition.setEmpty();
mRTLastReportedSurfaceSize.set(-1, -1);
- // positionLost can be called while UI thread is un-paused.
+ /**
+ * positionLost can be called while UI thread is un-paused so we
+ * need to hold the lock here.
+ */
synchronized (mSurfaceControlLock) {
- if (mSurfaceControl == null) return;
- // b/131239825
+ if (mPendingTransaction) {
+ Log.w(TAG, System.identityHashCode(SurfaceView.this)
+ + "Pending transaction cleared.");
+ mPositionChangedTransaction.clear();
+ mPendingTransaction = false;
+ }
+ if (mSurfaceControl == null) {
+ return;
+ }
mRtTransaction.hide(mSurfaceControl);
applyOrMergeTransaction(mRtTransaction, frameNumber);
}
}
+
+ public Transaction getTransaction() {
+ return mPositionChangedTransaction;
+ }
}
private SurfaceViewPositionUpdateListener mPositionListener = null;
@@ -1371,10 +1404,8 @@
* @hide
*/
public void setResizeBackgroundColor(int bgColor) {
- final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
- setResizeBackgroundColor(transaction, bgColor);
- applyTransactionOnVriDraw(transaction);
- invalidate();
+ setResizeBackgroundColor(mTmpTransaction, bgColor);
+ mTmpTransaction.apply();
}
/**
diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
index 3e21103..e9c937cc 100644
--- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java
+++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
@@ -65,10 +65,12 @@
applyParams(t, params);
mTargetViewRootImpl.registerRtFrameCallback(frame -> {
- if (mTargetSc == null || !mTargetSc.isValid()) {
- return;
+ if (mTargetSc != null && mTargetSc.isValid()) {
+ applyTransaction(t, frame);
}
- applyTransaction(t, frame);
+ // The transaction was either dropped, successfully applied, or merged with a future
+ // transaction, so we can safely release its resources.
+ t.close();
});
// Make sure a frame gets scheduled.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 553c537..8b3a29a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -46,9 +46,11 @@
import android.annotation.LayoutRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.Size;
import android.annotation.StyleRes;
import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.annotation.UiThread;
@@ -4744,6 +4746,7 @@
*/
private List<Rect> mSystemGestureExclusionRects = null;
private List<Rect> mKeepClearRects = null;
+ private List<Rect> mUnrestrictedKeepClearRects = null;
private boolean mPreferKeepClear = false;
private Rect mHandwritingArea = null;
@@ -11729,6 +11732,7 @@
final ListenerInfo info = getListenerInfo();
if (getSystemGestureExclusionRects().isEmpty()
&& collectPreferKeepClearRects().isEmpty()
+ && collectUnrestrictedPreferKeepClearRects().isEmpty()
&& (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) {
if (info.mPositionUpdateListener != null) {
mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
@@ -11871,6 +11875,52 @@
return Collections.emptyList();
}
+ /**
+ * Set a preference to keep the provided rects clear from floating windows above this
+ * view's window. This informs the system that these rects are considered vital areas for the
+ * user and that ideally they should not be covered. Setting this is only appropriate for UI
+ * where the user would likely take action to uncover it.
+ * <p>
+ * Note: The difference with {@link #setPreferKeepClearRects} is that the system won't apply
+ * restrictions to the rects set here.
+ * <p>
+ * @see #setPreferKeepClear
+ * @see #getPreferKeepClearRects
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
+ public final void setUnrestrictedPreferKeepClearRects(@NonNull List<Rect> rects) {
+ final ListenerInfo info = getListenerInfo();
+ if (info.mUnrestrictedKeepClearRects != null) {
+ info.mUnrestrictedKeepClearRects.clear();
+ info.mUnrestrictedKeepClearRects.addAll(rects);
+ } else {
+ info.mUnrestrictedKeepClearRects = new ArrayList<>(rects);
+ }
+ updatePositionUpdateListener();
+ postUpdate(this::updateKeepClearRects);
+ }
+
+ /**
+ * @return the list of rects, set by {@link #setPreferKeepClearRects}.
+ *
+ * @see #setPreferKeepClearRects
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public final List<Rect> getUnrestrictedPreferKeepClearRects() {
+ final ListenerInfo info = mListenerInfo;
+ if (info != null && info.mKeepClearRects != null) {
+ return new ArrayList(info.mUnrestrictedKeepClearRects);
+ }
+
+ return Collections.emptyList();
+ }
+
void updateKeepClearRects() {
final AttachInfo ai = mAttachInfo;
if (ai != null) {
@@ -11899,6 +11949,20 @@
}
/**
+ * Retrieve the list of unrestricted areas within this view's post-layout coordinate space
+ * which the system will try to not cover with other floating elements, like the pip window.
+ */
+ @NonNull
+ List<Rect> collectUnrestrictedPreferKeepClearRects() {
+ final ListenerInfo info = mListenerInfo;
+ if (info != null && info.mUnrestrictedKeepClearRects != null) {
+ return info.mUnrestrictedKeepClearRects;
+ }
+
+ return Collections.emptyList();
+ }
+
+ /**
* Set a list of handwriting areas in this view. If there is any stylus {@link MotionEvent}
* occurs within those areas, it will trigger stylus handwriting mode. This can be disabled by
* disabling the auto handwriting initiation by calling
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8bb7e67..d3d3625 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -76,6 +76,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -220,6 +221,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -777,6 +779,8 @@
new ViewRootRectTracker(v -> v.getSystemGestureExclusionRects());
private final ViewRootRectTracker mKeepClearRectsTracker =
new ViewRootRectTracker(v -> v.collectPreferKeepClearRects());
+ private final ViewRootRectTracker mUnrestrictedKeepClearRectsTracker =
+ new ViewRootRectTracker(v -> v.collectUnrestrictedPreferKeepClearRects());
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
@@ -2462,8 +2466,9 @@
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
- childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
- childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+ childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
+ childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
+ lp.privateFlags);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight()
@@ -2476,7 +2481,7 @@
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ baseSize);
- childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
+ childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
@@ -2489,8 +2494,10 @@
}
if (!goodMeasure) {
- childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
- childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+ childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width,
+ lp.privateFlags);
+ childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
+ lp.privateFlags);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
@@ -3150,8 +3157,10 @@
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || dispatchApplyInsets ||
updatedConfiguration) {
- int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
- int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
+ int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
+ lp.privateFlags);
+ int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,
+ lp.privateFlags);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
@@ -3951,31 +3960,28 @@
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
- * @param windowSize
- * The available width or height of the window
- *
- * @param rootDimension
- * The layout params for one dimension (width or height) of the
- * window.
- *
+ * @param windowSize The available width or height of the window.
+ * @param measurement The layout width or height requested in the layout params.
+ * @param privateFlags The private flags in the layout params of the window.
* @return The measure spec to use to measure the root view.
*/
- private static int getRootMeasureSpec(int windowSize, int rootDimension) {
+ private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
int measureSpec;
+ final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
+ ? MATCH_PARENT : measurement;
switch (rootDimension) {
-
- case ViewGroup.LayoutParams.MATCH_PARENT:
- // Window can't resize. Force root view to be windowSize.
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
- break;
- case ViewGroup.LayoutParams.WRAP_CONTENT:
- // Window can resize. Set max size for root view.
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
- break;
- default:
- // Window wants to be an exact size. Force root view to be that size.
- measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
- break;
+ case ViewGroup.LayoutParams.MATCH_PARENT:
+ // Window can't resize. Force root view to be windowSize.
+ measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
+ break;
+ case ViewGroup.LayoutParams.WRAP_CONTENT:
+ // Window can resize. Set max size for root view.
+ measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
+ break;
+ default:
+ // Window wants to be an exact size. Force root view to be that size.
+ measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
+ break;
}
return measureSpec;
}
@@ -4175,7 +4181,7 @@
+ " didProduceBuffer=" + didProduceBuffer);
}
- Transaction tmpTransaction = new Transaction();
+ final Transaction tmpTransaction = new Transaction();
tmpTransaction.merge(mRtBLASTSyncTransaction);
// If frame wasn't drawn, clear out the next transaction so it doesn't affect the next
@@ -4206,6 +4212,7 @@
blastSyncConsumer.accept(mSurfaceChangedTransaction);
}
}
+ tmpTransaction.close();
if (reportNextDraw) {
pendingDrawFinished();
@@ -4875,14 +4882,26 @@
*/
void updateKeepClearRectsForView(View view) {
mKeepClearRectsTracker.updateRectsForView(view);
+ mUnrestrictedKeepClearRectsTracker.updateRectsForView(view);
mHandler.sendEmptyMessage(MSG_KEEP_CLEAR_RECTS_CHANGED);
}
void keepClearRectsChanged() {
- final List<Rect> rectsForWindowManager = mKeepClearRectsTracker.computeChangedRects();
- if (rectsForWindowManager != null && mView != null) {
+ List<Rect> restrictedKeepClearRects = mKeepClearRectsTracker.computeChangedRects();
+ List<Rect> unrestrictedKeepClearRects =
+ mUnrestrictedKeepClearRectsTracker.computeChangedRects();
+ if ((restrictedKeepClearRects != null || unrestrictedKeepClearRects != null)
+ && mView != null) {
+ if (restrictedKeepClearRects == null) {
+ restrictedKeepClearRects = Collections.emptyList();
+ }
+ if (unrestrictedKeepClearRects == null) {
+ unrestrictedKeepClearRects = Collections.emptyList();
+ }
+
try {
- mWindowSession.reportKeepClearAreasChanged(mWindow, rectsForWindowManager);
+ mWindowSession.reportKeepClearAreasChanged(mWindow, restrictedKeepClearRects,
+ unrestrictedKeepClearRects);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 27f89ec..ad9f21b 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.Gravity.DISPLAY_CLIP_HORIZONTAL;
+import static android.view.Gravity.DISPLAY_CLIP_VERTICAL;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -29,6 +31,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
@@ -250,11 +253,39 @@
Gravity.apply(attrs.gravity, w, h, outParentFrame,
(int) (x + attrs.horizontalMargin * pw),
(int) (y + attrs.verticalMargin * ph), outFrame);
+
// Now make sure the window fits in the overall display frame.
if (fitToDisplay) {
Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
}
+ if ((attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
+ && !cutout.isEmpty()) {
+ // If the actual frame covering a display cutout, and the window is requesting to extend
+ // it's requested frame, re-do the frame calculation after getting the new requested
+ // size.
+ mTempRect.set(outFrame);
+ // Do nothing if the display cutout and window don't overlap entirely. This may happen
+ // when the cutout is not on the same side with the window.
+ boolean shouldExpand = false;
+ final Rect [] cutoutBounds = cutout.getBoundingRectsAll();
+ for (Rect cutoutBound : cutoutBounds) {
+ if (cutoutBound.isEmpty()) continue;
+ if (mTempRect.contains(cutoutBound) || cutoutBound.contains(mTempRect)) {
+ shouldExpand = true;
+ break;
+ }
+ }
+ if (shouldExpand) {
+ // Try to fit move the bar to avoid the display cutout first. Make sure the clip
+ // flags are not set to make the window move.
+ final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL;
+ Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe,
+ mTempRect);
+ outFrame.union(mTempRect);
+ }
+ }
+
if (DEBUG) Log.d(TAG, "computeWindowFrames " + attrs.getTitle()
+ " outFrame=" + outFrame.toShortString()
+ " outParentFrame=" + outParentFrame.toShortString()
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 8ee3e43..dbfae46 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2385,6 +2385,16 @@
public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000;
/**
+ * Flag to indicate that the window frame should be the requested frame adding the display
+ * cutout frame. This will only be applied if a specific size smaller than the parent frame
+ * is given, and the window is covering the display cutout. The extended frame will not be
+ * larger than the parent frame.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 0x00002000;
+
+ /**
* Flag that will make window ignore app visibility and instead depend purely on the decor
* view visibility for determining window visibility. This is used by recents to keep
* drawing after it launches an app.
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index c81b8cc..a270c92 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -31,6 +31,7 @@
import android.window.IOnBackInvokedCallback;
import java.util.HashMap;
+import java.util.List;
import java.util.Objects;
/**
@@ -473,12 +474,12 @@
@Override
public void reportSystemGestureExclusionChanged(android.view.IWindow window,
- java.util.List<android.graphics.Rect> exclusionRects) {
+ List<Rect> exclusionRects) {
}
@Override
- public void reportKeepClearAreasChanged(android.view.IWindow window,
- java.util.List<android.graphics.Rect> exclusionRects) {
+ public void reportKeepClearAreasChanged(android.view.IWindow window, List<Rect> restrictedRects,
+ List<Rect> unrestrictedRects) {
}
@Override
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index 67d9667..62d029b 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -32,7 +32,7 @@
/**
* Enables window magnification on specified display with given center and scale and animation.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
* @param scale magnification scale.
* @param centerX the screen-relative X coordinate around which to center,
* or {@link Float#NaN} to leave unchanged.
@@ -51,7 +51,7 @@
/**
* Sets the scale of the window magnifier on specified display.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
* @param scale magnification scale.
*/
void setScale(int displayId, float scale);
@@ -59,7 +59,7 @@
/**
* Disables window magnification on specified display with animation.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
* @param callback The callback called when the animation is completed or interrupted.
*/
void disableWindowMagnification(int displayId,
@@ -68,6 +68,7 @@
/**
* Moves the window magnifier on the specified display. It has no effect while animating.
*
+ * @param displayId the logical display id.
* @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
* current screen pixels.
* @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in
@@ -76,9 +77,20 @@
void moveWindowMagnifier(int displayId, float offsetX, float offsetY);
/**
+ * Moves the window magnifier on the given display.
+ *
+ * @param displayId the logical display id.
+ * @param positionX the x-axis position of the center of the magnified source bounds.
+ * @param positionY the y-axis position of the center of the magnified source bounds.
+ * @param callback the callback called when the animation is completed or interrupted.
+ */
+ void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+ in IRemoteMagnificationAnimationCallback callback);
+
+ /**
* Requests System UI show magnification mode button UI on the specified display.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
* @param magnificationMode the current magnification mode.
*/
void showMagnificationButton(int displayId, int magnificationMode);
@@ -86,7 +98,7 @@
/**
* Requests System UI remove magnification mode button UI on the specified display.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id.
*/
void removeMagnificationButton(int displayId);
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
index 722546e..adfeb6d 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
@@ -68,12 +68,10 @@
void onAccessibilityActionPerformed(int displayId);
/**
- * Called when the user is performing dragging gesture. It is started after the offset
- * between the down location and the move event location exceed
- * {@link ViewConfiguration#getScaledTouchSlop()}.
+ * Called when the user is performing move action.
*
* @param displayId The logical display id.
*/
- void onDrag(int displayId);
+ void onMove(int displayId);
}
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 2bfbe4b..bc7a5fd 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -25,6 +25,7 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.Zygote;
/** @hide */
public class WebViewZygote {
@@ -127,13 +128,15 @@
try {
String abi = sPackage.applicationInfo.primaryCpuAbi;
+ int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
+ sPackage.applicationInfo, null);
sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.WebViewZygoteInit",
"webview_zygote",
Process.WEBVIEW_ZYGOTE_UID,
Process.WEBVIEW_ZYGOTE_UID,
null, // gids
- 0, // runtimeFlags
+ runtimeFlags,
"webview_zygote", // seInfo
abi, // abi
TextUtils.join(",", Build.SUPPORTED_ABIS),
diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java
index e4f483a..9712311 100644
--- a/core/java/android/widget/inline/InlineContentView.java
+++ b/core/java/android/widget/inline/InlineContentView.java
@@ -230,9 +230,8 @@
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes) {
- // b/219807628
@Override
- protected void onSetSurfacePositionAndScale(
+ protected void onSetSurfacePositionAndScaleRT(
@NonNull SurfaceControl.Transaction transaction,
@NonNull SurfaceControl surface, int positionLeft, int positionTop,
float postScaleX, float postScaleY) {
@@ -249,7 +248,7 @@
postScaleX = InlineContentView.this.getScaleX();
postScaleY = InlineContentView.this.getScaleY();
- super.onSetSurfacePositionAndScale(transaction, surface, positionLeft,
+ super.onSetSurfacePositionAndScaleRT(transaction, surface, positionLeft,
positionTop, postScaleX, postScaleY);
}
};
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 34a3418..232248b 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -65,6 +65,7 @@
import java.time.Duration;
import java.time.Instant;
import java.util.function.Consumer;
+import java.util.function.LongConsumer;
/**
* <p>The view which allows an activity to customize its splash screen exit animation.</p>
@@ -234,7 +235,7 @@
/**
* Set the animation duration if icon is animatable.
*/
- public Builder setAnimationDurationMillis(int duration) {
+ public Builder setAnimationDurationMillis(long duration) {
mIconAnimationDuration = Duration.ofMillis(duration);
return this;
}
@@ -521,8 +522,11 @@
});
}
- private void animationStartCallback() {
+ private void animationStartCallback(long animDuration) {
mIconAnimationStart = Instant.now();
+ if (animDuration > 0) {
+ mIconAnimationDuration = Duration.ofMillis(animDuration);
+ }
}
/**
@@ -693,9 +697,8 @@
* Prepare the animation if this drawable also be animatable.
* @param duration The animation duration.
* @param startListener The callback listener used to receive the start of the animation.
- * @return true if this drawable object can also be animated and it can be played now.
*/
- boolean prepareAnimate(long duration, Runnable startListener);
+ void prepareAnimate(long duration, LongConsumer startListener);
/**
* Stop animation.
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 346aa33..35c9594 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -31,6 +31,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
+import android.view.InsetsState;
import android.view.SurfaceControl;
import java.util.ArrayList;
@@ -589,6 +590,56 @@
}
/**
+ * Adds a given {@code Rect} as a rect insets provider on the {@code receiverWindowContainer}.
+ * This will trigger a change of insets for all the children in the subtree of
+ * {@code receiverWindowContainer}.
+ *
+ * @param receiverWindowContainer the window container which the insets provider need to be
+ * added to
+ * @param insetsProviderFrame the frame that will be added as Insets provider
+ * @param insetsTypes types of insets the rect provides
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction addRectInsetsProvider(
+ @NonNull WindowContainerToken receiverWindowContainer,
+ @NonNull Rect insetsProviderFrame,
+ @InsetsState.InternalInsetsType int[] insetsTypes) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER)
+ .setContainer(receiverWindowContainer.asBinder())
+ .setInsetsProviderFrame(insetsProviderFrame)
+ .setInsetsTypes(insetsTypes)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * Removes the insets provider for the given types from the
+ * {@code receiverWindowContainer}. This will trigger a change of insets for all the children
+ * in the subtree of {@code receiverWindowContainer}.
+ *
+ * @param receiverWindowContainer the window container which the insets-override-provider has
+ * to be removed from
+ * @param insetsTypes types of insets that have to be removed
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction removeInsetsProvider(
+ @NonNull WindowContainerToken receiverWindowContainer,
+ @InsetsState.InternalInsetsType int[] insetsTypes) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER)
+ .setContainer(receiverWindowContainer.asBinder())
+ .setInsetsTypes(insetsTypes)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* When this {@link WindowContainerTransaction} failed to finish on the server side, it will
* trigger callback with this {@param errorCallbackToken}.
* @param errorCallbackToken client provided token that will be passed back as parameter in
@@ -993,6 +1044,8 @@
public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS = 13;
public static final int HIERARCHY_OP_TYPE_START_SHORTCUT = 14;
public static final int HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER = 15;
+ public static final int HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER = 16;
+ public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1012,6 +1065,10 @@
@Nullable
private IBinder mReparent;
+ private @InsetsState.InternalInsetsType int[] mInsetsTypes;
+
+ private Rect mInsetsProviderFrame;
+
// Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
private boolean mToTop;
@@ -1130,6 +1187,8 @@
mType = copy.mType;
mContainer = copy.mContainer;
mReparent = copy.mReparent;
+ mInsetsTypes = copy.mInsetsTypes;
+ mInsetsProviderFrame = copy.mInsetsProviderFrame;
mToTop = copy.mToTop;
mReparentTopOnly = copy.mReparentTopOnly;
mMoveAdjacentTogether = copy.mMoveAdjacentTogether;
@@ -1146,6 +1205,12 @@
mType = in.readInt();
mContainer = in.readStrongBinder();
mReparent = in.readStrongBinder();
+ mInsetsTypes = in.createIntArray();
+ if (in.readInt() != 0) {
+ mInsetsProviderFrame = Rect.CREATOR.createFromParcel(in);
+ } else {
+ mInsetsProviderFrame = null;
+ }
mToTop = in.readBoolean();
mReparentTopOnly = in.readBoolean();
mMoveAdjacentTogether = in.readBoolean();
@@ -1171,6 +1236,15 @@
return mReparent;
}
+ @Nullable
+ public @InsetsState.InternalInsetsType int[] getInsetsTypes() {
+ return mInsetsTypes;
+ }
+
+ public Rect getInsetsProviderFrame() {
+ return mInsetsProviderFrame;
+ }
+
@NonNull
public IBinder getContainer() {
return mContainer;
@@ -1276,6 +1350,13 @@
case HIERARCHY_OP_TYPE_START_SHORTCUT:
return "{StartShortcut: options=" + mLaunchOptions + " info=" + mShortcutInfo
+ "}";
+ case HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER:
+ return "{addRectInsetsProvider: container=" + mContainer
+ + " insetsProvidingFrame=" + mInsetsProviderFrame
+ + " insetsType=" + Arrays.toString(mInsetsTypes) + "}";
+ case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER:
+ return "{removeLocalInsetsProvider: container=" + mContainer
+ + " insetsType=" + Arrays.toString(mInsetsTypes) + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
@@ -1289,6 +1370,13 @@
dest.writeInt(mType);
dest.writeStrongBinder(mContainer);
dest.writeStrongBinder(mReparent);
+ dest.writeIntArray(mInsetsTypes);
+ if (mInsetsProviderFrame != null) {
+ dest.writeInt(1);
+ mInsetsProviderFrame.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeBoolean(mToTop);
dest.writeBoolean(mReparentTopOnly);
dest.writeBoolean(mMoveAdjacentTogether);
@@ -1328,6 +1416,10 @@
@Nullable
private IBinder mReparent;
+ private int[] mInsetsTypes;
+
+ private Rect mInsetsProviderFrame;
+
private boolean mToTop;
private boolean mReparentTopOnly;
@@ -1369,6 +1461,16 @@
return this;
}
+ Builder setInsetsTypes(int[] insetsTypes) {
+ mInsetsTypes = insetsTypes;
+ return this;
+ }
+
+ Builder setInsetsProviderFrame(Rect insetsProviderFrame) {
+ mInsetsProviderFrame = insetsProviderFrame;
+ return this;
+ }
+
Builder setToTop(boolean toTop) {
mToTop = toTop;
return this;
@@ -1430,6 +1532,8 @@
hierarchyOp.mActivityTypes = mActivityTypes != null
? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
: null;
+ hierarchyOp.mInsetsTypes = mInsetsTypes;
+ hierarchyOp.mInsetsProviderFrame = mInsetsProviderFrame;
hierarchyOp.mToTop = mToTop;
hierarchyOp.mReparentTopOnly = mReparentTopOnly;
hierarchyOp.mMoveAdjacentTogether = mMoveAdjacentTogether;
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 94e5ea9..909e277 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -54,7 +54,7 @@
private static final String TAG = "WindowOnBackDispatcher";
private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
private static final boolean IS_BACK_PREDICTABILITY_ENABLED = SystemProperties
- .getInt(BACK_PREDICTABILITY_PROP, 1) > 0;
+ .getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 4ae6bf7..70506cc 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -771,11 +771,6 @@
delegationIntent.setComponent(delegateActivity);
delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent());
delegationIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
-
- // Query prediction availability; mIsAppPredictorComponentAvailable isn't initialized.
- delegationIntent.putExtra(
- EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE, isAppPredictionServiceAvailable());
-
delegationIntent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
// Don't close until the delegate finishes, or the token will be invalidated.
@@ -972,34 +967,8 @@
/**
* Returns true if app prediction service is defined and the component exists on device.
*/
- @VisibleForTesting
- public boolean isAppPredictionServiceAvailable() {
- if (getPackageManager().getAppPredictionServicePackageName() == null) {
- // Default AppPredictionService is not defined.
- return false;
- }
-
- final String appPredictionServiceName =
- getString(R.string.config_defaultAppPredictionService);
- if (appPredictionServiceName == null) {
- return false;
- }
- final ComponentName appPredictionComponentName =
- ComponentName.unflattenFromString(appPredictionServiceName);
- if (appPredictionComponentName == null) {
- return false;
- }
-
- // Check if the app prediction component actually exists on the device. The component is
- // only visible when this is running in a system activity; otherwise this check will fail.
- Intent intent = new Intent();
- intent.setComponent(appPredictionComponentName);
- if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
- Log.e(TAG, "App prediction service is defined, but does not exist: "
- + appPredictionServiceName);
- return false;
- }
- return true;
+ private boolean isAppPredictionServiceAvailable() {
+ return getPackageManager().getAppPredictionServicePackageName() != null;
}
/**
diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java
index 9ced609..9e07f97 100644
--- a/core/java/com/android/internal/infra/ServiceConnector.java
+++ b/core/java/com/android/internal/infra/ServiceConnector.java
@@ -147,6 +147,15 @@
void unbind();
/**
+ * Registers a {@link ServiceLifecycleCallbacks callbacks} to be invoked when the lifecycle
+ * of the managed service changes.
+ *
+ * @param callbacks The callbacks that will be run, or {@code null} to clear the existing
+ * callbacks.
+ */
+ void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<I> callbacks);
+
+ /**
* A request to be run when the service is
* {@link ServiceConnection#onServiceConnected connected}.
*
@@ -189,6 +198,32 @@
}
}
+ /**
+ * Collection of callbacks invoked when the lifecycle of the service changes.
+ *
+ * @param <II> the type of the {@link IInterface ipc interface} for the remote service
+ * @see ServiceConnector#setServiceLifecycleCallbacks(ServiceLifecycleCallbacks)
+ */
+ interface ServiceLifecycleCallbacks<II extends IInterface> {
+ /**
+ * Called when the service has just connected and before any queued jobs are run.
+ */
+ default void onConnected(@NonNull II service) {}
+
+ /**
+ * Called just before the service is disconnected and unbound.
+ */
+ default void onDisconnected(@NonNull II service) {}
+
+ /**
+ * Called when the service Binder has died.
+ *
+ * In cases where {@link #onBinderDied()} is invoked the service becomes unbound without
+ * a callback to {@link #onDisconnected(IInterface)}.
+ */
+ default void onBinderDied() {}
+ }
+
/**
* Implementation of {@link ServiceConnector}
@@ -230,6 +265,8 @@
private final @NonNull Handler mHandler;
protected final @NonNull Executor mExecutor;
+ @Nullable
+ private volatile ServiceLifecycleCallbacks<I> mServiceLifecycleCallbacks = null;
private volatile I mService = null;
private boolean mBinding = false;
private boolean mUnbinding = false;
@@ -330,6 +367,19 @@
}
}
+ private void dispatchOnServiceConnectionStatusChanged(
+ @NonNull I service, boolean isConnected) {
+ ServiceLifecycleCallbacks<I> serviceLifecycleCallbacks = mServiceLifecycleCallbacks;
+ if (serviceLifecycleCallbacks != null) {
+ if (isConnected) {
+ serviceLifecycleCallbacks.onConnected(service);
+ } else {
+ serviceLifecycleCallbacks.onDisconnected(service);
+ }
+ }
+ onServiceConnectionStatusChanged(service, isConnected);
+ }
+
/**
* Called when the service just connected or is about to disconnect
*/
@@ -504,12 +554,17 @@
mHandler.post(this::unbindJobThread);
}
+ @Override
+ public void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<I> callbacks) {
+ mServiceLifecycleCallbacks = callbacks;
+ }
+
void unbindJobThread() {
cancelTimeout();
I service = mService;
boolean wasBound = service != null;
if (wasBound) {
- onServiceConnectionStatusChanged(service, false);
+ dispatchOnServiceConnectionStatusChanged(service, false);
mContext.unbindService(mServiceConnection);
service.asBinder().unlinkToDeath(this, 0);
mService = null;
@@ -560,7 +615,7 @@
} catch (RemoteException e) {
Log.e(LOG_TAG, "onServiceConnected " + name + ": ", e);
}
- onServiceConnectionStatusChanged(service, true);
+ dispatchOnServiceConnectionStatusChanged(service, true);
processQueue();
}
@@ -572,7 +627,7 @@
mBinding = true;
I service = mService;
if (service != null) {
- onServiceConnectionStatusChanged(service, false);
+ dispatchOnServiceConnectionStatusChanged(service, false);
mService = null;
}
}
@@ -592,6 +647,14 @@
}
mService = null;
unbind();
+ dispatchOnBinderDied();
+ }
+
+ private void dispatchOnBinderDied() {
+ ServiceLifecycleCallbacks<I> serviceLifecycleCallbacks = mServiceLifecycleCallbacks;
+ if (serviceLifecycleCallbacks != null) {
+ serviceLifecycleCallbacks.onBinderDied();
+ }
}
@Override
@@ -764,5 +827,10 @@
@Override
public void unbind() {}
+
+ @Override
+ public void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<T> callbacks) {
+ // Do nothing.
+ }
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 6d4b8c5..b1e7d15 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -20,13 +20,21 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ProcessInfo;
import android.net.Credentials;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
+import android.os.Build;
import android.os.FactoryTest;
import android.os.IVold;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.provider.DeviceConfig;
@@ -34,6 +42,7 @@
import android.system.Os;
import android.util.Log;
+import com.android.internal.compat.IPlatformCompat;
import com.android.internal.net.NetworkUtilsInternal;
import dalvik.annotation.optimization.CriticalNative;
@@ -125,6 +134,7 @@
public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
public static final int MEMORY_TAG_LEVEL_NONE = 0;
+
/**
* Enable pointer tagging in this process.
* Tags are checked during memory deallocation, but not on access.
@@ -170,10 +180,8 @@
*/
public static final int GWP_ASAN_LEVEL_ALWAYS = 1 << 22;
- /**
- * Enable automatic zero-initialization of native heap memory allocations.
- */
- public static final int NATIVE_HEAP_ZERO_INIT = 1 << 23;
+ /** Enable automatic zero-initialization of native heap memory allocations. */
+ public static final int NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23;
/**
* Enable profiling from system services. This loads profiling related plugins in ART.
@@ -1170,4 +1178,251 @@
* we failed to determine the level.
*/
public static native int nativeCurrentTaggingLevel();
+
+ /**
+ * Native heap allocations will now have a non-zero tag in the most significant byte.
+ *
+ * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+ * Pointers</a>
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
+
+ /**
+ * Native heap allocations in AppZygote process and its descendants will now have a non-zero tag
+ * in the most significant byte.
+ *
+ * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+ * Pointers</a>
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+ private static final long NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE = 207557677;
+
+ /**
+ * Enable asynchronous (ASYNC) memory tag checking in this process. This flag will only have an
+ * effect on hardware supporting the ARM Memory Tagging Extension (MTE).
+ */
+ @ChangeId @Disabled
+ private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id.
+
+ /**
+ * Enable synchronous (SYNC) memory tag checking in this process. This flag will only have an
+ * effect on hardware supporting the ARM Memory Tagging Extension (MTE). If both
+ * NATIVE_MEMTAG_ASYNC and this option is selected, this option takes preference and MTE is
+ * enabled in SYNC mode.
+ */
+ @ChangeId @Disabled
+ private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.
+
+ /** Enable automatic zero-initialization of native heap memory allocations. */
+ @ChangeId @Disabled
+ private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.
+
+ /**
+ * Enable sampled memory bug detection in the app.
+ *
+ * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
+ */
+ @ChangeId @Disabled private static final long GWP_ASAN = 135634846; // This is a bug id.
+
+ private static int memtagModeToZygoteMemtagLevel(int memtagMode) {
+ switch (memtagMode) {
+ case ApplicationInfo.MEMTAG_ASYNC:
+ return MEMORY_TAG_LEVEL_ASYNC;
+ case ApplicationInfo.MEMTAG_SYNC:
+ return MEMORY_TAG_LEVEL_SYNC;
+ default:
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+ }
+
+ private static boolean isCompatChangeEnabled(
+ long change,
+ @NonNull ApplicationInfo info,
+ @Nullable IPlatformCompat platformCompat,
+ int enabledAfter) {
+ try {
+ if (platformCompat != null) return platformCompat.isChangeEnabled(change, info);
+ } catch (RemoteException ignore) {
+ }
+ return enabledAfter > 0 && info.targetSdkVersion > enabledAfter;
+ }
+
+ // Returns the requested memory tagging level.
+ private static int getRequestedMemtagLevel(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Look at the process attribute first.
+ if (processInfo != null && processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
+ return memtagModeToZygoteMemtagLevel(processInfo.memtagMode);
+ }
+
+ // Then at the application attribute.
+ if (info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
+ return memtagModeToZygoteMemtagLevel(info.getMemtagMode());
+ }
+
+ if (isCompatChangeEnabled(NATIVE_MEMTAG_SYNC, info, platformCompat, 0)) {
+ return MEMORY_TAG_LEVEL_SYNC;
+ }
+
+ if (isCompatChangeEnabled(NATIVE_MEMTAG_ASYNC, info, platformCompat, 0)) {
+ return MEMORY_TAG_LEVEL_ASYNC;
+ }
+
+ // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
+ if (!info.allowsNativeHeapPointerTagging()) {
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+
+ String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
+ if ("sync".equals(defaultLevel)) {
+ return MEMORY_TAG_LEVEL_SYNC;
+ } else if ("async".equals(defaultLevel)) {
+ return MEMORY_TAG_LEVEL_ASYNC;
+ }
+
+ // Check to see that the compat feature for TBI is enabled.
+ if (isCompatChangeEnabled(
+ NATIVE_HEAP_POINTER_TAGGING, info, platformCompat, Build.VERSION_CODES.Q)) {
+ return MEMORY_TAG_LEVEL_TBI;
+ }
+
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+
+ private static int decideTaggingLevel(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Get the desired tagging level (app manifest + compat features).
+ int level = getRequestedMemtagLevel(info, processInfo, platformCompat);
+
+ // Take into account the hardware capabilities.
+ if (nativeSupportsMemoryTagging()) {
+ // MTE devices can not do TBI, because the Zygote process already has live MTE
+ // allocations. Downgrade TBI to NONE.
+ if (level == MEMORY_TAG_LEVEL_TBI) {
+ level = MEMORY_TAG_LEVEL_NONE;
+ }
+ } else if (nativeSupportsTaggedPointers()) {
+ // TBI-but-not-MTE devices downgrade MTE modes to TBI.
+ // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
+ // the "fake" pointer tagging (TBI).
+ if (level == MEMORY_TAG_LEVEL_ASYNC || level == MEMORY_TAG_LEVEL_SYNC) {
+ level = MEMORY_TAG_LEVEL_TBI;
+ }
+ } else {
+ // Otherwise disable all tagging.
+ level = MEMORY_TAG_LEVEL_NONE;
+ }
+
+ return level;
+ }
+
+ private static int decideGwpAsanLevel(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Look at the process attribute first.
+ if (processInfo != null && processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
+ return processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS
+ ? GWP_ASAN_LEVEL_ALWAYS
+ : GWP_ASAN_LEVEL_NEVER;
+ }
+ // Then at the application attribute.
+ if (info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
+ return info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
+ ? GWP_ASAN_LEVEL_ALWAYS
+ : GWP_ASAN_LEVEL_NEVER;
+ }
+ // If the app does not specify gwpAsanMode, the default behavior is lottery among the
+ // system apps, and disabled for user apps, unless overwritten by the compat feature.
+ if (isCompatChangeEnabled(GWP_ASAN, info, platformCompat, 0)) {
+ return GWP_ASAN_LEVEL_ALWAYS;
+ }
+ if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return GWP_ASAN_LEVEL_LOTTERY;
+ }
+ return GWP_ASAN_LEVEL_NEVER;
+ }
+
+ private static boolean enableNativeHeapZeroInit(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Look at the process attribute first.
+ if (processInfo != null
+ && processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) {
+ return processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED;
+ }
+ // Then at the application attribute.
+ if (info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) {
+ return info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED;
+ }
+ // Compat feature last.
+ if (isCompatChangeEnabled(NATIVE_HEAP_ZERO_INIT, info, platformCompat, 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
+ * for a given app.
+ */
+ public static int getMemorySafetyRuntimeFlags(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable String instructionSet,
+ @Nullable IPlatformCompat platformCompat) {
+ int runtimeFlags = decideGwpAsanLevel(info, processInfo, platformCompat);
+ // If instructionSet is non-null, this indicates that the system_server is spawning a
+ // process with an ISA that may be different from its own. System (kernel and hardware)
+ // compatibility for these features is checked in the decideTaggingLevel in the
+ // system_server process (not the child process). As both MTE and TBI are only supported
+ // in aarch64, we can simply ensure that the new process is also aarch64. This prevents
+ // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should
+ // enable some tagging variant. Theoretically, a 32-bit system server could exist that
+ // spawns 64-bit processes, in which case the new process won't get any tagging. This is
+ // fine as we haven't seen this configuration in practice, and we can reasonable assume
+ // that if tagging is desired, the system server will be 64-bit.
+ if (instructionSet == null || instructionSet.equals("arm64")) {
+ runtimeFlags |= decideTaggingLevel(info, processInfo, platformCompat);
+ }
+ if (enableNativeHeapZeroInit(info, processInfo, platformCompat)) {
+ runtimeFlags |= NATIVE_HEAP_ZERO_INIT_ENABLED;
+ }
+ return runtimeFlags;
+ }
+
+ /**
+ * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
+ * for a secondary zygote (AppZygote or WebViewZygote).
+ */
+ public static int getMemorySafetyRuntimeFlagsForSecondaryZygote(
+ @NonNull ApplicationInfo info, @Nullable ProcessInfo processInfo) {
+ final IPlatformCompat platformCompat =
+ IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ int runtimeFlags =
+ getMemorySafetyRuntimeFlags(
+ info, processInfo, null /*instructionSet*/, platformCompat);
+
+ // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
+ if ((runtimeFlags & MEMORY_TAG_LEVEL_MASK) == MEMORY_TAG_LEVEL_TBI
+ && isCompatChangeEnabled(
+ NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE,
+ info,
+ platformCompat,
+ Build.VERSION_CODES.S)) {
+ // Reset memory tag level to NONE.
+ runtimeFlags &= ~MEMORY_TAG_LEVEL_MASK;
+ runtimeFlags |= MEMORY_TAG_LEVEL_NONE;
+ }
+ return runtimeFlags;
+ }
}
diff --git a/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java b/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java
index 3e72564..75dce5a 100644
--- a/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java
+++ b/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java
@@ -26,7 +26,7 @@
/**
* A ContentObserver for listening force show navigation bar relative setting keys:
* - {@link Settings.Secure#NAVIGATION_MODE}
- * - {@link Settings.Secure#NAV_BAR_KIDS_MODE}
+ * - {@link Settings.Secure#NAV_BAR_FORCE_VISIBLE}
*
* @hide
*/
@@ -52,7 +52,7 @@
Settings.Secure.getUriFor(Settings.Secure.NAVIGATION_MODE),
false, this, UserHandle.USER_ALL);
r.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE),
+ Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_FORCE_VISIBLE),
false, this, UserHandle.USER_ALL);
}
@@ -78,6 +78,6 @@
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.NAVIGATION_MODE, 0, UserHandle.USER_CURRENT) == 0
&& Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.NAV_BAR_KIDS_MODE, 0, UserHandle.USER_CURRENT) == 1;
+ Settings.Secure.NAV_BAR_FORCE_VISIBLE, 0, UserHandle.USER_CURRENT) == 1;
}
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 099d1fc..d629d66 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -159,7 +159,7 @@
/**
* Used to notify the authentication dialog that a biometric has been authenticated.
*/
- void onBiometricAuthenticated();
+ void onBiometricAuthenticated(int modality);
/**
* Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc.
*/
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index dcc1a76..9163b6d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -125,7 +125,7 @@
int multiSensorConfig);
// Used to notify the authentication dialog that a biometric has been authenticated
- void onBiometricAuthenticated();
+ void onBiometricAuthenticated(int modality);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(int modality, String message);
// Used to show an error - the dialog will dismiss after a certain amount of time
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 93864fa..b1846d2 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -671,6 +671,7 @@
readPermissions(parser, Environment.buildPath(f, "etc", "permissions"),
apexPermissionFlag);
}
+ pruneVendorApexPrivappAllowlists();
}
@VisibleForTesting
@@ -1197,7 +1198,8 @@
readPrivAppPermissions(parser, mSystemExtPrivAppPermissions,
mSystemExtPrivAppDenyPermissions);
} else if (apex) {
- readApexPrivAppPermissions(parser, permFile);
+ readApexPrivAppPermissions(parser, permFile,
+ Environment.getApexDirectory().toPath());
} else {
readPrivAppPermissions(parser, mPrivAppPermissions,
mPrivAppDenyPermissions);
@@ -1547,6 +1549,21 @@
}
}
+ /**
+ * Prunes out any privileged permission allowlists bundled in vendor apexes.
+ */
+ @VisibleForTesting
+ public void pruneVendorApexPrivappAllowlists() {
+ for (String moduleName: mAllowedVendorApexes.keySet()) {
+ if (mApexPrivAppPermissions.containsKey(moduleName)
+ || mApexPrivAppDenyPermissions.containsKey(moduleName)) {
+ Slog.w(TAG, moduleName + " is a vendor apex, ignore its priv-app allowlist");
+ mApexPrivAppPermissions.remove(moduleName);
+ mApexPrivAppDenyPermissions.remove(moduleName);
+ }
+ }
+ }
+
private void readInstallInUserType(XmlPullParser parser,
Map<String, Set<String>> doInstallMap,
Map<String, Set<String>> nonInstallMap)
@@ -1757,8 +1774,7 @@
/**
* Returns the module name for a file in the apex module's partition.
*/
- private String getApexModuleNameFromFilePath(Path path) {
- final Path apexDirectoryPath = Environment.getApexDirectory().toPath();
+ private String getApexModuleNameFromFilePath(Path path, Path apexDirectoryPath) {
if (!path.startsWith(apexDirectoryPath)) {
throw new IllegalArgumentException("File " + path + " is not part of an APEX.");
}
@@ -1770,9 +1786,14 @@
return path.getName(apexDirectoryPath.getNameCount()).toString();
}
- private void readApexPrivAppPermissions(XmlPullParser parser, File permFile)
- throws IOException, XmlPullParserException {
- final String moduleName = getApexModuleNameFromFilePath(permFile.toPath());
+ /**
+ * Reads the contents of the privileged permission allowlist stored inside an APEX.
+ */
+ @VisibleForTesting
+ public void readApexPrivAppPermissions(XmlPullParser parser, File permFile,
+ Path apexDirectoryPath) throws IOException, XmlPullParserException {
+ final String moduleName =
+ getApexModuleNameFromFilePath(permFile.toPath(), apexDirectoryPath);
final ArrayMap<String, ArraySet<String>> privAppPermissions;
if (mApexPrivAppPermissions.containsKey(moduleName)) {
privAppPermissions = mApexPrivAppPermissions.get(moduleName);
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 90b272c..e781760 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -829,13 +829,6 @@
}
static jint
-android_media_AudioSystem_getDevicesForStream(JNIEnv *env, jobject thiz, jint stream)
-{
- return (jint)deviceTypesToBitMask(
- AudioSystem::getDevicesForStream(static_cast<audio_stream_type_t>(stream)));
-}
-
-static jint
android_media_AudioSystem_getPrimaryOutputSamplingRate(JNIEnv *env, jobject clazz)
{
return (jint) AudioSystem::getPrimaryOutputSamplingRate();
@@ -2959,7 +2952,6 @@
{"getMasterMono", "()Z", (void *)android_media_AudioSystem_getMasterMono},
{"setMasterBalance", "(F)I", (void *)android_media_AudioSystem_setMasterBalance},
{"getMasterBalance", "()F", (void *)android_media_AudioSystem_getMasterBalance},
- {"getDevicesForStream", "(I)I", (void *)android_media_AudioSystem_getDevicesForStream},
{"getPrimaryOutputSamplingRate", "()I",
(void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
{"getPrimaryOutputFrameCount", "()I",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 5971670..5b7092c 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -346,7 +346,7 @@
GWP_ASAN_LEVEL_NEVER = 0 << 21,
GWP_ASAN_LEVEL_LOTTERY = 1 << 21,
GWP_ASAN_LEVEL_ALWAYS = 2 << 21,
- NATIVE_HEAP_ZERO_INIT = 1 << 23,
+ NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23,
PROFILEABLE = 1 << 24,
};
@@ -1709,13 +1709,13 @@
// would be nice to have them for apps, we will have to wait until they are
// proven out, have more efficient hardware, and/or apply them only to new
// applications.
- if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT)) {
+ if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED)) {
mallopt(M_BIONIC_ZERO_INIT, 0);
}
// Now that we've used the flag, clear it so that we don't pass unknown flags to the ART
// runtime.
- runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT;
+ runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED;
bool forceEnableGwpAsan = false;
switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4c0ba8c..6a421f0 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -385,7 +385,18 @@
optional SettingProto multi_press_timeout = 38 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto nav_bar_kids_mode = 91 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ message NavBar {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ // Nav bar is forced to always be visible, even in immersive mode.
+ optional SettingProto nav_bar_force_visible = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // Indicates whether the device is in kids nav mode.
+ optional SettingProto nav_bar_kids_mode = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional NavBar nav_bar = 92;
+ reserved 91; // Formerly nav_bar_kids_mode
+ reserved "nav_bar_kids_mode"; // Moved to message NavBar
+
optional SettingProto navigation_mode = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
message NfcPayment {
@@ -668,5 +679,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 92;
+ // Next tag = 93;
}
diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto
index fc9da90..ac9e3e0 100644
--- a/core/proto/android/server/biometrics.proto
+++ b/core/proto/android/server/biometrics.proto
@@ -91,23 +91,9 @@
STATE_CLIENT_DIED_CANCELLING = 10;
}
- enum MultiSensorState {
- // Initializing or not yet started.
- MULTI_SENSOR_STATE_UNKNOWN = 0;
- // Sensors are in the process of being transitioned and there is no active sensor.
- MULTI_SENSOR_STATE_SWITCHING = 1;
- // Face sensor is being used as the primary input.
- MULTI_SENSOR_STATE_FACE_SCANNING = 2;
- // Fingerprint sensor is being used as the primary input.
- MULTI_SENSOR_STATE_FP_SCANNING = 3;
- }
-
repeated SensorServiceStateProto sensor_service_states = 1;
optional AuthSessionState auth_session_state = 2;
-
- // Additional session state information, when the device has multiple sensors.
- optional MultiSensorState auth_session_multi_sensor_state = 3;
}
// Overall state for an instance of a <Biometric>Service, for example FingerprintService or
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index e8f7b93..3929027 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -446,6 +446,7 @@
optional bool has_compat_scale = 43;
optional float global_scale = 44;
repeated .android.graphics.RectProto keep_clear_areas = 45;
+ repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
}
message IdentifierProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 73cdaba..d7744e3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6926,6 +6926,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.BinaryTransparencyService$UpdateMeasurementsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4648f8a..0763ddb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2113,7 +2113,7 @@
TODO(b/189347385) make this a @SystemAPI -->
<string name="config_systemTelevisionRemoteService" translatable="false">@string/config_tvRemoteServicePackage</string>
<!-- The name of the package that will hold the device management role -->
- <string name="config_deviceManager" translatable="false"></string>
+ <string name="config_devicePolicyManagement" translatable="false"></string>
<!-- The name of the package that will hold the app protection service role. -->
<string name="config_systemAppProtectionService" translatable="false"></string>
<!-- The name of the package that will hold the system calendar sync manager role. -->
@@ -2122,7 +2122,7 @@
<string name="config_defaultAutomotiveNavigation" translatable="false"></string>
<!-- The name of the package that will handle updating the device management role. -->
- <string name="config_deviceManagerUpdater" translatable="false"></string>
+ <string name="config_devicePolicyManagementUpdater" translatable="false"></string>
<!-- The name of the package that will be allowed to change its components' label/icon. -->
<string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3beb4b2..3467e1b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3301,7 +3301,7 @@
<!-- @hide @SystemApi -->
<public name="config_systemSupervision" />
<!-- @hide @SystemApi -->
- <public name="config_deviceManager" />
+ <public name="config_devicePolicyManagement" />
<!-- @hide @SystemApi -->
<public name="config_systemAppProtectionService" />
<!-- @hide @SystemApi @TestApi -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ffd1f125..dae746b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4726,7 +4726,7 @@
<java-symbol type="bool" name="config_enableSafetyCenter" />
- <java-symbol type="string" name="config_deviceManagerUpdater" />
+ <java-symbol type="string" name="config_devicePolicyManagementUpdater" />
<java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index a80424e..04857ec 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1494,6 +1494,16 @@
android:permission="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"
android:exported="true"/>
+ <service
+ android:name="com.android.internal.infra.ServiceConnectorTest$TestService"
+ android:process=":ServiceConnectorRemoteService"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.BIND_TEST_SERVICE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </service>
+
<provider android:name="android.app.activity.LocalProvider"
android:authorities="com.android.frameworks.coretests.LocalProvider">
<meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestServiceConnectorService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestServiceConnectorService.aidl
new file mode 100644
index 0000000..09544b3
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestServiceConnectorService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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.frameworks.coretests.aidl;
+
+interface ITestServiceConnectorService {
+ void crashProcess();
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 23ec3ea..cf78646 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1307,55 +1307,6 @@
is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
}
- /**
- * The case when AppPrediction service is not defined in PackageManager is already covered
- * as a test parameter {@link ChooserActivityTest#packageManagers}. This test is checking the
- * case when the prediction service is defined but the component is not available on the device.
- */
- @Test
- public void testIsAppPredictionServiceAvailable() {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resolverListController
- .getResolversForIntent(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(resolvedComponentInfos);
-
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- if (activity.getPackageManager().getAppPredictionServicePackageName() == null) {
- assertThat(activity.isAppPredictionServiceAvailable(), is(false));
- } else {
- if (!shouldTestTogglingAppPredictionServiceAvailabilityAtRuntime()) {
- return;
- }
-
- // This isn't a toggle per-se, but isAppPredictionServiceAvailable only works in
- // system (see comment in the method).
- assertThat(activity.isAppPredictionServiceAvailable(), is(true));
-
- ChooserActivityOverrideData.getInstance().resources =
- Mockito.spy(activity.getResources());
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resources
- .getString(
- getRuntimeResourceId(
- "config_defaultAppPredictionService",
- "string")))
- .thenReturn("ComponentNameThatDoesNotExist");
-
- assertThat(activity.isAppPredictionServiceAvailable(), is(false));
- }
- }
-
@Test
public void testConvertToChooserTarget_predictionService() {
Intent sendIntent = createSendTextIntent();
diff --git a/core/tests/coretests/src/com/android/internal/infra/ServiceConnectorTest.java b/core/tests/coretests/src/com/android/internal/infra/ServiceConnectorTest.java
new file mode 100644
index 0000000..725dcf3
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/infra/ServiceConnectorTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2022 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.internal.infra;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.aidl.ITestServiceConnectorService;
+import com.android.internal.infra.ServiceConnectorTest.CapturingServiceLifecycleCallbacks.ServiceLifeCycleEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Unit tests for {@link ServiceConnector}
+ */
+@RunWith(AndroidJUnit4.class)
+public class ServiceConnectorTest {
+
+ private final CapturingServiceLifecycleCallbacks mCapturingServiceLifecycleCallbacks =
+ new CapturingServiceLifecycleCallbacks();
+ private ServiceConnector<ITestServiceConnectorService> mServiceConnector;
+
+ @Before
+ public void setup() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Intent testServiceConnectorServiceIntent = new Intent(TestService.ACTION_TEST_SERVICE);
+ testServiceConnectorServiceIntent.setPackage(context.getPackageName());
+
+ ServiceConnector.Impl<ITestServiceConnectorService> serviceConnector =
+ new ServiceConnector.Impl<ITestServiceConnectorService>(
+ context,
+ testServiceConnectorServiceIntent,
+ /* bindingFlags= */ 0,
+ UserHandle.myUserId(),
+ ITestServiceConnectorService.Stub::asInterface);
+ serviceConnector.setServiceLifecycleCallbacks(mCapturingServiceLifecycleCallbacks);
+ mServiceConnector = serviceConnector;
+ }
+
+ @Test
+ public void connect_invokesLifecycleCallbacks() throws Exception {
+ connectAndWaitForDone();
+
+ assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+ .containsExactly(ServiceLifeCycleEvent.ON_CONNECTED)
+ .inOrder();
+ }
+
+ @Test
+ public void connect_alreadyConnected_invokesLifecycleCallbacksOnce() throws Exception {
+ connectAndWaitForDone();
+ connectAndWaitForDone();
+
+ assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+ .containsExactly(ServiceLifeCycleEvent.ON_CONNECTED)
+ .inOrder();
+ }
+
+ @Test
+ public void unbind_neverConnected_noLifecycleCallbacks() {
+ unbindAndWaitForDone();
+
+ assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+ .isEmpty();
+ }
+
+ @Test
+ public void unbind_whileConnected_invokesLifecycleCallbacks() throws Exception {
+ connectAndWaitForDone();
+ unbindAndWaitForDone();
+
+ assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+ .containsExactly(
+ ServiceLifeCycleEvent.ON_CONNECTED,
+ ServiceLifeCycleEvent.ON_DISCONNECTED)
+ .inOrder();
+ }
+
+
+ @Test
+ public void unbind_alreadyUnbound_invokesLifecycleCallbacks() throws Exception {
+ connectAndWaitForDone();
+ unbindAndWaitForDone();
+ unbindAndWaitForDone();
+
+ assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+ .containsExactly(
+ ServiceLifeCycleEvent.ON_CONNECTED,
+ ServiceLifeCycleEvent.ON_DISCONNECTED)
+ .inOrder();
+ }
+
+ @Test
+ public void binds_connectsAndUnbindsMultipleTimes_invokesLifecycleCallbacks() throws Exception {
+ connectAndWaitForDone();
+ unbindAndWaitForDone();
+ connectAndWaitForDone();
+ unbindAndWaitForDone();
+ connectAndWaitForDone();
+
+ assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+ .containsExactly(
+ ServiceLifeCycleEvent.ON_CONNECTED,
+ ServiceLifeCycleEvent.ON_DISCONNECTED,
+ ServiceLifeCycleEvent.ON_CONNECTED,
+ ServiceLifeCycleEvent.ON_DISCONNECTED,
+ ServiceLifeCycleEvent.ON_CONNECTED)
+ .inOrder();
+ }
+
+ @Test
+ public void processCrashes_whileConnected_invokesLifecycleCallbacks() throws Exception {
+ connectAndWaitForDone();
+ waitForDone(mServiceConnector.post(service -> service.crashProcess()));
+
+ assertThat(mCapturingServiceLifecycleCallbacks.getCapturedLifecycleEvents())
+ .containsExactly(
+ ServiceLifeCycleEvent.ON_CONNECTED,
+ ServiceLifeCycleEvent.ON_BINDER_DIED)
+ .inOrder();
+ }
+
+ private void connectAndWaitForDone() {
+ waitForDone(mServiceConnector.connect());
+ }
+
+ private void unbindAndWaitForDone() {
+ mServiceConnector.unbind();
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private static void waitForDone(AndroidFuture<?> androidFuture) {
+ if (androidFuture.isDone()) {
+ return;
+ }
+
+ try {
+ androidFuture.get(10, TimeUnit.SECONDS);
+ } catch (InterruptedException | TimeoutException ex) {
+ throw new RuntimeException(ex);
+ } catch (ExecutionException | CancellationException ex) {
+ // Failed and canceled futures are completed
+ return;
+ }
+ }
+
+ public static final class CapturingServiceLifecycleCallbacks implements
+ ServiceConnector.ServiceLifecycleCallbacks<ITestServiceConnectorService> {
+ public enum ServiceLifeCycleEvent {
+ ON_CONNECTED,
+ ON_DISCONNECTED,
+ ON_BINDER_DIED,
+ }
+
+ private final ArrayList<ServiceLifeCycleEvent> mCapturedLifecycleEventServices =
+ new ArrayList<>();
+
+ public ArrayList<ServiceLifeCycleEvent> getCapturedLifecycleEvents() {
+ return mCapturedLifecycleEventServices;
+ }
+
+ @Override
+ public void onConnected(@NonNull ITestServiceConnectorService service) {
+ requireNonNull(service);
+ mCapturedLifecycleEventServices.add(ServiceLifeCycleEvent.ON_CONNECTED);
+ }
+
+ @Override
+ public void onDisconnected(@NonNull ITestServiceConnectorService service) {
+ requireNonNull(service);
+ mCapturedLifecycleEventServices.add(ServiceLifeCycleEvent.ON_DISCONNECTED);
+ }
+
+ @Override
+ public void onBinderDied() {
+ mCapturedLifecycleEventServices.add(ServiceLifeCycleEvent.ON_BINDER_DIED);
+ }
+ }
+
+ public static final class TestService extends Service {
+
+ public static String ACTION_TEST_SERVICE = "android.intent.action.BIND_TEST_SERVICE";
+
+ @Nullable
+ @Override
+ public IBinder onBind(@Nullable Intent intent) {
+ if (intent == null) {
+ return null;
+ }
+
+ if (!intent.getAction().equals(ACTION_TEST_SERVICE)) {
+ return null;
+ }
+
+ return new TestServiceConnectorService().asBinder();
+ }
+ }
+
+ private static final class TestServiceConnectorService extends
+ ITestServiceConnectorService.Stub {
+ @Override
+ public void crashProcess() {
+ Process.killProcess(Process.myPid());
+ }
+ }
+}
+
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 33b09b8..55f205b 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -690,6 +690,14 @@
}
}
+ /**
+ * Gets the total duration of the animation
+ * @hide
+ */
+ public long getTotalDuration() {
+ return mAnimatorSet.getTotalDuration();
+ }
+
private static class AnimatedVectorDrawableState extends ConstantState {
@Config int mChangingConfigurations;
VectorDrawable mVectorDrawable;
@@ -1074,6 +1082,7 @@
boolean isInfinite();
void pause();
void resume();
+ long getTotalDuration();
}
private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator {
@@ -1085,6 +1094,7 @@
// setup by init().
private ArrayList<AnimatorListener> mListenerArray = null;
private boolean mIsInfinite = false;
+ private long mTotalDuration;
VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) {
mDrawable = drawable;
@@ -1100,7 +1110,8 @@
// Keep a deep copy of the set, such that set can be still be constantly representing
// the static content from XML file.
mSet = set.clone();
- mIsInfinite = mSet.getTotalDuration() == Animator.DURATION_INFINITE;
+ mTotalDuration = mSet.getTotalDuration();
+ mIsInfinite = mTotalDuration == Animator.DURATION_INFINITE;
// If there are listeners added before calling init(), now they should be setup.
if (mListenerArray != null && !mListenerArray.isEmpty()) {
@@ -1219,6 +1230,11 @@
private void invalidateOwningView() {
mDrawable.invalidateSelf();
}
+
+ @Override
+ public long getTotalDuration() {
+ return mTotalDuration;
+ }
}
/**
@@ -1249,6 +1265,7 @@
private int mLastListenerId = 0;
private final IntArray mPendingAnimationActions = new IntArray();
private final AnimatedVectorDrawable mDrawable;
+ private long mTotalDuration;
VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) {
mDrawable = drawable;
@@ -1270,7 +1287,8 @@
.getNativeTree();
nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr);
mInitialized = true;
- mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE;
+ mTotalDuration = set.getTotalDuration();
+ mIsInfinite = mTotalDuration == Animator.DURATION_INFINITE;
// Check reversible.
mIsReversible = true;
@@ -1796,6 +1814,11 @@
}
mPendingAnimationActions.clear();
}
+
+ @Override
+ public long getTotalDuration() {
+ return mTotalDuration;
+ }
}
private static native long nCreateAnimatorSet();
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 8c3fa44..7fd2201 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -424,6 +424,17 @@
System.arraycopy(mDurations, 0, newDurations, 0, oldSize);
mDurations = newDurations;
}
+
+ public long getTotalDuration() {
+ if (mDurations != null) {
+ int total = 0;
+ for (int dur : mDurations) {
+ total += dur;
+ }
+ return total;
+ }
+ return 0;
+ }
}
@Override
@@ -435,6 +446,14 @@
}
}
+ /**
+ * Gets the total duration of the animation
+ * @hide
+ */
+ public long getTotalDuration() {
+ return mAnimationState.getTotalDuration();
+ }
+
private AnimationDrawable(AnimationState state, Resources res) {
final AnimationState as = new AnimationState(state, this, res);
setConstantState(as);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 2e85b30..31dd10a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -66,6 +66,7 @@
import java.security.UnrecoverableKeyException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
+import java.security.spec.NamedParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
@@ -119,36 +120,42 @@
private static final int RSA_MIN_KEY_SIZE = 512;
private static final int RSA_MAX_KEY_SIZE = 8192;
- private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE =
+ private static final Map<String, Integer> SUPPORTED_EC_CURVE_NAME_TO_SIZE =
new HashMap<String, Integer>();
- private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>();
- private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>();
+ private static final List<String> SUPPORTED_EC_CURVE_NAMES = new ArrayList<String>();
+ private static final List<Integer> SUPPORTED_EC_CURVE_SIZES = new ArrayList<Integer>();
+ private static final String CURVE_X_25519 = NamedParameterSpec.X25519.getName();
+ private static final String CURVE_ED_25519 = NamedParameterSpec.ED25519.getName();
+
static {
// Aliases for NIST P-224
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-224", 224);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
// Aliases for NIST P-256
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-256", 256);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
+ // Aliases for Curve 25519
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put(CURVE_X_25519.toLowerCase(Locale.US), 256);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put(CURVE_ED_25519.toLowerCase(Locale.US), 256);
// Aliases for NIST P-384
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-384", 384);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
// Aliases for NIST P-521
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521);
- SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-521", 521);
+ SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
- SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet());
- Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES);
+ SUPPORTED_EC_CURVE_NAMES.addAll(SUPPORTED_EC_CURVE_NAME_TO_SIZE.keySet());
+ Collections.sort(SUPPORTED_EC_CURVE_NAMES);
- SUPPORTED_EC_NIST_CURVE_SIZES.addAll(
- new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values()));
- Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES);
+ SUPPORTED_EC_CURVE_SIZES.addAll(
+ new HashSet<Integer>(SUPPORTED_EC_CURVE_NAME_TO_SIZE.values()));
+ Collections.sort(SUPPORTED_EC_CURVE_SIZES);
}
private final int mOriginalKeymasterAlgorithm;
@@ -164,6 +171,7 @@
private int mKeySizeBits;
private SecureRandom mRng;
private KeyDescriptor mAttestKeyDescriptor;
+ private String mEcCurveName;
private int[] mKeymasterPurposes;
private int[] mKeymasterBlockModes;
@@ -177,12 +185,15 @@
mOriginalKeymasterAlgorithm = keymasterAlgorithm;
}
- private @EcCurve int keySize2EcCurve(int keySizeBits)
+ private static @EcCurve int keySizeAndNameToEcCurve(int keySizeBits, String ecCurveName)
throws InvalidAlgorithmParameterException {
switch (keySizeBits) {
case 224:
return EcCurve.P_224;
case 256:
+ if (isCurve25519(ecCurveName)) {
+ return EcCurve.CURVE_25519;
+ }
return EcCurve.P_256;
case 384:
return EcCurve.P_384;
@@ -247,7 +258,8 @@
if (mKeySizeBits == -1) {
mKeySizeBits = getDefaultKeySize(keymasterAlgorithm);
}
- checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked());
+ checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked(),
+ mEcCurveName);
if (spec.getKeystoreAlias() == null) {
throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
@@ -299,6 +311,7 @@
mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec);
checkAttestKeyPurpose(spec);
+ checkCorrectKeyPurposeForCurve(spec);
success = true;
} finally {
@@ -317,6 +330,42 @@
}
}
+ private void checkCorrectKeyPurposeForCurve(KeyGenParameterSpec spec)
+ throws InvalidAlgorithmParameterException {
+ // Validate the key usage purposes against the curve. x25519 should be
+ // key exchange only, ed25519 signing and attesting.
+
+ if (!isCurve25519(mEcCurveName)) {
+ return;
+ }
+
+ if (mEcCurveName.equalsIgnoreCase(CURVE_X_25519)
+ && spec.getPurposes() != KeyProperties.PURPOSE_AGREE_KEY) {
+ throw new InvalidAlgorithmParameterException(
+ "x25519 may only be used for key agreement.");
+ } else if (mEcCurveName.equalsIgnoreCase(CURVE_ED_25519)
+ && !hasOnlyAllowedPurposeForEd25519(spec.getPurposes())) {
+ throw new InvalidAlgorithmParameterException(
+ "ed25519 may not be used for key agreement.");
+ }
+ }
+
+ private static boolean isCurve25519(String ecCurveName) {
+ if (ecCurveName == null) {
+ return false;
+ }
+ return ecCurveName.equalsIgnoreCase(CURVE_X_25519)
+ || ecCurveName.equalsIgnoreCase(CURVE_ED_25519);
+ }
+
+ private static boolean hasOnlyAllowedPurposeForEd25519(@KeyProperties.PurposeEnum int purpose) {
+ final int allowedPurposes = KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY
+ | KeyProperties.PURPOSE_ATTEST_KEY;
+ boolean hasAllowedPurpose = (purpose & allowedPurposes) != 0;
+ boolean hasDisallowedPurpose = (purpose & ~allowedPurposes) != 0;
+ return hasAllowedPurpose && !hasDisallowedPurpose;
+ }
+
private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec)
throws InvalidAlgorithmParameterException {
if (spec.getAttestKeyAlias() != null) {
@@ -473,6 +522,7 @@
mRSAPublicExponent = null;
mRng = null;
mKeyStore = null;
+ mEcCurveName = null;
}
private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException {
@@ -514,13 +564,13 @@
case KeymasterDefs.KM_ALGORITHM_EC:
if (algSpecificSpec instanceof ECGenParameterSpec) {
ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
- String curveName = ecSpec.getName();
- Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get(
- curveName.toLowerCase(Locale.US));
+ mEcCurveName = ecSpec.getName();
+ final Integer ecSpecKeySizeBits = SUPPORTED_EC_CURVE_NAME_TO_SIZE.get(
+ mEcCurveName.toLowerCase(Locale.US));
if (ecSpecKeySizeBits == null) {
throw new InvalidAlgorithmParameterException(
- "Unsupported EC curve name: " + curveName
- + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES);
+ "Unsupported EC curve name: " + mEcCurveName
+ + ". Supported: " + SUPPORTED_EC_CURVE_NAMES);
}
if (mKeySizeBits == -1) {
mKeySizeBits = ecSpecKeySizeBits;
@@ -744,7 +794,7 @@
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
params.add(KeyStore2ParameterUtils.makeEnum(
- Tag.EC_CURVE, keySize2EcCurve(mKeySizeBits)
+ Tag.EC_CURVE, keySizeAndNameToEcCurve(mKeySizeBits, mEcCurveName)
));
}
@@ -864,7 +914,8 @@
private static void checkValidKeySize(
int keymasterAlgorithm,
int keySize,
- boolean isStrongBoxBacked)
+ boolean isStrongBoxBacked,
+ String mEcCurveName)
throws InvalidAlgorithmParameterException {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_EC:
@@ -873,9 +924,13 @@
"Unsupported StrongBox EC key size: "
+ keySize + " bits. Supported: 256");
}
- if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) {
+ if (isStrongBoxBacked && isCurve25519(mEcCurveName)) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported StrongBox EC: " + mEcCurveName);
+ }
+ if (!SUPPORTED_EC_CURVE_SIZES.contains(keySize)) {
throw new InvalidAlgorithmParameterException("Unsupported EC key size: "
- + keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES);
+ + keySize + " bits. Supported: " + SUPPORTED_EC_CURVE_SIZES);
}
break;
case KeymasterDefs.KM_ALGORITHM_RSA:
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 72a145f..358104f 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -66,6 +66,11 @@
private static final String DESEDE_SYSTEM_PROPERTY =
"ro.hardware.keystore_desede";
+ // Conscrypt returns the Ed25519 OID as the JCA key algorithm.
+ private static final String ED25519_OID = "1.3.101.112";
+ // Conscrypt returns "XDH" as the X25519 JCA key algorithm.
+ private static final String X25519_ALIAS = "XDH";
+
/** @hide **/
public AndroidKeyStoreProvider() {
super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
@@ -78,10 +83,16 @@
// java.security.KeyPairGenerator
put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
+ put("KeyPairGenerator." + X25519_ALIAS,
+ PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
+ put("KeyPairGenerator." + ED25519_OID,
+ PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
// java.security.KeyFactory
putKeyFactoryImpl("EC");
putKeyFactoryImpl("RSA");
+ putKeyFactoryImpl(X25519_ALIAS);
+ putKeyFactoryImpl(ED25519_OID);
// javax.crypto.KeyGenerator
put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
@@ -219,12 +230,17 @@
KeyStoreSecurityLevel securityLevel = iSecurityLevel;
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) {
-
return new AndroidKeyStoreECPublicKey(descriptor, metadata,
iSecurityLevel, (ECPublicKey) publicKey);
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(jcaKeyAlgorithm)) {
return new AndroidKeyStoreRSAPublicKey(descriptor, metadata,
iSecurityLevel, (RSAPublicKey) publicKey);
+ } else if (ED25519_OID.equalsIgnoreCase(jcaKeyAlgorithm)) {
+ //TODO(b/214203951) missing classes in conscrypt
+ throw new ProviderException("Curve " + ED25519_OID + " not supported yet");
+ } else if (X25519_ALIAS.equalsIgnoreCase(jcaKeyAlgorithm)) {
+ //TODO(b/214203951) missing classes in conscrypt
+ throw new ProviderException("Curve " + X25519_ALIAS + " not supported yet");
} else {
throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ jcaKeyAlgorithm);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 9b41468..8d5fdfb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -52,6 +52,9 @@
*/
public class BackAnimationController implements RemoteCallable<BackAnimationController> {
+ private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
+ public static final boolean IS_ENABLED = SystemProperties
+ .getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
private static final String BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP =
"persist.debug.back_predictability_progress_threshold";
private static final int PROGRESS_THRESHOLD = SystemProperties
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c6a68dc..58f79f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1608,7 +1608,9 @@
Log.d(TAG, "addBubble: " + bubble);
}
- if (getBubbleCount() == 0 && shouldShowStackEdu()) {
+ final boolean firstBubble = getBubbleCount() == 0;
+
+ if (firstBubble && shouldShowStackEdu()) {
// Override the default stack position if we're showing user education.
mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
}
@@ -1621,7 +1623,7 @@
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
mPositioner.getBubbleSize()));
- if (getBubbleCount() == 0) {
+ if (firstBubble) {
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
// Set the dot position to the opposite of the side the stack is resting on, since the stack
@@ -1652,6 +1654,10 @@
bubble.cleanupViews();
}
updateExpandedView();
+ if (getBubbleCount() == 0 && !isExpanded()) {
+ // This is the last bubble and the stack is collapsed
+ updateStackPosition();
+ }
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 8a482fb..b52c8d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -502,16 +502,24 @@
if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
wct.setBounds(task1.token, mBounds1);
+ wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
mWinBounds1.set(mBounds1);
mWinToken1 = task1.token;
}
if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
wct.setBounds(task2.token, mBounds2);
+ wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
mWinBounds2.set(mBounds2);
mWinToken2 = task2.token;
}
}
+ private int getSmallestWidthDp(Rect bounds) {
+ final int minWidth = Math.min(bounds.width(), bounds.height());
+ final float density = mContext.getResources().getDisplayMetrics().density;
+ return (int) (minWidth / density);
+ }
+
/**
* Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
* restore shifted configuration bounds if it's no longer shifted.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c94f3d1..0362b3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -699,7 +699,10 @@
Context context,
@ShellMainThread ShellExecutor shellExecutor
) {
- return Optional.of(
- new BackAnimationController(shellExecutor, context));
+ if (BackAnimationController.IS_ENABLED) {
+ return Optional.of(
+ new BackAnimationController(shellExecutor, context));
+ }
+ return Optional.empty();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 082fe92..22dd9b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -49,14 +48,13 @@
return mIsActive;
}
- void activate(Rect rootBounds, WindowContainerTransaction wct, boolean includingTopTask) {
+ void activate(WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds)
- // Moving the root task to top after the child tasks were re-parented , or the root
- // task cannot be visible and focused.
- .reorder(rootToken, true /* onTop */);
+ // Moving the root task to top after the child tasks were re-parented , or the root
+ // task cannot be visible and focused.
+ wct.reorder(rootToken, true /* onTop */);
if (includingTopTask) {
wct.reparentTasks(
null /* currentParent */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 76641f0..7318f48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -354,8 +354,8 @@
mSplitLayout.setDivideRatio(splitRatio);
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
- mSideStage.setBounds(getSideStageBounds(), wct);
+ mMainStage.activate(wct, false /* reparent */);
+ updateWindowBounds(mSplitLayout, wct);
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
@@ -470,13 +470,14 @@
mSplitLayout.setDivideRatio(splitRatio);
if (mMainStage.isActive()) {
- mMainStage.moveToTop(getMainStageBounds(), wct);
+ mMainStage.moveToTop(wct);
} else {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
+ mMainStage.activate(wct, false /* reparent */);
}
- mSideStage.moveToTop(getSideStageBounds(), wct);
+ mSideStage.moveToTop(wct);
+ updateWindowBounds(mSplitLayout, wct);
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
@@ -774,8 +775,9 @@
setSideStagePosition(startPosition, wct);
mSideStage.addTask(taskInfo, wct);
}
- mMainStage.activate(getMainStageBounds(), wct, true /* includingTopTask */);
- mSideStage.moveToTop(getSideStageBounds(), wct);
+ mMainStage.activate(wct, true /* includingTopTask */);
+ mSideStage.moveToTop(wct);
+ updateWindowBounds(mSplitLayout, wct);
}
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -997,10 +999,9 @@
// Exit to side stage if main stage no longer has children.
exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);
}
- } else if (isSideStage) {
+ } else if (isSideStage && !mMainStage.isActive()) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
- // Make sure the main stage is active.
prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 83534c1..c5aab45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -53,8 +53,8 @@
* Base class that handle common task org. related for split-screen stages.
* Note that this class and its sub-class do not directly perform hierarchy operations.
* They only serve to hold a collection of tasks and provide APIs like
- * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
- * to perform operations in-sync with other containers.
+ * {@link #addTask(ActivityManager.RunningTaskInfo, WindowContainerTransaction)} for the centralized
+ * {@link StageCoordinator} to perform hierarchy operations in-sync with other containers.
*
* @see StageCoordinator
*/
@@ -310,13 +310,9 @@
wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
}
- void moveToTop(Rect rootBounds, WindowContainerTransaction wct) {
+ void moveToTop(WindowContainerTransaction wct) {
final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */);
- }
-
- void setBounds(Rect bounds, WindowContainerTransaction wct) {
- wct.setBounds(mRootTaskInfo.token, bounds);
+ wct.reorder(rootToken, true /* onTop */);
}
void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 025bcad..33aa018 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -69,6 +69,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -115,8 +116,10 @@
private final Handler mSplashscreenWorkerHandler;
@VisibleForTesting
final ColorCache mColorCache;
+ private final ShellExecutor mSplashScreenExecutor;
- SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) {
+ SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool,
+ ShellExecutor splashScreenExecutor) {
mContext = context;
mIconProvider = iconProvider;
mTransactionPool = pool;
@@ -129,6 +132,7 @@
shellSplashscreenWorkerThread.start();
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
+ mSplashScreenExecutor = splashScreenExecutor;
}
/**
@@ -397,7 +401,7 @@
SplashScreenView build() {
Drawable iconDrawable;
- final int animationDuration;
+ final long animationDuration;
if (mSuggestType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
|| mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
// empty or legacy splash screen case
@@ -455,8 +459,8 @@
iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
} else {
mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable(
- mTmpAttrs.mIconBgColor, mThemeColor,
- iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
+ mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize,
+ mFinalIconSize, mSplashscreenWorkerHandler, mSplashScreenExecutor);
}
}
@@ -516,7 +520,7 @@
}
private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
- int animationDuration, Consumer<Runnable> uiThreadInitTask) {
+ long animationDuration, Consumer<Runnable> uiThreadInitTask) {
Drawable foreground = null;
Drawable background = null;
if (iconDrawable != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 54281e0..fdd5a15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -18,9 +18,7 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +34,8 @@
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Animatable;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Trace;
@@ -44,6 +44,9 @@
import android.window.SplashScreenView;
import com.android.internal.R;
+import com.android.wm.shell.common.ShellExecutor;
+
+import java.util.function.LongConsumer;
/**
* Creating a lightweight Drawable object used for splash screen.
@@ -60,14 +63,15 @@
*/
static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
@NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
- Handler splashscreenWorkerHandler) {
+ Handler splashscreenWorkerHandler, ShellExecutor splashScreenExecutor) {
Drawable foreground;
Drawable background = null;
boolean drawBackground =
backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor;
if (foregroundDrawable instanceof Animatable) {
- foreground = new AnimatableIconAnimateListener(foregroundDrawable);
+ foreground = new AnimatableIconAnimateListener(foregroundDrawable,
+ splashScreenExecutor);
} else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
// If the icon is Adaptive, we already use the icon background.
drawBackground = false;
@@ -266,14 +270,37 @@
*/
public static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable
implements SplashScreenView.IconAnimateListener {
- private Animatable mAnimatableIcon;
- private Animator mIconAnimator;
+ private final Animatable mAnimatableIcon;
private boolean mAnimationTriggered;
private AnimatorListenerAdapter mJankMonitoringListener;
+ private boolean mRunning;
+ private long mDuration;
+ private LongConsumer mStartListener;
+ private final ShellExecutor mSplashScreenExecutor;
- AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable) {
+ AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable,
+ ShellExecutor splashScreenExecutor) {
super(foregroundDrawable);
- mForegroundDrawable.setCallback(mCallback);
+ Callback callback = new Callback() {
+ @Override
+ public void invalidateDrawable(@NonNull Drawable who) {
+ invalidateSelf();
+ }
+
+ @Override
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what,
+ long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ unscheduleSelf(what);
+ }
+ };
+ mForegroundDrawable.setCallback(callback);
+ mSplashScreenExecutor = splashScreenExecutor;
+ mAnimatableIcon = (Animatable) mForegroundDrawable;
}
@Override
@@ -282,83 +309,68 @@
}
@Override
- public boolean prepareAnimate(long duration, Runnable startListener) {
- mAnimatableIcon = (Animatable) mForegroundDrawable;
- mIconAnimator = ValueAnimator.ofInt(0, 1);
- mIconAnimator.setDuration(duration);
- mIconAnimator.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- if (startListener != null) {
- startListener.run();
- }
- try {
- if (mJankMonitoringListener != null) {
- mJankMonitoringListener.onAnimationStart(animation);
- }
- mAnimatableIcon.start();
- } catch (Exception ex) {
- Log.e(TAG, "Error while running the splash screen animated icon", ex);
- animation.cancel();
- }
- }
+ public void prepareAnimate(long duration, LongConsumer startListener) {
+ stopAnimation();
+ mDuration = duration;
+ mStartListener = startListener;
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimatableIcon.stop();
- if (mJankMonitoringListener != null) {
- mJankMonitoringListener.onAnimationEnd(animation);
- }
+ private void startAnimation() {
+ if (mJankMonitoringListener != null) {
+ mJankMonitoringListener.onAnimationStart(null);
+ }
+ try {
+ mAnimatableIcon.start();
+ } catch (Exception ex) {
+ Log.e(TAG, "Error while running the splash screen animated icon", ex);
+ mRunning = false;
+ if (mJankMonitoringListener != null) {
+ mJankMonitoringListener.onAnimationCancel(null);
}
+ if (mStartListener != null) {
+ mStartListener.accept(mDuration);
+ }
+ return;
+ }
+ long animDuration = mDuration;
+ if (mAnimatableIcon instanceof AnimatedVectorDrawable
+ && ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration() > 0) {
+ animDuration = ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration();
+ } else if (mAnimatableIcon instanceof AnimationDrawable
+ && ((AnimationDrawable) mAnimatableIcon).getTotalDuration() > 0) {
+ animDuration = ((AnimationDrawable) mAnimatableIcon).getTotalDuration();
+ }
+ mRunning = true;
+ mSplashScreenExecutor.executeDelayed(this::stopAnimation, animDuration);
+ if (mStartListener != null) {
+ mStartListener.accept(Math.max(animDuration, 0));
+ }
+ }
- @Override
- public void onAnimationCancel(Animator animation) {
- mAnimatableIcon.stop();
- if (mJankMonitoringListener != null) {
- mJankMonitoringListener.onAnimationCancel(animation);
- }
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- // do not repeat
- mAnimatableIcon.stop();
- }
- });
- return true;
+ private void onAnimationEnd() {
+ mAnimatableIcon.stop();
+ if (mJankMonitoringListener != null) {
+ mJankMonitoringListener.onAnimationEnd(null);
+ }
+ mStartListener = null;
+ mRunning = false;
}
@Override
public void stopAnimation() {
- if (mIconAnimator != null && mIconAnimator.isRunning()) {
- mIconAnimator.end();
+ if (mRunning) {
+ mSplashScreenExecutor.removeCallbacks(this::stopAnimation);
+ onAnimationEnd();
mJankMonitoringListener = null;
}
}
- private final Callback mCallback = new Callback() {
- @Override
- public void invalidateDrawable(@NonNull Drawable who) {
- invalidateSelf();
- }
-
- @Override
- public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
- scheduleSelf(what, when);
- }
-
- @Override
- public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
- unscheduleSelf(what);
- }
- };
-
private void ensureAnimationStarted() {
if (mAnimationTriggered) {
return;
}
- if (mIconAnimator != null && !mIconAnimator.isRunning()) {
- mIconAnimator.start();
+ if (!mRunning) {
+ startAnimation();
}
mAnimationTriggered = true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 61cbf6e..04d6ef7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -148,7 +148,8 @@
mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool,
+ mSplashScreenExecutor);
mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
mWindowManagerGlobal = WindowManagerGlobal.getInstance();
mDisplayManager.getDisplay(DEFAULT_DISPLAY);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 83d5f04..daec336 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -28,8 +28,10 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +40,7 @@
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayImeController;
import org.junit.Before;
@@ -56,6 +59,7 @@
@Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
+ @Mock WindowContainerTransaction mWct;
@Captor ArgumentCaptor<Runnable> mRunnableCaptor;
private SplitLayout mSplitLayout;
@@ -149,6 +153,16 @@
verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true));
}
+ @Test
+ public void testApplyTaskChanges_updatesSmallestScreenWidthDp() {
+ final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
+ final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
+ mSplitLayout.applyTaskChanges(mWct, task1, task2);
+
+ verify(mWct).setSmallestScreenWidthDp(eq(task1.token), anyInt());
+ verify(mWct).setSmallestScreenWidthDp(eq(task2.token), anyInt());
+ }
+
private void waitDividerFlingFinished() {
verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index c972067..0639ad5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -67,8 +67,7 @@
@Test
public void testActiveDeactivate() {
- mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct,
- true /* reparent */);
+ mMainStage.activate(mWct, true /* reparent */);
assertThat(mMainStage.isActive()).isTrue();
mMainStage.deactivate(mWct);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 59c377a..19d2a7e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -44,7 +44,6 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
-import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.SurfaceControl;
@@ -421,8 +420,7 @@
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
- mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction(),
- true /* includingTopTask */);
+ mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */);
}
private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 6c99a07..3a2f0d9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5624,27 +5624,33 @@
* Note that the information may be imprecise when the implementation
* cannot distinguish whether a particular device is enabled.
*
- * {@hide}
+ * @deprecated on {@link android.os.Build.VERSION_CODES#T} as new devices
+ * will have multi-bit device types.
+ * Prefer to use {@link #getDevicesForAttributes()} instead,
+ * noting that getDevicesForStream() has a few small discrepancies
+ * for better volume handling.
+ * @hide
*/
@UnsupportedAppUsage
+ @Deprecated
public int getDevicesForStream(int streamType) {
switch (streamType) {
- case STREAM_VOICE_CALL:
- case STREAM_SYSTEM:
- case STREAM_RING:
- case STREAM_MUSIC:
- case STREAM_ALARM:
- case STREAM_NOTIFICATION:
- case STREAM_DTMF:
- case STREAM_ACCESSIBILITY:
- final IAudioService service = getService();
- try {
- return service.getDevicesForStream(streamType);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- default:
- return 0;
+ case STREAM_VOICE_CALL:
+ case STREAM_SYSTEM:
+ case STREAM_RING:
+ case STREAM_MUSIC:
+ case STREAM_ALARM:
+ case STREAM_NOTIFICATION:
+ case STREAM_DTMF:
+ case STREAM_ACCESSIBILITY:
+ final IAudioService service = getService();
+ try {
+ return service.getDeviceMaskForStream(streamType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ default:
+ return 0;
}
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 210f3e5..acb34b5 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -29,6 +29,7 @@
import android.media.audio.common.AidlConversion;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioProductStrategy;
import android.os.Build;
import android.os.IBinder;
import android.os.Parcel;
@@ -47,6 +48,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.TreeSet;
/* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
* TO UPDATE THE CORRESPONDING NATIVE GLUE AND AudioManager.java.
@@ -1655,9 +1657,63 @@
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static native boolean getMasterMute();
- /** @hide */
+ /** @hide
+ * Only used (unsupported) for legacy apps.
+ * @deprecated on {@link android.os.Build.VERSION_CODES#T} as new devices
+ * will have multi-bit device types.
+ * Use {@link AudioManager#getDevicesForAttributes(AudioAttributes)} instead.
+ */
@UnsupportedAppUsage
- public static native int getDevicesForStream(int stream);
+ @Deprecated
+ public static int getDevicesForStream(int stream) {
+ final AudioAttributes attr =
+ AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(stream);
+ return getDeviceMaskFromSet(generateAudioDeviceTypesSet(
+ getDevicesForAttributes(attr, true /* forVolume */)));
+ }
+
+ /** @hide
+ * Conversion from a device set to a bit mask.
+ *
+ * Legacy compatibility method (use a device list instead of a bit mask).
+ * Conversion to bit mask skips multi-bit (S and later) internal device types
+ * (e.g. AudioSystem.DEVICE_OUT* or AudioManager.DEVICE_OUT*) for legacy
+ * compatibility reasons. Legacy apps will not understand these new device types
+ * and it will raise false matches with old device types.
+ */
+ public static int getDeviceMaskFromSet(@NonNull Set<Integer> deviceSet) {
+ int deviceMask = DEVICE_NONE; // zero.
+ int deviceInChecksum = DEVICE_BIT_IN;
+ for (Integer device : deviceSet) {
+ if ((device & (device - 1) & ~DEVICE_BIT_IN) != 0) {
+ Log.v(TAG, "getDeviceMaskFromSet skipping multi-bit device value " + device);
+ continue;
+ }
+ deviceMask |= device;
+ deviceInChecksum &= device;
+ }
+ // Validate that deviceSet is either ALL input devices or ALL output devices.
+ // We check that the "OR" of all the DEVICE_BIT_INs == the "AND" of all DEVICE_BIT_INs.
+ if (!deviceSet.isEmpty() && deviceInChecksum != (deviceMask & DEVICE_BIT_IN)) {
+ Log.e(TAG, "getDeviceMaskFromSet: Invalid set: " + deviceSetToString(deviceSet));
+ }
+ return deviceMask;
+ }
+
+ /** @hide */
+ @NonNull
+ public static String deviceSetToString(@NonNull Set<Integer> devices) {
+ int n = 0;
+ StringBuilder sb = new StringBuilder();
+ for (Integer device : devices) {
+ if (n++ > 0) {
+ sb.append(", ");
+ }
+ sb.append(AudioSystem.getDeviceName(device));
+ sb.append("(" + Integer.toHexString(device) + ")");
+ }
+ return sb.toString();
+ }
/**
* @hide
@@ -2252,18 +2308,15 @@
/**
* @hide
- * Return a set of audio device types from a bit mask audio device type, which may
+ * Return a set of audio device types from a list of audio device attributes, which may
* represent multiple audio device types.
- * FIXME: Remove this when getting ride of bit mask usage of audio device types.
*/
- public static Set<Integer> generateAudioDeviceTypesSet(int types) {
- Set<Integer> deviceTypes = new HashSet<>();
- Set<Integer> allDeviceTypes =
- (types & DEVICE_BIT_IN) == 0 ? DEVICE_OUT_ALL_SET : DEVICE_IN_ALL_SET;
- for (int deviceType : allDeviceTypes) {
- if ((types & deviceType) == deviceType) {
- deviceTypes.add(deviceType);
- }
+ @NonNull
+ public static Set<Integer> generateAudioDeviceTypesSet(
+ @NonNull List<AudioDeviceAttributes> deviceList) {
+ Set<Integer> deviceTypes = new TreeSet<>();
+ for (AudioDeviceAttributes device : deviceList) {
+ deviceTypes.add(device.getInternalType());
}
return deviceTypes;
}
@@ -2274,7 +2327,7 @@
*/
public static Set<Integer> intersectionAudioDeviceTypes(
@NonNull Set<Integer> a, @NonNull Set<Integer> b) {
- Set<Integer> intersection = new HashSet<>(a);
+ Set<Integer> intersection = new TreeSet<>(a);
intersection.retainAll(b);
return intersection;
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d702eb9..fa3057a 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -360,7 +360,7 @@
boolean isMusicActive(in boolean remotely);
- int getDevicesForStream(in int streamType);
+ int getDeviceMaskForStream(in int streamType);
int[] getAvailableCommunicationDeviceIds();
diff --git a/media/java/android/media/NearbyDevice.java b/media/java/android/media/NearbyDevice.java
index cb85109..c203e7b 100644
--- a/media/java/android/media/NearbyDevice.java
+++ b/media/java/android/media/NearbyDevice.java
@@ -81,6 +81,8 @@
/**
* Gets a human-readable string of the range zone.
+ *
+ * @hide
*/
@NonNull
public static String rangeZoneToString(@RangeZone int rangeZone) {
@@ -103,13 +105,14 @@
@NonNull private final String mMediaRoute2Id;
@RangeZone private final int mRangeZone;
- public NearbyDevice(@NonNull String mediaRoute2Id, int rangeZone) {
+ /** Creates a device object with the given ID and range zone. */
+ public NearbyDevice(@NonNull String mediaRoute2Id, @RangeZone int rangeZone) {
mMediaRoute2Id = mediaRoute2Id;
mRangeZone = rangeZone;
}
private NearbyDevice(@NonNull Parcel in) {
- mMediaRoute2Id = in.readString();
+ mMediaRoute2Id = in.readString8();
mRangeZone = in.readInt();
}
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index 50bb79c..9ed8770 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -15,6 +15,7 @@
*/
//#define LOG_NDEBUG 0
+#include <utility>
#define LOG_TAG "SoundPool::Stream"
#include <utils/Log.h>
#include <android/content/AttributionSourceState.h>
@@ -309,13 +310,11 @@
}
if (mAudioTrack == nullptr) {
// mToggle toggles each time a track is started on a given stream.
- // The toggle is concatenated with the Stream address and passed to AudioTrack
- // as callback user data. This enables the detection of callbacks received from the old
+ // This enables the detection of callbacks received from the old
// audio track while the new one is being started and avoids processing them with
// wrong audio audio buffer size (mAudioBufferSize)
auto toggle = mToggle ^ 1;
// NOLINTNEXTLINE(performance-no-int-to-ptr)
- void* userData = reinterpret_cast<void*>((uintptr_t)this | toggle);
audio_channel_mask_t soundChannelMask = sound->getChannelMask();
// When sound contains a valid channel mask, use it as is.
// Otherwise, use stream count to calculate channel mask.
@@ -327,10 +326,11 @@
android::content::AttributionSourceState attributionSource;
attributionSource.packageName = mStreamManager->getOpPackageName();
attributionSource.token = sp<BBinder>::make();
+ mCallback = sp<StreamCallback>::make(this, toggle),
// TODO b/182469354 make consistent with AudioRecord, add util for native source
mAudioTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
- staticCallback, userData,
+ mCallback,
0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
AudioTrack::TRANSFER_DEFAULT,
nullptr /*offloadInfo*/, attributionSource,
@@ -375,16 +375,55 @@
mStreamID = nextStreamID; // prefer this to be the last, as it is an atomic sync point
}
-/* static */
-void Stream::staticCallback(int event, void* user, void* info)
-{
- const auto userAsInt = (uintptr_t)user;
- // NOLINTNEXTLINE(performance-no-int-to-ptr)
- auto stream = reinterpret_cast<Stream*>(userAsInt & ~1);
- stream->callback(event, info, int(userAsInt & 1), 0 /* tries */);
+int Stream::getCorrespondingStreamID() {
+ std::lock_guard lock(mLock);
+ return static_cast<int>(mAudioTrack ? mStreamID : getPairStream()->mStreamID);
+}
+size_t Stream::StreamCallback::onMoreData(const AudioTrack::Buffer&) {
+ ALOGW("%s streamID %d Unexpected EVENT_MORE_DATA for static track",
+ __func__, mStream->getCorrespondingStreamID());
+ return 0;
}
-void Stream::callback(int event, void* info, int toggle, int tries)
+void Stream::StreamCallback::onUnderrun() {
+ ALOGW("%s streamID %d Unexpected EVENT_UNDERRUN for static track",
+ __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onLoopEnd(int32_t) {
+ ALOGV("%s streamID %d EVENT_LOOP_END", __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onMarker(uint32_t) {
+ ALOGW("%s streamID %d Unexpected EVENT_MARKER for static track",
+ __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onNewPos(uint32_t) {
+ ALOGW("%s streamID %d Unexpected EVENT_NEW_POS for static track",
+ __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onBufferEnd() {
+ mStream->onBufferEnd(mToggle, 0);
+}
+
+void Stream::StreamCallback::onNewIAudioTrack() {
+ ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, mStream->getCorrespondingStreamID());
+}
+
+void Stream::StreamCallback::onStreamEnd() {
+ ALOGW("%s streamID %d Unexpected EVENT_STREAM_END for static track",
+ __func__, mStream->getCorrespondingStreamID());
+}
+
+size_t Stream::StreamCallback::onCanWriteMoreData(const AudioTrack::Buffer&) {
+ ALOGW("%s streamID %d Unexpected EVENT_CAN_WRITE_MORE_DATA for static track",
+ __func__, mStream->getCorrespondingStreamID());
+ return 0;
+}
+
+void Stream::onBufferEnd(int toggle, int tries)
{
int32_t activeStreamIDToRestart = 0;
{
@@ -400,7 +439,7 @@
if (tries < 3) {
lock.unlock();
ALOGV("%s streamID %d going to pair stream", __func__, (int)mStreamID);
- getPairStream()->callback(event, info, toggle, tries + 1);
+ getPairStream()->onBufferEnd(toggle, tries + 1);
} else {
ALOGW("%s streamID %d cannot find track", __func__, (int)mStreamID);
}
@@ -410,31 +449,10 @@
ALOGD("%s streamID %d wrong toggle", __func__, (int)mStreamID);
return;
}
- switch (event) {
- case AudioTrack::EVENT_MORE_DATA:
- ALOGW("%s streamID %d Invalid EVENT_MORE_DATA for static track",
- __func__, (int)mStreamID);
- break;
- case AudioTrack::EVENT_UNDERRUN:
- ALOGW("%s streamID %d Invalid EVENT_UNDERRUN for static track",
- __func__, (int)mStreamID);
- break;
- case AudioTrack::EVENT_BUFFER_END:
- ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
- if (mState != IDLE) {
- activeStreamIDToRestart = mStreamID;
- mStopTimeNs = systemTime();
- }
- break;
- case AudioTrack::EVENT_LOOP_END:
- ALOGV("%s streamID %d EVENT_LOOP_END", __func__, (int)mStreamID);
- break;
- case AudioTrack::EVENT_NEW_IAUDIOTRACK:
- ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, (int)mStreamID);
- break;
- default:
- ALOGW("%s streamID %d Invalid event %d", __func__, (int)mStreamID, event);
- break;
+ ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
+ if (mState != IDLE) {
+ activeStreamIDToRestart = mStreamID;
+ mStopTimeNs = systemTime();
}
} // lock ends here. This is on the callback thread, no need to be precise.
if (activeStreamIDToRestart > 0) {
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
index aa0eef5..0054eec 100644
--- a/media/jni/soundpool/Stream.h
+++ b/media/jni/soundpool/Stream.h
@@ -124,6 +124,35 @@
// This never changes. See top of header.
Stream* getPairStream() const;
+ // Stream ID of ourselves, or the pair depending on who holds the AudioTrack
+ int getCorrespondingStreamID();
+
+protected:
+ // AudioTrack callback interface implementation
+ class StreamCallback : public AudioTrack::IAudioTrackCallback {
+ public:
+ StreamCallback(Stream * stream, bool toggle) : mStream(stream), mToggle(toggle) {}
+ size_t onMoreData(const AudioTrack::Buffer& buffer) override;
+ void onUnderrun() override;
+ void onLoopEnd(int32_t loopsRemaining) override;
+ void onMarker(uint32_t markerPosition) override;
+ void onNewPos(uint32_t newPos) override;
+ void onBufferEnd() override;
+ void onNewIAudioTrack() override;
+ void onStreamEnd() override;
+ size_t onCanWriteMoreData(const AudioTrack::Buffer& buffer) override;
+
+ // Holding a raw ptr is technically unsafe, but, Stream objects persist
+ // through the lifetime of the StreamManager through the use of a
+ // unique_ptr<Stream[]>. Ensuring lifetime will cause us to give up
+ // locality as well as pay RefBase/sp performance cost, which we are
+ // unwilling to do. Non-owning refs to unique_ptrs are idiomatically raw
+ // ptrs, as below.
+ Stream * const mStream;
+ const bool mToggle;
+ };
+
+ sp<StreamCallback> mCallback;
private:
// garbage is used to release tracks and data outside of any lock.
void play_l(const std::shared_ptr<Sound>& sound, int streamID,
@@ -133,9 +162,7 @@
void setVolume_l(float leftVolume, float rightVolume) REQUIRES(mLock);
// For use with AudioTrack callback.
- static void staticCallback(int event, void* user, void* info);
- void callback(int event, void* info, int toggle, int tries)
- NO_THREAD_SAFETY_ANALYSIS; // uses unique_lock
+ void onBufferEnd(int toggle, int tries) NO_THREAD_SAFETY_ANALYSIS;
// StreamManager should be set on construction and not changed.
// release mLock before calling into StreamManager
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
deleted file mode 100644
index f23794b..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2021 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.mediaframeworktest.unit;
-
-import static org.junit.Assert.assertEquals;
-
-import android.bluetooth.BluetoothProfile;
-import android.media.BluetoothProfileConnectionInfo;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class BluetoothProfileConnectionInfoTest {
-
- @Test
- public void testCoverageA2dp() {
- final boolean supprNoisy = false;
- final int volume = 42;
- final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
- .createA2dpInfo(supprNoisy, volume);
- assertEquals(info.getProfile(), BluetoothProfile.A2DP);
- assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
- assertEquals(info.getVolume(), volume);
- }
-
- @Test
- public void testCoverageA2dpSink() {
- final int volume = 42;
- final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
- .createA2dpSinkInfo(volume);
- assertEquals(info.getProfile(), BluetoothProfile.A2DP_SINK);
- assertEquals(info.getVolume(), volume);
- }
-
- @Test
- public void testCoveragehearingAid() {
- final boolean supprNoisy = true;
- final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
- .createHearingAidInfo(supprNoisy);
- assertEquals(info.getProfile(), BluetoothProfile.HEARING_AID);
- assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
- }
-
- @Test
- public void testCoverageLeAudio() {
- final boolean supprNoisy = false;
- final boolean isLeOutput = true;
- final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
- .createLeAudioInfo(supprNoisy, isLeOutput);
- assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO);
- assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
- assertEquals(info.isLeOutput(), isLeOutput);
- }
-}
-
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 0c36051..65428de 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -48,6 +48,7 @@
static APerformanceHintManager* create(sp<IHintManager> iHintManager);
sp<IHintManager> mHintManager;
+ const sp<IBinder> mToken = sp<BBinder>::make();
const int64_t mPreferredRateNanos;
};
@@ -119,11 +120,10 @@
APerformanceHintSession* APerformanceHintManager::createSession(
const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos) {
- sp<IBinder> token = sp<BBinder>::make();
std::vector<int32_t> tids(threadIds, threadIds + size);
sp<IHintSession> session;
binder::Status ret =
- mHintManager->createHintSession(token, tids, initialTargetWorkDurationNanos, &session);
+ mHintManager->createHintSession(mToken, tids, initialTargetWorkDurationNanos, &session);
if (!ret.isOk() || !session) {
return nullptr;
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 7a5ea47..1b7298a 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -19,6 +19,7 @@
"androidx.appcompat_appcompat",
"androidx.lifecycle_lifecycle-runtime",
"androidx.mediarouter_mediarouter-nodeps",
+ "com.google.android.material_material",
"iconloader",
"WifiTrackerLibRes",
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 77c4533..5e3907c 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -31,7 +31,7 @@
<!-- Dialog accent color -->
<color name="settingslib_dialog_accent">@android:color/system_accent1_100</color>
<!-- Dialog background color. -->
- <color name="settingslib_dialog_background">@android:color/system_neutral1_800</color>
+ <color name="settingslib_dialog_background">@color/settingslib_surface_dark</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
@@ -49,4 +49,8 @@
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color>
<color name="settingslib_ripple_color">@color/settingslib_material_grey_900</color>
+
+ <color name="settingslib_surface_dark">@android:color/system_neutral1_800</color>
+
+ <color name="settingslib_colorSurface">@color/settingslib_surface_dark</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index 6adb789..c4dbc5e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -71,5 +71,12 @@
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color>
<color name="settingslib_ripple_color">?android:attr/colorControlHighlight</color>
+
<color name="settingslib_material_grey_900">#ff212121</color>
+
+ <color name="settingslib_colorAccentPrimary">@color/settingslib_accent_primary_device_default</color>
+
+ <color name="settingslib_colorAccentSecondary">@color/settingslib_accent_secondary_device_default</color>
+
+ <color name="settingslib_colorSurface">@color/settingslib_surface_light</color>
</resources>
diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..9a09360
--- /dev/null
+++ b/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/settingslib_colorAccentSecondary" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml
new file mode 100644
index 0000000..33f96df
--- /dev/null
+++ b/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true" android:color="?android:attr/textColorPrimaryInverse"/>
+ <item android:state_enabled="false" android:color="?android:attr/textColorTertiary"/>
+ <item android:color="?android:attr/textColorSecondary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..57fef52f
--- /dev/null
+++ b/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/settingslib_colorAccentPrimary" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml
new file mode 100644
index 0000000..df2346d
--- /dev/null
+++ b/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true" android:color="?android:attr/textColorPrimary"/>
+ <item android:state_enabled="false" android:color="?android:attr/textColorTertiary"/>
+ <item android:color="?android:attr/textColorSecondary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml b/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml
new file mode 100644
index 0000000..f10b563
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <inset
+ android:insetLeft="4dp"
+ android:insetRight="4dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="12dp" />
+ <solid android:color="?android:colorControlHighlight" />
+ </shape>
+ </inset>
+ </item>
+
+ <item android:id="@android:id/background">
+ <inset
+ android:insetLeft="4dp"
+ android:insetRight="4dp">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/settingslib_colorSurface" />
+ <corners android:radius="12dp" />
+ </shape>
+ </inset>
+ </item>
+</ripple>
diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml b/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml
new file mode 100644
index 0000000..f5a9782
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="4dp"
+ android:insetRight="4dp">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/settingslib_tabs_indicator_color" />
+ <corners android:radius="12dp" />
+ </shape>
+</inset>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-v31/styles.xml b/packages/SettingsLib/res/values-v31/styles.xml
new file mode 100644
index 0000000..343de2c
--- /dev/null
+++ b/packages/SettingsLib/res/values-v31/styles.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+<resources>
+ <style name="SettingsLibTabsTextAppearance" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <item name="android:textSize">16sp</item>
+ </style>
+
+ <style name="SettingsLibTabsStyle" parent="Base.Widget.Design.TabLayout">
+ <item name="android:background">@android:color/transparent</item>
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">48dp</item>
+ <item name="android:layout_marginStart">?android:attr/listPreferredItemPaddingStart</item>
+ <item name="android:layout_marginEnd">?android:attr/listPreferredItemPaddingEnd</item>
+ <item name="tabGravity">fill</item>
+ <item name="tabBackground">@drawable/settingslib_tabs_background</item>
+ <item name="tabIndicator">@drawable/settingslib_tabs_indicator_background</item>
+ <item name="tabIndicatorColor">@color/settingslib_tabs_indicator_color</item>
+ <item name="tabIndicatorFullWidth">true</item>
+ <item name="tabIndicatorGravity">stretch</item>
+ <item name="tabIndicatorAnimationMode">fade</item>
+ <item name="tabIndicatorAnimationDuration">0</item>
+ <item name="tabTextAppearance">@style/SettingsLibTabsTextAppearance</item>
+ <item name="tabTextColor">@color/settingslib_tabs_text_color</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
index 61b8911..0cb2c0b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
@@ -21,7 +21,8 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.database.Cursor;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -30,15 +31,15 @@
import android.graphics.RectF;
import android.media.ExifInterface;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.StrictMode;
-import android.provider.ContactsContract;
import android.provider.MediaStore;
import android.util.EventLog;
import android.util.Log;
import androidx.core.content.FileProvider;
+import com.android.settingslib.utils.ThreadUtils;
+
import libcore.io.Streams;
import java.io.File;
@@ -47,39 +48,64 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.concurrent.ExecutionException;
class AvatarPhotoController {
+
+ interface AvatarUi {
+ boolean isFinishing();
+
+ void returnUriResult(Uri uri);
+
+ void startActivityForResult(Intent intent, int resultCode);
+
+ boolean startSystemActivityForResult(Intent intent, int resultCode);
+
+ int getPhotoSize();
+ }
+
+ interface ContextInjector {
+ File getCacheDir();
+
+ Uri createTempImageUri(File parentDir, String fileName, boolean purge);
+
+ ContentResolver getContentResolver();
+ }
+
private static final String TAG = "AvatarPhotoController";
- private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
- private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
- private static final int REQUEST_CODE_CROP_PHOTO = 1003;
- // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
- // so we need a default photo size
- private static final int DEFAULT_PHOTO_SIZE = 500;
+ static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+ static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+ static final int REQUEST_CODE_CROP_PHOTO = 1003;
private static final String IMAGES_DIR = "multi_user";
+ private static final String PRE_CROP_PICTURE_FILE_NAME = "PreCropEditUserPhoto.jpg";
private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
private final int mPhotoSize;
- private final AvatarPickerActivity mActivity;
- private final String mFileAuthority;
+ private final AvatarUi mAvatarUi;
+ private final ContextInjector mContextInjector;
private final File mImagesDir;
+ private final Uri mPreCropPictureUri;
private final Uri mCropPictureUri;
private final Uri mTakePictureUri;
- AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) {
- mActivity = activity;
- mFileAuthority = fileAuthority;
+ AvatarPhotoController(AvatarUi avatarUi, ContextInjector contextInjector, boolean waiting) {
+ mAvatarUi = avatarUi;
+ mContextInjector = contextInjector;
- mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
+ mImagesDir = new File(mContextInjector.getCacheDir(), IMAGES_DIR);
mImagesDir.mkdir();
- mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
- mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
- mPhotoSize = getPhotoSize(activity);
+ mPreCropPictureUri = mContextInjector
+ .createTempImageUri(mImagesDir, PRE_CROP_PICTURE_FILE_NAME, !waiting);
+ mCropPictureUri =
+ mContextInjector.createTempImageUri(mImagesDir, CROP_PICTURE_FILE_NAME, !waiting);
+ mTakePictureUri =
+ mContextInjector.createTempImageUri(mImagesDir, TAKE_PICTURE_FILE_NAME, !waiting);
+ mPhotoSize = mAvatarUi.getPhotoSize();
}
/**
@@ -102,16 +128,12 @@
switch (requestCode) {
case REQUEST_CODE_CROP_PHOTO:
- mActivity.returnUriResult(pictureUri);
+ mAvatarUi.returnUriResult(pictureUri);
return true;
case REQUEST_CODE_TAKE_PHOTO:
case REQUEST_CODE_CHOOSE_PHOTO:
if (mTakePictureUri.equals(pictureUri)) {
- if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
- cropPhoto();
- } else {
- onPhotoNotCropped(pictureUri);
- }
+ cropPhoto(pictureUri);
} else {
copyAndCropPhoto(pictureUri);
}
@@ -123,55 +145,52 @@
void takePhoto() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
appendOutputExtra(intent, mTakePictureUri);
- mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+ mAvatarUi.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
}
void choosePhoto() {
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null);
intent.setType("image/*");
- mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+ mAvatarUi.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
}
private void copyAndCropPhoto(final Uri pictureUri) {
- // TODO: Replace AsyncTask
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- final ContentResolver cr = mActivity.getContentResolver();
+ try {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ final ContentResolver cr = mContextInjector.getContentResolver();
try (InputStream in = cr.openInputStream(pictureUri);
- OutputStream out = cr.openOutputStream(mTakePictureUri)) {
+ OutputStream out = cr.openOutputStream(mPreCropPictureUri)) {
Streams.copy(in, out);
} catch (IOException e) {
Log.w(TAG, "Failed to copy photo", e);
+ return;
}
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
- cropPhoto();
- }
- }
- }.execute();
+ ThreadUtils.postOnMainThread(() -> {
+ if (!mAvatarUi.isFinishing()) {
+ cropPhoto(mPreCropPictureUri);
+ }
+ });
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error performing copy-and-crop", e);
+ }
}
- private void cropPhoto() {
+ private void cropPhoto(final Uri pictureUri) {
// TODO: Use a public intent, when there is one.
Intent intent = new Intent("com.android.camera.action.CROP");
- intent.setDataAndType(mTakePictureUri, "image/*");
+ intent.setDataAndType(pictureUri, "image/*");
appendOutputExtra(intent, mCropPictureUri);
appendCropExtras(intent);
- if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
- try {
- StrictMode.disableDeathOnFileUriExposure();
- mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
+ try {
+ StrictMode.disableDeathOnFileUriExposure();
+ if (mAvatarUi.startSystemActivityForResult(intent, REQUEST_CODE_CROP_PHOTO)) {
+ return;
}
- } else {
- onPhotoNotCropped(mTakePictureUri);
+ } finally {
+ StrictMode.enableDeathOnFileUriExposure();
}
+ onPhotoNotCropped(pictureUri);
}
private void appendOutputExtra(Intent intent, Uri pictureUri) {
@@ -192,24 +211,22 @@
}
private void onPhotoNotCropped(final Uri data) {
- // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
+ try {
+ ThreadUtils.postOnBackgroundThread(() -> {
// Scale and crop to a square aspect ratio
Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(croppedImage);
Bitmap fullImage;
try {
- InputStream imageStream = mActivity.getContentResolver()
+ InputStream imageStream = mContextInjector.getContentResolver()
.openInputStream(data);
fullImage = BitmapFactory.decodeStream(imageStream);
} catch (FileNotFoundException fe) {
- return null;
+ return;
}
if (fullImage != null) {
- int rotation = getRotation(mActivity, data);
+ int rotation = getRotation(data);
final int squareSize = Math.min(fullImage.getWidth(),
fullImage.getHeight());
final int left = (fullImage.getWidth() - squareSize) / 2;
@@ -222,29 +239,27 @@
matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
canvas.drawBitmap(fullImage, matrix, new Paint());
- return croppedImage;
- } else {
- // Bah! Got nothin.
- return null;
- }
- }
+ saveBitmapToFile(croppedImage, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
- mActivity.returnUriResult(mCropPictureUri);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ ThreadUtils.postOnMainThread(() -> {
+ mAvatarUi.returnUriResult(mCropPictureUri);
+ });
+ }
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error performing internal crop", e);
+ }
}
/**
* Reads the image's exif data and determines the rotation degree needed to display the image
* in portrait mode.
*/
- private int getRotation(Context context, Uri selectedImage) {
+ private int getRotation(Uri selectedImage) {
int rotation = -1;
try {
- InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
+ InputStream imageStream =
+ mContextInjector.getContentResolver().openInputStream(selectedImage);
ExifInterface exif = new ExifInterface(imageStream);
rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
} catch (IOException exception) {
@@ -274,24 +289,74 @@
}
}
- private static int getPhotoSize(Context context) {
- try (Cursor cursor = context.getContentResolver().query(
- ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
- new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
- if (cursor != null) {
- cursor.moveToFirst();
- return cursor.getInt(0);
- } else {
- return DEFAULT_PHOTO_SIZE;
+ static class AvatarUiImpl implements AvatarUi {
+ private final AvatarPickerActivity mActivity;
+
+ AvatarUiImpl(AvatarPickerActivity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public boolean isFinishing() {
+ return mActivity.isFinishing() || mActivity.isDestroyed();
+ }
+
+ @Override
+ public void returnUriResult(Uri uri) {
+ mActivity.returnUriResult(uri);
+ }
+
+ @Override
+ public void startActivityForResult(Intent intent, int resultCode) {
+ mActivity.startActivityForResult(intent, resultCode);
+ }
+
+ @Override
+ public boolean startSystemActivityForResult(Intent intent, int code) {
+ ActivityInfo info = intent.resolveActivityInfo(mActivity.getPackageManager(),
+ PackageManager.MATCH_SYSTEM_ONLY);
+ if (info == null) {
+ Log.w(TAG, "No system package activity could be found for code " + code);
+ return false;
}
+ intent.setPackage(info.packageName);
+ mActivity.startActivityForResult(intent, code);
+ return true;
+ }
+
+ @Override
+ public int getPhotoSize() {
+ return mActivity.getResources()
+ .getDimensionPixelSize(com.android.internal.R.dimen.user_icon_size);
}
}
- private Uri createTempImageUri(Context context, String fileName, boolean purge) {
- final File fullPath = new File(mImagesDir, fileName);
- if (purge) {
- fullPath.delete();
+ static class ContextInjectorImpl implements ContextInjector {
+ private final Context mContext;
+ private final String mFileAuthority;
+
+ ContextInjectorImpl(Context context, String fileAuthority) {
+ mContext = context;
+ mFileAuthority = fileAuthority;
}
- return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
+
+ @Override
+ public File getCacheDir() {
+ return mContext.getCacheDir();
+ }
+
+ @Override
+ public Uri createTempImageUri(File parentDir, String fileName, boolean purge) {
+ final File fullPath = new File(parentDir, fileName);
+ if (purge) {
+ fullPath.delete();
+ }
+ return FileProvider.getUriForFile(mContext, mFileAuthority, fullPath);
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContext.getContentResolver();
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 1e1dfae..75bb70a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -95,7 +95,9 @@
restoreState(savedInstanceState);
mAvatarPhotoController = new AvatarPhotoController(
- this, mWaitingForActivityResult, getFileAuthority());
+ new AvatarPhotoController.AvatarUiImpl(this),
+ new AvatarPhotoController.ContextInjectorImpl(this, getFileAuthority()),
+ mWaitingForActivityResult);
}
private void setUpButtons() {
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index da808dd..2a4dfdd 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,10 +25,19 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-
<application>
<uses-library android:name="android.test.runner" />
<activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="com.android.settingslib.test"
+ android:grantUriPermissions="true"
+ android:exported="false">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/packages/SettingsLib/tests/integ/res/xml/file_paths.xml b/packages/SettingsLib/tests/integ/res/xml/file_paths.xml
new file mode 100644
index 0000000..ccd11a4
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/res/xml/file_paths.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Offer access to files under Context.getCacheDir() -->
+ <cache-path name="my_cache" />
+</paths>
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
new file mode 100644
index 0000000..9ebdba3
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2022 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.settingslib.users;
+
+import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CHOOSE_PHOTO;
+import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CROP_PHOTO;
+import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_TAKE_PHOTO;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+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;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@RunWith(AndroidJUnit4.class)
+public class AvatarPhotoControllerTest {
+
+ private static final long TIMEOUT_MILLIS = 5000;
+ private static final int PHOTO_SIZE = 200;
+
+ @Mock AvatarPhotoController.AvatarUi mMockAvatarUi;
+
+ private File mImagesDir;
+ private AvatarPhotoController mController;
+ private Uri mTakePhotoUri = Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/TakeEditUserPhoto.jpg");
+ private Uri mCropPhotoUri = Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/CropEditUserPhoto.jpg");
+ private Context mContext = InstrumentationRegistry.getTargetContext();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockAvatarUi.getPhotoSize()).thenReturn(PHOTO_SIZE);
+ when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(true);
+
+ mImagesDir = new File(
+ InstrumentationRegistry.getTargetContext().getCacheDir(), "multi_user");
+ mImagesDir.mkdir();
+
+ AvatarPhotoController.ContextInjector contextInjector =
+ new AvatarPhotoController.ContextInjectorImpl(
+ InstrumentationRegistry.getTargetContext(), "com.android.settingslib.test");
+ mController = new AvatarPhotoController(mMockAvatarUi, contextInjector, false);
+ }
+
+ @After
+ public void tearDown() {
+ mImagesDir.delete();
+ }
+
+ @Test
+ public void takePhotoHasCorrectIntentAndResultCode() {
+ mController.takePhoto();
+
+ verifyStartActivityForResult(
+ MediaStore.ACTION_IMAGE_CAPTURE_SECURE, REQUEST_CODE_TAKE_PHOTO);
+ }
+
+ @Test
+ public void choosePhotoHasCorrectIntentAndResultCode() {
+ mController.choosePhoto();
+
+ verifyStartActivityForResult(
+ MediaStore.ACTION_PICK_IMAGES, REQUEST_CODE_CHOOSE_PHOTO);
+ }
+
+ @Test
+ public void takePhotoIsFollowedByCrop() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ }
+
+ @Test
+ public void takePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_CANCELED, intent);
+
+ verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
+ verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
+ }
+
+ @Test
+ public void takePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
+ new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(mTakePhotoUri);
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ }
+
+ @Test
+ public void takePhotoIsNotFollowedByCropIntentWhenCropNotSupported() throws IOException {
+ when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false);
+
+ File file = new File(mImagesDir, "file.txt");
+ saveBitmapToFile(file);
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
+ verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
+ }
+
+ @Test
+ public void choosePhotoIsFollowedByCrop() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
+
+ verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ }
+
+ @Test
+ public void choosePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_CANCELED, intent);
+
+ verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
+ verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
+ }
+
+ @Test
+ public void choosePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
+ new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(mTakePhotoUri);
+ mController.onActivityResult(
+ REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
+
+ verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ }
+
+ @Test
+ public void cropPhotoResultIsReturnedIfResultOkAndContent() {
+ Intent intent = new Intent();
+ intent.setData(mCropPhotoUri);
+ mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
+ }
+
+ @Test
+ public void cropPhotoResultIsNotReturnedIfResultCancel() {
+ Intent intent = new Intent();
+ intent.setData(mCropPhotoUri);
+ mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_CANCELED, intent);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
+ }
+
+ @Test
+ public void cropPhotoResultIsNotReturnedIfResultNotContent() {
+ Intent intent = new Intent();
+ intent.setData(Uri.parse("file://test"));
+ mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
+ }
+
+ @Test
+ public void cropDoesNotUseTakePhotoUri() throws IOException {
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ Intent startIntent = verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ assertThat(startIntent.getData()).isNotEqualTo(mTakePhotoUri);
+ }
+
+ @Test
+ public void internalCropUsedIfNoSystemCropperFound() throws IOException {
+ when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false);
+
+ new File(mImagesDir, "file.txt").createNewFile();
+
+ Intent intent = new Intent();
+ intent.setData(Uri.parse(
+ "content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
+ mController.onActivityResult(
+ REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
+
+ Intent startIntent = verifyStartSystemActivityForResult(
+ "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
+ assertThat(startIntent.getData()).isNotEqualTo(mTakePhotoUri);
+
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
+
+ InputStream imageStream = mContext.getContentResolver().openInputStream(mCropPhotoUri);
+ Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
+ assertThat(bitmap.getWidth()).isEqualTo(PHOTO_SIZE);
+ assertThat(bitmap.getHeight()).isEqualTo(PHOTO_SIZE);
+ }
+
+ private Intent verifyStartActivityForResult(String action, int resultCode) {
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS))
+ .startActivityForResult(captor.capture(), eq(resultCode));
+ Intent intent = captor.getValue();
+ assertThat(intent.getAction()).isEqualTo(action);
+ return intent;
+ }
+
+ private Intent verifyStartSystemActivityForResult(String action, int resultCode) {
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS))
+ .startSystemActivityForResult(captor.capture(), eq(resultCode));
+ Intent intent = captor.getValue();
+ assertThat(intent.getAction()).isEqualTo(action);
+ return intent;
+ }
+
+ private void saveBitmapToFile(File file) throws IOException {
+ Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
+ OutputStream os = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+ os.flush();
+ os.close();
+ }
+
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 42aa205..fa3360c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -231,6 +231,7 @@
VALIDATORS.put(Secure.SKIP_DIRECTION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SILENCE_GESTURE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, JSON_OBJECT_VALIDATOR);
+ VALIDATORS.put(Secure.NAV_BAR_FORCE_VISIBLE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NAV_BAR_KIDS_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.NAVIGATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a2b6992..1c5bf81 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2253,8 +2253,11 @@
SecureSettingsProto.MULTI_PRESS_TIMEOUT);
dumpSetting(s, p,
+ Settings.Secure.NAV_BAR_FORCE_VISIBLE,
+ SecureSettingsProto.NavBar.NAV_BAR_FORCE_VISIBLE);
+ dumpSetting(s, p,
Settings.Secure.NAV_BAR_KIDS_MODE,
- SecureSettingsProto.NAV_BAR_KIDS_MODE);
+ SecureSettingsProto.NavBar.NAV_BAR_KIDS_MODE);
dumpSetting(s, p,
Settings.Secure.NAVIGATION_MODE,
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
index 0ae5dc7..5084ca4 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
@@ -1,254 +1 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!-- Copyright (C) 2021 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
- android:viewportWidth="60">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
- <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
- android:translateY="38.75" android:scaleX="1" android:scaleY="1">
- <path android:name="_R_G_L_1_G_D_0_P_0"
- android:fillColor="@color/biometric_dialog_error"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
- </group>
- <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
- android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
- android:scaleX="1" android:scaleY="1">
- <path android:name="_R_G_L_1_G_D_1_P_0"
- android:fillColor="@color/biometric_dialog_error"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
- </group>
- <path android:name="_R_G_L_1_G_D_2_P_0"
- android:strokeColor="@color/biometric_dialog_error"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2.5" android:strokeAlpha="1"
- android:trimPathStart="0" android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-10.325"
- android:translateY="-10.25">
- <path android:name="_R_G_L_0_G_D_0_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="0" android:trimPathOffset="0"
- android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
- <path android:name="_R_G_L_0_G_D_1_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="0" android:trimPathOffset="0"
- android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
- <path android:name="_R_G_L_0_G_D_2_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="0" android:trimPathOffset="0"
- android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
- <path android:name="_R_G_L_0_G_D_3_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="0" android:trimPathOffset="0"
- android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="100"
- android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="100"
- android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="100"
- android:startOffset="67" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="100"
- android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="67"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="133"
- android:startOffset="67" android:valueFrom="1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
- android:startOffset="83" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_1_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
- android:startOffset="83" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
- android:startOffset="83" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_3_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
- android:startOffset="83" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="translateX" android:duration="417"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="417" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
new file mode 100644
index 0000000..c4f8181
--- /dev/null
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
@@ -0,0 +1 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.75" android:translateY="15.75" android:pivotX="19.341" android:pivotY="24.25" android:scaleX="0.5" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="0" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="267" android:startOffset="233" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0" android:valueTo="0.5" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="683" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
index fc2c7d0..c05a8d5 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
@@ -1,247 +1 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!-- Copyright (C) 2021 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <aapt:attr name="android:drawable">
- <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
- android:viewportWidth="60">
- <group android:name="_R_G">
- <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
- <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
- android:translateY="38.75" android:scaleX="0" android:scaleY="0">
- <path android:name="_R_G_L_1_G_D_0_P_0"
- android:fillColor="@color/biometric_dialog_error"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
- </group>
- <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
- android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
- android:scaleX="1" android:scaleY="0">
- <path android:name="_R_G_L_1_G_D_1_P_0"
- android:fillColor="@color/biometric_dialog_error"
- android:fillAlpha="1" android:fillType="nonZero"
- android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
- </group>
- <path android:name="_R_G_L_1_G_D_2_P_0"
- android:strokeColor="@color/biometric_dialog_error"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2.5" android:strokeAlpha="1"
- android:trimPathStart="1" android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
- </group>
- <group android:name="_R_G_L_0_G" android:translateX="-10.325"
- android:translateY="-10.25">
- <path android:name="_R_G_L_0_G_D_0_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="1" android:trimPathOffset="0"
- android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
- <path android:name="_R_G_L_0_G_D_1_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="1" android:trimPathOffset="0"
- android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
- <path android:name="_R_G_L_0_G_D_2_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="1" android:trimPathOffset="0"
- android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
- <path android:name="_R_G_L_0_G_D_3_P_0"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round" android:strokeLineJoin="round"
- android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
- android:trimPathEnd="1" android:trimPathOffset="0"
- android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
- </group>
- </group>
- <group android:name="time_group"/>
- </vector>
- </aapt:attr>
- <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="100"
- android:startOffset="167" android:valueFrom="0"
- android:valueTo="1.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="100"
- android:startOffset="167" android:valueFrom="0"
- android:valueTo="1.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="67"
- android:startOffset="267" android:valueFrom="1.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="67"
- android:startOffset="267" android:valueFrom="1.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="167"
- android:startOffset="0" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="100"
- android:startOffset="167" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="100"
- android:startOffset="167" android:valueFrom="0"
- android:valueTo="1.1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleX" android:duration="67"
- android:startOffset="267" android:valueFrom="1" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="67"
- android:startOffset="267" android:valueFrom="1.1"
- android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="267"
- android:startOffset="0" android:valueFrom="1" android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_1_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_3_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="trimPathStart" android:duration="167"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="translateX" android:duration="350"
- android:startOffset="0" android:valueFrom="0" android:valueTo="1"
- android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
-</animated-vector>
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="0" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="0"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="233" android:startOffset="67" android:valueFrom="0" android:valueTo="2.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="350" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
new file mode 100644
index 0000000..1694429
--- /dev/null
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
@@ -0,0 +1 @@
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="20.75" android:translateY="15.75"><path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group><group android:name="_R_G_L_0_G" android:translateX="37.357" android:translateY="43.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="200" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.23,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 89690e8..58adb91 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -49,8 +49,8 @@
<ImageView
android:id="@+id/biometric_icon"
- android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
- android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@null"
android:scaleType="fitXY" />
diff --git a/packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
similarity index 83%
rename from packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml
rename to packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
index 7cf1789..05ca2a7 100644
--- a/packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
@@ -14,12 +14,13 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthBiometricFaceToFingerprintView
+<com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/contents"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/auth_biometric_contents"/>
-</com.android.systemui.biometrics.AuthBiometricFaceToFingerprintView>
\ No newline at end of file
+</com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_udfps_view.xml b/packages/SystemUI/res/layout/auth_biometric_view.xml
similarity index 83%
rename from packages/SystemUI/res/layout/auth_biometric_udfps_view.xml
rename to packages/SystemUI/res/layout/auth_biometric_view.xml
index 238288e..ee4da25 100644
--- a/packages/SystemUI/res/layout/auth_biometric_udfps_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_view.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthBiometricUdfpsView
+<com.android.systemui.biometrics.AuthBiometricView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contents"
android:layout_width="match_parent"
@@ -23,4 +23,4 @@
<include layout="@layout/auth_biometric_contents"/>
-</com.android.systemui.biometrics.AuthBiometricUdfpsView>
\ No newline at end of file
+</com.android.systemui.biometrics.AuthBiometricView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 65c17b9..5a7efca 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -876,7 +876,8 @@
<dimen name="remote_input_history_extra_height">60dp</dimen>
<!-- Biometric Dialog values -->
- <dimen name="biometric_dialog_biometric_icon_size">64dp</dimen>
+ <dimen name="biometric_dialog_face_icon_size">64dp</dimen>
+ <dimen name="biometric_dialog_fingerprint_icon_size">80dp</dimen>
<dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
<dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
<dimen name="biometric_dialog_corner_size">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6d336e6..9e1f57b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -322,6 +322,8 @@
<string name="biometric_dialog_face_icon_description_confirmed">Confirmed</string>
<!-- Message shown when a biometric is authenticated, waiting for the user to confirm authentication [CHAR LIMIT=40]-->
<string name="biometric_dialog_tap_confirm">Tap Confirm to complete</string>
+ <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]-->
+ <string name="biometric_dialog_tap_confirm_with_face">Unlocked by your face. Press to continue.</string>
<!-- Talkback string when a biometric is authenticated [CHAR LIMIT=NONE] -->
<string name="biometric_dialog_authenticated">Authenticated</string>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 885a177..aafbf7e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -172,6 +172,17 @@
}
@MainThread
+ void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY,
+ IRemoteMagnificationAnimationCallback callback) {
+ final WindowMagnificationController windowMagnificationController =
+ mMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null) {
+ windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
+ callback);
+ }
+ }
+
+ @MainThread
void disableWindowMagnification(int displayId,
@Nullable IRemoteMagnificationAnimationCallback callback) {
final WindowMagnificationController windowMagnificationController =
@@ -210,9 +221,9 @@
}
@Override
- public void onDrag(int displayId) {
+ public void onMove(int displayId) {
if (mWindowMagnificationConnectionImpl != null) {
- mWindowMagnificationConnectionImpl.onDrag(displayId);
+ mWindowMagnificationConnectionImpl.onMove(displayId);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index dc1e005..3b4114b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -156,6 +156,7 @@
}
mAnimationCallback = animationCallback;
setupEnableAnimationSpecs(scale, centerX, centerY);
+
if (mEndSpec.equals(mStartSpec)) {
if (mState == STATE_DISABLED) {
mController.enableWindowMagnificationInternal(scale, centerX, centerY,
@@ -178,6 +179,24 @@
}
}
+ void moveWindowMagnifierToPosition(float centerX, float centerY,
+ IRemoteMagnificationAnimationCallback callback) {
+ if (mState == STATE_ENABLED) {
+ // We set the animation duration to shortAnimTime which would be reset at the end.
+ mValueAnimator.setDuration(mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_shortAnimTime));
+ enableWindowMagnification(Float.NaN, centerX, centerY,
+ /* magnificationFrameOffsetRatioX */ Float.NaN,
+ /* magnificationFrameOffsetRatioY */ Float.NaN, callback);
+ } else if (mState == STATE_ENABLING) {
+ sendAnimationCallback(false);
+ mAnimationCallback = callback;
+ mValueAnimator.setDuration(mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_shortAnimTime));
+ setupEnableAnimationSpecs(Float.NaN, centerX, centerY);
+ }
+ }
+
private void setupEnableAnimationSpecs(float scale, float centerX, float centerY) {
if (mController == null) {
return;
@@ -193,9 +212,16 @@
R.integer.magnification_default_scale) : scale, centerX, centerY);
} else {
mStartSpec.set(currentScale, currentCenterX, currentCenterY);
- mEndSpec.set(Float.isNaN(scale) ? currentScale : scale,
- Float.isNaN(centerX) ? currentCenterX : centerX,
- Float.isNaN(centerY) ? currentCenterY : centerY);
+
+ final float endScale = (mState == STATE_ENABLING ? mEndSpec.mScale : currentScale);
+ final float endCenterX =
+ (mState == STATE_ENABLING ? mEndSpec.mCenterX : currentCenterX);
+ final float endCenterY =
+ (mState == STATE_ENABLING ? mEndSpec.mCenterY : currentCenterY);
+
+ mEndSpec.set(Float.isNaN(scale) ? endScale : scale,
+ Float.isNaN(centerX) ? endCenterX : centerX,
+ Float.isNaN(centerY) ? endCenterY : centerY);
}
if (DEBUG) {
Log.d(TAG, "SetupEnableAnimationSpecs : mStartSpec = " + mStartSpec + ", endSpec = "
@@ -269,6 +295,9 @@
setState(STATE_ENABLED);
}
sendAnimationCallback(true);
+ // We reset the duration to config_longAnimTime
+ mValueAnimator.setDuration(mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_longAnimTime));
}
@Override
@@ -313,10 +342,10 @@
mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
}
- private static ValueAnimator newValueAnimator(Resources resources) {
+ private static ValueAnimator newValueAnimator(Resources resource) {
final ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(
- resources.getInteger(com.android.internal.R.integer.config_longAnimTime));
+ resource.getInteger(com.android.internal.R.integer.config_longAnimTime));
valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
valueAnimator.setFloatValues(0.0f, 1.0f);
return valueAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
index 1d22633..aa684fa 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
@@ -77,6 +77,13 @@
}
@Override
+ public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+ IRemoteMagnificationAnimationCallback callback) {
+ mHandler.post(() -> mWindowMagnification.moveWindowMagnifierToPositionInternal(
+ displayId, positionX, positionY, callback));
+ }
+
+ @Override
public void showMagnificationButton(int displayId, int magnificationMode) {
mHandler.post(
() -> mModeSwitchesController.showButton(displayId, magnificationMode));
@@ -143,10 +150,10 @@
}
}
- void onDrag(int displayId) {
+ void onMove(int displayId) {
if (mConnectionCallback != null) {
try {
- mConnectionCallback.onDrag(displayId);
+ mConnectionCallback.onMove(displayId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to inform taking control by a user", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index de03993..50ca447 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -852,6 +852,7 @@
@Override
public void move(int xOffset, int yOffset) {
moveWindowMagnifier(xOffset, yOffset);
+ mWindowMagnifierCallback.onMove(mDisplayId);
}
/**
@@ -985,6 +986,14 @@
}
}
+ void moveWindowMagnifierToPosition(float positionX, float positionY,
+ IRemoteMagnificationAnimationCallback callback) {
+ if (mMirrorSurfaceView == null) {
+ return;
+ }
+ mAnimationController.moveWindowMagnifierToPosition(positionX, positionY, callback);
+ }
+
/**
* Gets the scale.
*
@@ -1037,8 +1046,7 @@
@Override
public boolean onDrag(float offsetX, float offsetY) {
- moveWindowMagnifier(offsetX, offsetY);
- mWindowMagnifierCallback.onDrag(mDisplayId);
+ move((int) offsetX, (int) offsetY);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index bdded10..c334ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -17,7 +17,6 @@
package com.android.systemui.accessibility;
import android.graphics.Rect;
-import android.view.ViewConfiguration;
/**
* A callback to inform {@link com.android.server.accessibility.AccessibilityManagerService} about
@@ -56,11 +55,9 @@
void onAccessibilityActionPerformed(int displayId);
/**
- * Called when the user is performing dragging gesture. It is started after the offset
- * between the down location and the move event location exceed
- * {@link ViewConfiguration#getScaledTouchSlop()}.
+ * Called when the user is performing a move action.
*
* @param displayId The logical display id.
*/
- void onDrag(int displayId);
+ void onMove(int displayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
new file mode 100644
index 0000000..55611f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.widget.ImageView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
+import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
+import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+
+private const val TAG = "AuthBiometricFaceIconController"
+
+/** Face only icon animator for BiometricPrompt. */
+class AuthBiometricFaceIconController(
+ context: Context,
+ iconView: ImageView
+) : AuthIconController(context, iconView) {
+
+ // false = dark to light, true = light to dark
+ private var lastPulseLightToDark = false
+
+ @BiometricState
+ private var state = 0
+
+ init {
+ val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+ iconView.layoutParams.width = size
+ iconView.layoutParams.height = size
+ showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
+ }
+
+ private fun startPulsing() {
+ lastPulseLightToDark = false
+ animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true)
+ }
+
+ private fun pulseInNextDirection() {
+ val iconRes = if (lastPulseLightToDark) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ animateIcon(iconRes, true /* repeat */)
+ lastPulseLightToDark = !lastPulseLightToDark
+ }
+
+ override fun handleAnimationEnd(drawable: Drawable) {
+ if (state == STATE_AUTHENTICATING || state == STATE_HELP) {
+ pulseInNextDirection()
+ }
+ }
+
+ override fun updateIcon(@BiometricState oldState: Int, @BiometricState newState: Int) {
+ val lastStateIsErrorIcon = (oldState == STATE_ERROR || oldState == STATE_HELP)
+ if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+ showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticating
+ )
+ } else if (newState == STATE_AUTHENTICATING) {
+ startPulsing()
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticating
+ )
+ } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
+ animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_confirmed
+ )
+ } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
+ animateIconOnce(R.drawable.face_dialog_error_to_idle)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_idle
+ )
+ } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
+ animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticated
+ )
+ } else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
+ animateIconOnce(R.drawable.face_dialog_dark_to_error)
+ } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
+ animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticated
+ )
+ } else if (newState == STATE_PENDING_CONFIRMATION) {
+ animateIconOnce(R.drawable.face_dialog_wink_from_dark)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_authenticated
+ )
+ } else if (newState == STATE_IDLE) {
+ showStaticDrawable(R.drawable.face_dialog_idle_static)
+ iconView.contentDescription = context.getString(
+ R.string.biometric_dialog_face_icon_description_idle
+ )
+ } else {
+ Log.w(TAG, "Unhandled state: $newState")
+ }
+ state = newState
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
deleted file mode 100644
index ae3e94b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-
-/**
- * Manages the layout of an auth dialog for devices with both a face sensor and a fingerprint
- * sensor. Face authentication is attempted first, followed by fingerprint if the initial attempt is
- * unsuccessful.
- */
-public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
- private static final String TAG = "BiometricPrompt/AuthBiometricFaceToFingerprintView";
-
- protected static class UdfpsIconController extends IconController {
- @BiometricState private int mIconState = STATE_IDLE;
-
- protected UdfpsIconController(
- @NonNull Context context, @NonNull ImageView iconView, @NonNull TextView textView) {
- super(context, iconView, textView);
- }
-
- void updateState(@BiometricState int newState) {
- updateState(mIconState, newState);
- }
-
- @Override
- protected void updateState(int lastState, int newState) {
- final boolean lastStateIsErrorIcon =
- lastState == STATE_ERROR || lastState == STATE_HELP;
-
- switch (newState) {
- case STATE_IDLE:
- case STATE_AUTHENTICATING_ANIMATING_IN:
- case STATE_AUTHENTICATING:
- case STATE_PENDING_CONFIRMATION:
- case STATE_AUTHENTICATED:
- if (lastStateIsErrorIcon) {
- animateOnce(R.drawable.fingerprint_dialog_error_to_fp);
- } else {
- showStaticDrawable(R.drawable.fingerprint_dialog_fp_to_error);
- }
- mIconView.setContentDescription(mContext.getString(
- R.string.accessibility_fingerprint_dialog_fingerprint_icon));
- break;
-
- case STATE_ERROR:
- case STATE_HELP:
- if (!lastStateIsErrorIcon) {
- animateOnce(R.drawable.fingerprint_dialog_fp_to_error);
- } else {
- showStaticDrawable(R.drawable.fingerprint_dialog_error_to_fp);
- }
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_try_again));
- break;
-
- default:
- Log.e(TAG, "Unknown biometric dialog state: " + newState);
- break;
- }
-
- mState = newState;
- mIconState = newState;
- }
- }
-
- @Modality private int mActiveSensorType = TYPE_FACE;
- @Nullable private ModalityListener mModalityListener;
- @Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps;
- @Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
- @Nullable @VisibleForTesting UdfpsIconController mUdfpsIconController;
-
-
- public AuthBiometricFaceToFingerprintView(Context context) {
- super(context);
- }
-
- public AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @VisibleForTesting
- AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs, Injector injector) {
- super(context, attrs, injector);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mUdfpsIconController = new UdfpsIconController(mContext, mIconView, mIndicatorView);
- }
-
- @Modality
- int getActiveSensorType() {
- return mActiveSensorType;
- }
-
- boolean isFingerprintUdfps() {
- return mFingerprintSensorProps.isAnyUdfpsType();
- }
-
- void setModalityListener(@NonNull ModalityListener listener) {
- mModalityListener = listener;
- }
-
- void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
- mFingerprintSensorProps = sensorProps;
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return mActiveSensorType == TYPE_FINGERPRINT ? 0
- : super.getDelayAfterAuthenticatedDurationMs();
- }
-
- @Override
- protected boolean supportsManualRetry() {
- return false;
- }
-
- @Override
- public void onAuthenticationFailed(
- @Modality int modality, @Nullable String failureReason) {
- super.onAuthenticationFailed(modality, checkErrorForFallback(failureReason));
- }
-
- @Override
- public void onError(int modality, String error) {
- super.onError(modality, checkErrorForFallback(error));
- }
-
- private String checkErrorForFallback(String message) {
- if (mActiveSensorType == TYPE_FACE) {
- Log.d(TAG, "Falling back to fingerprint: " + message);
-
- // switching from face -> fingerprint mode, suppress root error messages
- mCallback.onAction(Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR);
- return mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead);
- }
- return message;
- }
-
- @Override
- @BiometricState
- protected int getStateForAfterError() {
- if (mActiveSensorType == TYPE_FACE) {
- return STATE_AUTHENTICATING;
- }
-
- return super.getStateForAfterError();
- }
-
- @Override
- public void updateState(@BiometricState int newState) {
- if (mActiveSensorType == TYPE_FACE) {
- if (newState == STATE_HELP || newState == STATE_ERROR) {
- mActiveSensorType = TYPE_FINGERPRINT;
-
- setRequireConfirmation(false);
- mConfirmButton.setEnabled(false);
- mConfirmButton.setVisibility(View.GONE);
-
- if (mModalityListener != null) {
- mModalityListener.onModalitySwitched(TYPE_FACE, mActiveSensorType);
- }
-
- // Deactivate the face icon controller so it stops drawing to the view
- mFaceIconController.deactivate();
- // Then, activate this icon controller. We need to start in the "idle" state
- mUdfpsIconController.updateState(STATE_IDLE);
- }
- } else { // Fingerprint
- mUdfpsIconController.updateState(newState);
- }
-
- super.updateState(newState);
- }
-
- @Override
- @NonNull
- AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
- final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
- return isFingerprintUdfps()
- ? getUdfpsMeasureAdapter().onMeasureInternal(width, height, layoutParams)
- : layoutParams;
- }
-
- @NonNull
- private UdfpsDialogMeasureAdapter getUdfpsMeasureAdapter() {
- if (mUdfpsMeasureAdapter == null
- || mUdfpsMeasureAdapter.getSensorProps() != mFingerprintSensorProps) {
- mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, mFingerprintSensorProps);
- }
- return mUdfpsMeasureAdapter;
- }
-
- @Override
- public void onSaveState(@NonNull Bundle outState) {
- super.onSaveState(outState);
- outState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, mActiveSensorType);
- outState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS, mFingerprintSensorProps);
- }
-
- @Override
- public void restoreState(@Nullable Bundle savedState) {
- super.restoreState(savedState);
- if (savedState != null) {
- mActiveSensorType = savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FACE);
- mFingerprintSensorProps =
- savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
deleted file mode 100644
index 48f6431..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import android.content.Context;
-import android.graphics.drawable.Animatable2;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-
-public class AuthBiometricFaceView extends AuthBiometricView {
-
- private static final String TAG = "BiometricPrompt/AuthBiometricFaceView";
-
- // Delay before dismissing after being authenticated/confirmed.
- private static final int HIDE_DELAY_MS = 500;
-
- protected static class IconController extends Animatable2.AnimationCallback {
- protected Context mContext;
- protected ImageView mIconView;
- protected TextView mTextView;
- protected Handler mHandler;
- protected boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
- protected @BiometricState int mState;
- protected boolean mDeactivated;
-
- protected IconController(Context context, ImageView iconView, TextView textView) {
- mContext = context;
- mIconView = iconView;
- mTextView = textView;
- mHandler = new Handler(Looper.getMainLooper());
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
- }
-
- protected void animateOnce(int iconRes) {
- animateIcon(iconRes, false);
- }
-
- protected void showStaticDrawable(int iconRes) {
- mIconView.setImageDrawable(mContext.getDrawable(iconRes));
- }
-
- protected void animateIcon(int iconRes, boolean repeat) {
- Log.d(TAG, "animateIcon, state: " + mState + ", deactivated: " + mDeactivated);
- if (mDeactivated) {
- return;
- }
-
- final AnimatedVectorDrawable icon =
- (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
- mIconView.setImageDrawable(icon);
- icon.forceAnimationOnUI();
- if (repeat) {
- icon.registerAnimationCallback(this);
- }
- icon.start();
- }
-
- protected void startPulsing() {
- mLastPulseLightToDark = false;
- animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
- }
-
- protected void pulseInNextDirection() {
- int iconRes = mLastPulseLightToDark ? R.drawable.face_dialog_pulse_dark_to_light
- : R.drawable.face_dialog_pulse_light_to_dark;
- animateIcon(iconRes, true /* repeat */);
- mLastPulseLightToDark = !mLastPulseLightToDark;
- }
-
- @Override
- public void onAnimationEnd(Drawable drawable) {
- super.onAnimationEnd(drawable);
- Log.d(TAG, "onAnimationEnd, mState: " + mState + ", deactivated: " + mDeactivated);
- if (mDeactivated) {
- return;
- }
-
- if (mState == STATE_AUTHENTICATING || mState == STATE_HELP) {
- pulseInNextDirection();
- }
- }
-
- protected void deactivate() {
- mDeactivated = true;
- }
-
- protected void updateState(int lastState, int newState) {
- if (mDeactivated) {
- Log.w(TAG, "Ignoring updateState when deactivated: " + newState);
- return;
- }
-
- final boolean lastStateIsErrorIcon =
- lastState == STATE_ERROR || lastState == STATE_HELP;
-
- if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticating));
- } else if (newState == STATE_AUTHENTICATING) {
- startPulsing();
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticating));
- } else if (lastState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
- animateOnce(R.drawable.face_dialog_dark_to_checkmark);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_confirmed));
- } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
- animateOnce(R.drawable.face_dialog_error_to_idle);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_idle));
- } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
- animateOnce(R.drawable.face_dialog_dark_to_checkmark);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticated));
- } else if (newState == STATE_ERROR && lastState != STATE_ERROR) {
- animateOnce(R.drawable.face_dialog_dark_to_error);
- } else if (lastState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
- animateOnce(R.drawable.face_dialog_dark_to_checkmark);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticated));
- } else if (newState == STATE_PENDING_CONFIRMATION) {
- animateOnce(R.drawable.face_dialog_wink_from_dark);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_authenticated));
- } else if (newState == STATE_IDLE) {
- showStaticDrawable(R.drawable.face_dialog_idle_static);
- mIconView.setContentDescription(mContext.getString(
- R.string.biometric_dialog_face_icon_description_idle));
- } else {
- Log.w(TAG, "Unhandled state: " + newState);
- }
- mState = newState;
- }
- }
-
- @Nullable @VisibleForTesting IconController mFaceIconController;
- @NonNull private final OnAttachStateChangeListener mOnAttachStateChangeListener =
- new OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
-
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mFaceIconController.deactivate();
- }
- };
-
- public AuthBiometricFaceView(Context context) {
- this(context, null);
- }
-
- public AuthBiometricFaceView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @VisibleForTesting
- AuthBiometricFaceView(Context context, AttributeSet attrs, Injector injector) {
- super(context, attrs, injector);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mFaceIconController = new IconController(mContext, mIconView, mIndicatorView);
-
- addOnAttachStateChangeListener(mOnAttachStateChangeListener);
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return HIDE_DELAY_MS;
- }
-
- @Override
- protected int getStateForAfterError() {
- return STATE_IDLE;
- }
-
- @Override
- protected void handleResetAfterError() {
- resetErrorView();
- }
-
- @Override
- protected void handleResetAfterHelp() {
- resetErrorView();
- }
-
- @Override
- protected boolean supportsSmallDialog() {
- return true;
- }
-
- @Override
- protected boolean supportsManualRetry() {
- return true;
- }
-
- @Override
- public void updateState(@BiometricState int newState) {
- mFaceIconController.updateState(mState, newState);
-
- if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
- (newState == STATE_AUTHENTICATING && getSize() == AuthDialog.SIZE_MEDIUM)) {
- resetErrorView();
- }
-
- // Do this last since the state variable gets updated.
- super.updateState(newState);
- }
-
- @Override
- public void onAuthenticationFailed(@Modality int modality, @Nullable String failureReason) {
- if (getSize() == AuthDialog.SIZE_MEDIUM) {
- if (supportsManualRetry()) {
- mTryAgainButton.setVisibility(View.VISIBLE);
- mConfirmButton.setVisibility(View.GONE);
- }
- }
-
- // Do this last since we want to know if the button is being animated (in the case of
- // small -> medium dialog)
- super.onAuthenticationFailed(modality, failureReason);
- }
-
- private void resetErrorView() {
- mIndicatorView.setTextColor(mTextColorHint);
- mIndicatorView.setVisibility(View.INVISIBLE);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
new file mode 100644
index 0000000..be89d10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 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.biometrics
+
+import android.content.Context
+import android.hardware.biometrics.BiometricAuthenticator.Modality
+import android.util.AttributeSet
+
+/** Face only view for BiometricPrompt. */
+class AuthBiometricFaceView(
+ context: Context,
+ attrs: AttributeSet? = null
+) : AuthBiometricView(context, attrs) {
+
+ override fun getDelayAfterAuthenticatedDurationMs() = HIDE_DELAY_MS
+
+ override fun getStateForAfterError() = STATE_IDLE
+
+ override fun handleResetAfterError() = resetErrorView()
+
+ override fun handleResetAfterHelp() = resetErrorView()
+
+ override fun supportsSmallDialog() = true
+
+ override fun supportsManualRetry() = true
+
+ override fun supportsRequireConfirmation() = true
+
+ override fun createIconController(): AuthIconController =
+ AuthBiometricFaceIconController(mContext, mIconView)
+
+ override fun updateState(@BiometricState newState: Int) {
+ if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
+ newState == STATE_AUTHENTICATING && size == AuthDialog.SIZE_MEDIUM) {
+ resetErrorView()
+ }
+
+ // Do this last since the state variable gets updated.
+ super.updateState(newState)
+ }
+
+ override fun onAuthenticationFailed(
+ @Modality modality: Int,
+ failureReason: String?
+ ) {
+ if (size == AuthDialog.SIZE_MEDIUM) {
+ if (supportsManualRetry()) {
+ mTryAgainButton.visibility = VISIBLE
+ mConfirmButton.visibility = GONE
+ }
+ }
+
+ // Do this last since we want to know if the button is being animated (in the case of
+ // small -> medium dialog)
+ super.onAuthenticationFailed(modality, failureReason)
+ }
+
+ private fun resetErrorView() {
+ mIndicatorView.setTextColor(mTextColorHint)
+ mIndicatorView.visibility = INVISIBLE
+ }
+
+ companion object {
+ /** Delay before dismissing after being authenticated/confirmed. */
+ const val HIDE_DELAY_MS = 500
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
new file mode 100644
index 0000000..3e4e573
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.widget.ImageView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
+import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+
+/** Face/Fingerprint combined icon animator for BiometricPrompt. */
+class AuthBiometricFingerprintAndFaceIconController(
+ context: Context,
+ iconView: ImageView
+) : AuthBiometricFingerprintIconController(context, iconView) {
+
+ override val actsAsConfirmButton: Boolean = true
+
+ override fun shouldAnimateForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Boolean = when (newState) {
+ STATE_PENDING_CONFIRMATION -> true
+ STATE_AUTHENTICATED -> false
+ else -> super.shouldAnimateForTransition(oldState, newState)
+ }
+
+ override fun getAnimationForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Drawable? = when (newState) {
+ STATE_PENDING_CONFIRMATION -> {
+ if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+ context.getDrawable(R.drawable.fingerprint_dialog_error_to_unlock)
+ } else {
+ context.getDrawable(R.drawable.fingerprint_dialog_fp_to_unlock)
+ }
+ }
+ STATE_AUTHENTICATED -> null
+ else -> super.getAnimationForTransition(oldState, newState)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
new file mode 100644
index 0000000..7371442
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.content.Context
+import android.hardware.biometrics.BiometricAuthenticator.Modality
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
+import android.util.AttributeSet
+import com.android.systemui.R
+
+/** Face/Fingerprint combined view for BiometricPrompt. */
+class AuthBiometricFingerprintAndFaceView(
+ context: Context,
+ attrs: AttributeSet?
+) : AuthBiometricFingerprintView(context, attrs) {
+
+ constructor (context: Context) : this(context, null)
+
+ override fun getConfirmationPrompt() = R.string.biometric_dialog_tap_confirm_with_face
+
+ override fun forceRequireConfirmation(@Modality modality: Int) = modality == TYPE_FACE
+
+ override fun ignoreUnsuccessfulEventsFrom(@Modality modality: Int) = modality == TYPE_FACE
+
+ override fun onPointerDown(failedModalities: Set<Int>) = failedModalities.contains(TYPE_FACE)
+
+ override fun createIconController(): AuthIconController =
+ AuthBiometricFingerprintAndFaceIconController(mContext, mIconView)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
new file mode 100644
index 0000000..cd16379
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.content.Context
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.widget.ImageView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
+import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
+import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+
+/** Fingerprint only icon animator for BiometricPrompt. */
+open class AuthBiometricFingerprintIconController(
+ context: Context,
+ iconView: ImageView
+) : AuthIconController(context, iconView) {
+
+ init {
+ val size = context.resources.getDimensionPixelSize(
+ R.dimen.biometric_dialog_fingerprint_icon_size
+ )
+ iconView.layoutParams.width = size
+ iconView.layoutParams.height = size
+ }
+
+ override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
+ val icon = getAnimationForTransition(lastState, newState) ?: return
+
+ iconView.setImageDrawable(icon)
+
+ val iconContentDescription = getIconContentDescription(newState)
+ if (iconContentDescription != null) {
+ iconView.contentDescription = iconContentDescription
+ }
+
+ (icon as? AnimatedVectorDrawable)?.apply {
+ reset()
+ if (shouldAnimateForTransition(lastState, newState)) {
+ forceAnimationOnUI()
+ start()
+ }
+ }
+ }
+
+ private fun getIconContentDescription(@BiometricState newState: Int): CharSequence? {
+ val id = when (newState) {
+ STATE_IDLE,
+ STATE_AUTHENTICATING_ANIMATING_IN,
+ STATE_AUTHENTICATING,
+ STATE_PENDING_CONFIRMATION,
+ STATE_AUTHENTICATED -> R.string.accessibility_fingerprint_dialog_fingerprint_icon
+ STATE_ERROR,
+ STATE_HELP -> R.string.biometric_dialog_try_again
+ else -> null
+ }
+ return if (id != null) context.getString(id) else null
+ }
+
+ protected open fun shouldAnimateForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ) = when (newState) {
+ STATE_HELP,
+ STATE_ERROR -> true
+ STATE_AUTHENTICATING_ANIMATING_IN,
+ STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
+ STATE_AUTHENTICATED -> false
+ else -> false
+ }
+
+ protected open fun getAnimationForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Drawable? {
+ val id = when (newState) {
+ STATE_HELP,
+ STATE_ERROR -> R.drawable.fingerprint_dialog_fp_to_error
+ STATE_AUTHENTICATING_ANIMATING_IN,
+ STATE_AUTHENTICATING -> {
+ if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+ R.drawable.fingerprint_dialog_error_to_fp
+ } else {
+ R.drawable.fingerprint_dialog_fp_to_error
+ }
+ }
+ STATE_AUTHENTICATED -> R.drawable.fingerprint_dialog_fp_to_error
+ else -> return null
+ }
+ return if (id != null) context.getDrawable(id) else null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
deleted file mode 100644
index ee602bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-
-import android.content.Context;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-
-public class AuthBiometricFingerprintView extends AuthBiometricView {
-
- private static final String TAG = "BiometricPrompt/AuthBiometricFingerprintView";
-
- public AuthBiometricFingerprintView(Context context) {
- this(context, null);
- }
-
- public AuthBiometricFingerprintView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return 0;
- }
-
- @Override
- protected int getStateForAfterError() {
- return STATE_AUTHENTICATING;
- }
-
- @Override
- protected void handleResetAfterError() {
- showTouchSensorString();
- }
-
- @Override
- protected void handleResetAfterHelp() {
- showTouchSensorString();
- }
-
- @Override
- protected boolean supportsSmallDialog() {
- return false;
- }
-
- @Override
- public void updateState(@BiometricState int newState) {
- updateIcon(mState, newState);
-
- // Do this last since the state variable gets updated.
- super.updateState(newState);
- }
-
- @Override
- void onAttachedToWindowInternal() {
- super.onAttachedToWindowInternal();
- showTouchSensorString();
- }
-
- private void showTouchSensorString() {
- mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor);
- mIndicatorView.setTextColor(mTextColorHint);
- }
-
- private void updateIcon(int lastState, int newState) {
- final Drawable icon = getAnimationForTransition(lastState, newState);
- if (icon == null) {
- Log.e(TAG, "Animation not found, " + lastState + " -> " + newState);
- return;
- }
-
- final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
- ? (AnimatedVectorDrawable) icon
- : null;
-
- mIconView.setImageDrawable(icon);
-
- final CharSequence iconContentDescription = getIconContentDescription(newState);
- if (iconContentDescription != null) {
- mIconView.setContentDescription(iconContentDescription);
- }
-
- if (animation != null && shouldAnimateForTransition(lastState, newState)) {
- animation.forceAnimationOnUI();
- animation.start();
- }
- }
-
- @Nullable
- private CharSequence getIconContentDescription(int newState) {
- switch (newState) {
- case STATE_IDLE:
- case STATE_AUTHENTICATING_ANIMATING_IN:
- case STATE_AUTHENTICATING:
- case STATE_PENDING_CONFIRMATION:
- case STATE_AUTHENTICATED:
- return mContext.getString(
- R.string.accessibility_fingerprint_dialog_fingerprint_icon);
-
- case STATE_ERROR:
- case STATE_HELP:
- return mContext.getString(R.string.biometric_dialog_try_again);
-
- default:
- return null;
- }
- }
-
- private boolean shouldAnimateForTransition(int oldState, int newState) {
- switch (newState) {
- case STATE_HELP:
- case STATE_ERROR:
- return true;
- case STATE_AUTHENTICATING_ANIMATING_IN:
- case STATE_AUTHENTICATING:
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- return true;
- } else {
- return false;
- }
- case STATE_AUTHENTICATED:
- return false;
- default:
- return false;
- }
- }
-
- private Drawable getAnimationForTransition(int oldState, int newState) {
- int iconRes;
-
- switch (newState) {
- case STATE_HELP:
- case STATE_ERROR:
- iconRes = R.drawable.fingerprint_dialog_fp_to_error;
- break;
- case STATE_AUTHENTICATING_ANIMATING_IN:
- case STATE_AUTHENTICATING:
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- iconRes = R.drawable.fingerprint_dialog_error_to_fp;
- } else {
- iconRes = R.drawable.fingerprint_dialog_fp_to_error;
- }
- break;
- case STATE_AUTHENTICATED:
- iconRes = R.drawable.fingerprint_dialog_fp_to_error;
- break;
- default:
- return null;
- }
-
- return mContext.getDrawable(iconRes);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
new file mode 100644
index 0000000..368bc3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 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.biometrics
+
+import android.content.Context
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.util.AttributeSet
+import android.util.Log
+import android.widget.FrameLayout
+import android.widget.TextView
+import com.android.systemui.R
+
+private const val TAG = "AuthBiometricFingerprintView"
+
+/** Fingerprint only view for BiometricPrompt. */
+open class AuthBiometricFingerprintView(
+ context: Context,
+ attrs: AttributeSet? = null
+) : AuthBiometricView(context, attrs) {
+ /** If this view is for a UDFPS sensor. */
+ var isUdfps = false
+ private set
+
+ private var udfpsAdapter: UdfpsDialogMeasureAdapter? = null
+
+ /** Set the [sensorProps] of this sensor so the view can be customized prior to layout. */
+ fun setSensorProperties(sensorProps: FingerprintSensorPropertiesInternal) {
+ isUdfps = sensorProps.isAnyUdfpsType
+ udfpsAdapter = if (isUdfps) UdfpsDialogMeasureAdapter(this, sensorProps) else null
+ }
+
+ override fun onMeasureInternal(width: Int, height: Int): AuthDialog.LayoutParams {
+ val layoutParams = super.onMeasureInternal(width, height)
+ return udfpsAdapter?.onMeasureInternal(width, height, layoutParams) ?: layoutParams
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+
+ val adapter = udfpsAdapter
+ if (adapter != null) {
+ // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen
+ // for devices where the UDFPS sensor is too low.
+ // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap
+ // the button bar area.
+ val bottomSpacerHeight = adapter.bottomSpacerHeight
+ Log.w(TAG, "bottomSpacerHeight: $bottomSpacerHeight")
+ if (bottomSpacerHeight < 0) {
+ val iconFrame = findViewById<FrameLayout>(R.id.biometric_icon_frame)!!
+ iconFrame.translationY = -bottomSpacerHeight.toFloat()
+ val indicator = findViewById<TextView>(R.id.indicator)!!
+ indicator.translationY = -bottomSpacerHeight.toFloat()
+ }
+ }
+ }
+
+ override fun getDelayAfterAuthenticatedDurationMs() = 0
+
+ override fun getStateForAfterError() = STATE_AUTHENTICATING
+
+ override fun handleResetAfterError() = showTouchSensorString()
+
+ override fun handleResetAfterHelp() = showTouchSensorString()
+
+ override fun supportsSmallDialog() = false
+
+ override fun createIconController(): AuthIconController =
+ AuthBiometricFingerprintIconController(mContext, mIconView)
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ showTouchSensorString()
+ }
+
+ private fun showTouchSensorString() {
+ mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor)
+ mIndicatorView.setTextColor(mTextColorHint)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
new file mode 100644
index 0000000..ce5e600
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.annotation.DrawableRes
+import android.content.Context
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.widget.ImageView
+import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+
+private const val TAG = "AuthIconController"
+
+/** Controller for animating the BiometricPrompt icon/affordance. */
+abstract class AuthIconController(
+ protected val context: Context,
+ protected val iconView: ImageView
+) : Animatable2.AnimationCallback() {
+
+ /** If this controller should ignore events and pause. */
+ var deactivated: Boolean = false
+
+ /** If the icon view should be treated as an alternate "confirm" button. */
+ open val actsAsConfirmButton: Boolean = false
+
+ final override fun onAnimationStart(drawable: Drawable) {
+ super.onAnimationStart(drawable)
+ }
+
+ final override fun onAnimationEnd(drawable: Drawable) {
+ super.onAnimationEnd(drawable)
+
+ if (!deactivated) {
+ handleAnimationEnd(drawable)
+ }
+ }
+
+ /** Set the icon to a static image. */
+ protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
+ iconView.setImageDrawable(context.getDrawable(iconRes))
+ }
+
+ /** Animate a resource. */
+ protected fun animateIconOnce(@DrawableRes iconRes: Int) {
+ animateIcon(iconRes, false)
+ }
+
+ /** Animate a resource. */
+ protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
+ if (!deactivated) {
+ val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
+ iconView.setImageDrawable(icon)
+ icon.forceAnimationOnUI()
+ if (repeat) {
+ icon.registerAnimationCallback(this)
+ }
+ icon.start()
+ }
+ }
+
+ /** Update the icon to reflect the [newState]. */
+ fun updateState(@BiometricState lastState: Int, @BiometricState newState: Int) {
+ if (deactivated) {
+ Log.w(TAG, "Ignoring updateState when deactivated: $newState")
+ } else {
+ updateIcon(lastState, newState)
+ }
+ }
+
+ /** If the icon should act as a "retry" button in the [currentState]. */
+ fun iconTapSendsRetryWhen(@BiometricState currentState: Int): Boolean = false
+
+ /** Call during [updateState] if the controller is not [deactivated]. */
+ abstract fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int)
+
+ /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
+ open fun handleAnimationEnd(drawable: Drawable) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java
deleted file mode 100644
index d80d9cc..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java
+++ /dev/null
@@ -1,80 +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.systemui.biometrics;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-/**
- * Manages the layout for under-display fingerprint sensors (UDFPS). Ensures that UI elements
- * do not overlap with
- */
-public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
- private static final String TAG = "AuthBiometricUdfpsView";
-
- @Nullable private UdfpsDialogMeasureAdapter mMeasureAdapter;
-
- public AuthBiometricUdfpsView(Context context) {
- this(context, null /* attrs */);
- }
-
- public AuthBiometricUdfpsView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- void setSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
- if (mMeasureAdapter == null || mMeasureAdapter.getSensorProps() != sensorProps) {
- mMeasureAdapter = new UdfpsDialogMeasureAdapter(this, sensorProps);
- }
- }
-
- @Override
- @NonNull
- AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
- final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
- return mMeasureAdapter != null
- ? mMeasureAdapter.onMeasureInternal(width, height, layoutParams)
- : layoutParams;
- }
-
- @Override
- void onLayoutInternal() {
- super.onLayoutInternal();
-
- // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen
- // for devices where the UDFPS sensor is too low.
- // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap
- // the button bar area.
- final int bottomSpacerHeight = mMeasureAdapter.getBottomSpacerHeight();
- Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight);
- if (bottomSpacerHeight < 0) {
- FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame);
- iconFrame.setTranslationY(-bottomSpacerHeight);
-
- TextView indicator = findViewById(R.id.indicator);
- indicator.setTranslationY(-bottomSpacerHeight);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 1496f17..76d4aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -25,6 +25,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricPrompt;
@@ -44,19 +45,21 @@
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/**
- * Contains the Biometric views (title, subtitle, icon, buttons, etc) and its controllers.
+ * Contains the Biometric views (title, subtitle, icon, buttons, etc.) and its controllers.
*/
-public abstract class AuthBiometricView extends LinearLayout {
+public class AuthBiometricView extends LinearLayout {
- private static final String TAG = "BiometricPrompt/AuthBiometricView";
+ private static final String TAG = "AuthBiometricView";
/**
* Authentication hardware idle.
@@ -102,13 +105,6 @@
int ACTION_BUTTON_TRY_AGAIN = 4;
int ACTION_ERROR = 5;
int ACTION_USE_DEVICE_CREDENTIAL = 6;
- /**
- * Notify the receiver to start the fingerprint sensor.
- *
- * This is only applicable to multi-sensor devices that need to delay fingerprint auth
- * (i.e face -> fingerprint).
- */
- int ACTION_START_DELAYED_FINGERPRINT_SENSOR = 7;
/**
* When an action has occurred. The caller will only invoke this when the callback should
@@ -118,66 +114,9 @@
void onAction(int action);
}
- @VisibleForTesting
- static class Injector {
- AuthBiometricView mBiometricView;
-
- public Button getNegativeButton() {
- return mBiometricView.findViewById(R.id.button_negative);
- }
-
- public Button getCancelButton() {
- return mBiometricView.findViewById(R.id.button_cancel);
- }
-
- public Button getUseCredentialButton() {
- return mBiometricView.findViewById(R.id.button_use_credential);
- }
-
- public Button getConfirmButton() {
- return mBiometricView.findViewById(R.id.button_confirm);
- }
-
- public Button getTryAgainButton() {
- return mBiometricView.findViewById(R.id.button_try_again);
- }
-
- public TextView getTitleView() {
- return mBiometricView.findViewById(R.id.title);
- }
-
- public TextView getSubtitleView() {
- return mBiometricView.findViewById(R.id.subtitle);
- }
-
- public TextView getDescriptionView() {
- return mBiometricView.findViewById(R.id.description);
- }
-
- public TextView getIndicatorView() {
- return mBiometricView.findViewById(R.id.indicator);
- }
-
- public ImageView getIconView() {
- return mBiometricView.findViewById(R.id.biometric_icon);
- }
-
- public View getIconHolderView() {
- return mBiometricView.findViewById(R.id.biometric_icon_frame);
- }
-
- public int getDelayAfterError() {
- return BiometricPrompt.HIDE_DIALOG_DELAY;
- }
-
- public int getMediumToLargeAnimationDurationMs() {
- return AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS;
- }
- }
-
- private final Injector mInjector;
protected final Handler mHandler;
private final AccessibilityManager mAccessibilityManager;
+ private final LockPatternUtils mLockPatternUtils;
protected final int mTextColorError;
protected final int mTextColorHint;
@@ -195,6 +134,11 @@
protected ImageView mIconView;
protected TextView mIndicatorView;
+ @VisibleForTesting @NonNull AuthIconController mIconController;
+ @VisibleForTesting int mAnimationDurationShort = AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS;
+ @VisibleForTesting int mAnimationDurationLong = AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS;
+ @VisibleForTesting int mAnimationDurationHideDialog = BiometricPrompt.HIDE_DIALOG_DELAY;
+
// Negative button position, exclusively for the app-specified behavior
@VisibleForTesting Button mNegativeButton;
// Negative button position, exclusively for cancelling auth after passive auth success
@@ -217,30 +161,7 @@
protected boolean mDialogSizeAnimating;
protected Bundle mSavedState;
- /**
- * Delay after authentication is confirmed, before the dialog should be animated away.
- */
- protected abstract int getDelayAfterAuthenticatedDurationMs();
- /**
- * State that the dialog/icon should be in after showing a help message.
- */
- protected abstract int getStateForAfterError();
- /**
- * Invoked when the error message is being cleared.
- */
- protected abstract void handleResetAfterError();
- /**
- * Invoked when the help message is being cleared.
- */
- protected abstract void handleResetAfterHelp();
-
- /**
- * @return true if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}
- */
- protected abstract boolean supportsSmallDialog();
-
private final Runnable mResetErrorRunnable;
-
private final Runnable mResetHelpRunnable;
private final OnClickListener mBackgroundClickListener = (view) -> {
@@ -262,11 +183,6 @@
}
public AuthBiometricView(Context context, AttributeSet attrs) {
- this(context, attrs, new Injector());
- }
-
- @VisibleForTesting
- AuthBiometricView(Context context, AttributeSet attrs, Injector injector) {
super(context, attrs);
mHandler = new Handler(Looper.getMainLooper());
mTextColorError = getResources().getColor(
@@ -274,10 +190,8 @@
mTextColorHint = getResources().getColor(
R.color.biometric_dialog_gray, context.getTheme());
- mInjector = injector;
- mInjector.mBiometricView = this;
-
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ mLockPatternUtils = new LockPatternUtils(context);
mResetErrorRunnable = () -> {
updateState(getStateForAfterError());
@@ -292,36 +206,91 @@
};
}
- public void setPanelController(AuthPanelController panelController) {
+ /** Delay after authentication is confirmed, before the dialog should be animated away. */
+ protected int getDelayAfterAuthenticatedDurationMs() {
+ return 0;
+ }
+
+ /** State that the dialog/icon should be in after showing a help message. */
+ protected int getStateForAfterError() {
+ return STATE_IDLE;
+ }
+
+ /** Invoked when the error message is being cleared. */
+ protected void handleResetAfterError() {}
+
+ /** Invoked when the help message is being cleared. */
+ protected void handleResetAfterHelp() {}
+
+ /** True if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}. */
+ protected boolean supportsSmallDialog() {
+ return false;
+ }
+
+ /** The string to show when the user must tap to confirm via the button or icon. */
+ @StringRes
+ protected int getConfirmationPrompt() {
+ return R.string.biometric_dialog_tap_confirm;
+ }
+
+ /** True if require confirmation will be honored when set via the API. */
+ protected boolean supportsRequireConfirmation() {
+ return false;
+ }
+
+ /** True if confirmation will be required even if it was not supported/requested. */
+ protected boolean forceRequireConfirmation(@Modality int modality) {
+ return false;
+ }
+
+ /** Ignore all events from this (secondary) modality except successful authentication. */
+ protected boolean ignoreUnsuccessfulEventsFrom(@Modality int modality) {
+ return false;
+ }
+
+ /**
+ * Create the controller for managing the icons transitions during the prompt.
+ *
+ * Subclass should override.
+ */
+ @NonNull
+ protected AuthIconController createIconController() {
+ return new AuthIconController(mContext, mIconView) {
+ @Override
+ public void updateIcon(int lastState, int newState) {}
+ };
+ }
+
+ void setPanelController(AuthPanelController panelController) {
mPanelController = panelController;
}
- public void setPromptInfo(PromptInfo promptInfo) {
+ void setPromptInfo(PromptInfo promptInfo) {
mPromptInfo = promptInfo;
}
- public void setCallback(Callback callback) {
+ void setCallback(Callback callback) {
mCallback = callback;
}
- public void setBackgroundView(View backgroundView) {
+ void setBackgroundView(View backgroundView) {
backgroundView.setOnClickListener(mBackgroundClickListener);
}
- public void setUserId(int userId) {
+ void setUserId(int userId) {
mUserId = userId;
}
- public void setEffectiveUserId(int effectiveUserId) {
+ void setEffectiveUserId(int effectiveUserId) {
mEffectiveUserId = effectiveUserId;
}
- public void setRequireConfirmation(boolean requireConfirmation) {
- mRequireConfirmation = requireConfirmation;
+ void setRequireConfirmation(boolean requireConfirmation) {
+ mRequireConfirmation = requireConfirmation && supportsRequireConfirmation();
}
@VisibleForTesting
- void updateSize(@AuthDialog.DialogSize int newSize) {
+ final void updateSize(@AuthDialog.DialogSize int newSize) {
Log.v(TAG, "Current size: " + mSize + " New size: " + newSize);
if (newSize == AuthDialog.SIZE_SMALL) {
mTitleView.setVisibility(View.GONE);
@@ -376,7 +345,7 @@
// Choreograph together
final AnimatorSet as = new AnimatorSet();
- as.setDuration(AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS);
+ as.setDuration(mAnimationDurationShort);
as.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -429,7 +398,7 @@
// Translate at full duration
final ValueAnimator translationAnimator = ValueAnimator.ofFloat(
biometricView.getY(), biometricView.getY() - translationY);
- translationAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs());
+ translationAnimator.setDuration(mAnimationDurationLong);
translationAnimator.addUpdateListener((animation) -> {
final float translation = (float) animation.getAnimatedValue();
biometricView.setTranslationY(translation);
@@ -438,7 +407,7 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- if (biometricView.getParent() != null) {
+ if (biometricView.getParent() instanceof ViewGroup) {
((ViewGroup) biometricView.getParent()).removeView(biometricView);
}
mSize = newSize;
@@ -447,7 +416,7 @@
// Opacity to 0 in half duration
final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(1, 0);
- opacityAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs() / 2);
+ opacityAnimator.setDuration(mAnimationDurationLong / 2);
opacityAnimator.addUpdateListener((animation) -> {
final float opacity = (float) animation.getAnimatedValue();
biometricView.setAlpha(opacity);
@@ -457,7 +426,7 @@
mPanelController.updateForContentDimensions(
mPanelController.getContainerWidth(),
mPanelController.getContainerHeight(),
- mInjector.getMediumToLargeAnimationDurationMs());
+ mAnimationDurationLong);
// Start the animations together
AnimatorSet as = new AnimatorSet();
@@ -466,7 +435,7 @@
animators.add(opacityAnimator);
as.playTogether(animators);
- as.setDuration(mInjector.getMediumToLargeAnimationDurationMs() * 2 / 3);
+ as.setDuration(mAnimationDurationLong * 2 / 3);
as.start();
} else {
Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize);
@@ -481,6 +450,8 @@
public void updateState(@BiometricState int newState) {
Log.v(TAG, "newState: " + newState);
+ mIconController.updateState(mState, newState);
+
switch (newState) {
case STATE_AUTHENTICATING_ANIMATING_IN:
case STATE_AUTHENTICATING:
@@ -510,10 +481,11 @@
mNegativeButton.setVisibility(View.GONE);
mCancelButton.setVisibility(View.VISIBLE);
mUseCredentialButton.setVisibility(View.GONE);
- mConfirmButton.setEnabled(true);
- mConfirmButton.setVisibility(View.VISIBLE);
+ // forced confirmations (multi-sensor) use the icon view as the confirm button
+ mConfirmButton.setEnabled(mRequireConfirmation);
+ mConfirmButton.setVisibility(mRequireConfirmation ? View.VISIBLE : View.GONE);
mIndicatorView.setTextColor(mTextColorHint);
- mIndicatorView.setText(R.string.biometric_dialog_tap_confirm);
+ mIndicatorView.setText(getConfirmationPrompt());
mIndicatorView.setVisibility(View.VISIBLE);
break;
@@ -536,9 +508,9 @@
updateState(STATE_AUTHENTICATING);
}
- public void onAuthenticationSucceeded() {
+ public void onAuthenticationSucceeded(@Modality int modality) {
removePendingAnimations();
- if (mRequireConfirmation) {
+ if (mRequireConfirmation || forceRequireConfirmation(modality)) {
updateState(STATE_PENDING_CONFIRMATION);
} else {
updateState(STATE_AUTHENTICATED);
@@ -553,6 +525,10 @@
*/
public void onAuthenticationFailed(
@Modality int modality, @Nullable String failureReason) {
+ if (ignoreUnsuccessfulEventsFrom(modality)) {
+ return;
+ }
+
showTemporaryMessage(failureReason, mResetErrorRunnable);
updateState(STATE_ERROR);
}
@@ -564,12 +540,27 @@
* @param error message
*/
public void onError(@Modality int modality, String error) {
+ if (ignoreUnsuccessfulEventsFrom(modality)) {
+ return;
+ }
+
showTemporaryMessage(error, mResetErrorRunnable);
updateState(STATE_ERROR);
- mHandler.postDelayed(() -> {
- mCallback.onAction(Callback.ACTION_ERROR);
- }, mInjector.getDelayAfterError());
+ mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_ERROR),
+ mAnimationDurationHideDialog);
+ }
+
+ /**
+ * Fingerprint pointer down event. This does nothing by default and will not be called if the
+ * device does not have an appropriate sensor (UDFPS), but it may be used as an alternative
+ * to the "retry" button when fingerprint is used with other modalities.
+ *
+ * @param failedModalities the set of modalities that have failed
+ * @return true if a retry was initiated as a result of this event
+ */
+ public boolean onPointerDown(Set<Integer> failedModalities) {
+ return false;
}
/**
@@ -579,6 +570,9 @@
* @param help message
*/
public void onHelp(@Modality int modality, String help) {
+ if (ignoreUnsuccessfulEventsFrom(modality)) {
+ return;
+ }
if (mSize != AuthDialog.SIZE_MEDIUM) {
Log.w(TAG, "Help received in size: " + mSize);
return;
@@ -639,7 +633,7 @@
// select to enable marquee unless a screen reader is enabled
mIndicatorView.setSelected(!mAccessibilityManager.isEnabled()
|| !mAccessibilityManager.isTouchExplorationEnabled());
- mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError());
+ mHandler.postDelayed(resetMessageRunnable, mAnimationDurationHideDialog);
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
}
@@ -647,29 +641,22 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- onFinishInflateInternal();
- }
- /**
- * After inflation, but before things like restoreState, onAttachedToWindow, etc.
- */
- @VisibleForTesting
- void onFinishInflateInternal() {
- mTitleView = mInjector.getTitleView();
- mSubtitleView = mInjector.getSubtitleView();
- mDescriptionView = mInjector.getDescriptionView();
- mIconView = mInjector.getIconView();
- mIconHolderView = mInjector.getIconHolderView();
- mIndicatorView = mInjector.getIndicatorView();
+ mTitleView = findViewById(R.id.title);
+ mSubtitleView = findViewById(R.id.subtitle);
+ mDescriptionView = findViewById(R.id.description);
+ mIconView = findViewById(R.id.biometric_icon);
+ mIconHolderView = findViewById(R.id.biometric_icon_frame);
+ mIndicatorView = findViewById(R.id.indicator);
// Negative-side (left) buttons
- mNegativeButton = mInjector.getNegativeButton();
- mCancelButton = mInjector.getCancelButton();
- mUseCredentialButton = mInjector.getUseCredentialButton();
+ mNegativeButton = findViewById(R.id.button_negative);
+ mCancelButton = findViewById(R.id.button_cancel);
+ mUseCredentialButton = findViewById(R.id.button_use_credential);
// Positive-side (right) buttons
- mConfirmButton = mInjector.getConfirmButton();
- mTryAgainButton = mInjector.getTryAgainButton();
+ mConfirmButton = findViewById(R.id.button_confirm);
+ mTryAgainButton = findViewById(R.id.button_try_again);
mNegativeButton.setOnClickListener((view) -> {
mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
@@ -693,6 +680,15 @@
mTryAgainButton.setVisibility(View.GONE);
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
});
+
+ mIconController = createIconController();
+ if (mIconController.getActsAsConfirmButton()) {
+ mIconView.setOnClickListener((view) -> {
+ if (mState == STATE_PENDING_CONFIRMATION) {
+ updateState(STATE_AUTHENTICATED);
+ }
+ });
+ }
}
/**
@@ -706,21 +702,13 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- onAttachedToWindowInternal();
- }
- /**
- * Contains all the testable logic that should be invoked when {@link #onAttachedToWindow()} is
- * invoked.
- */
- @VisibleForTesting
- void onAttachedToWindowInternal() {
mTitleView.setText(mPromptInfo.getTitle());
if (isDeviceCredentialAllowed()) {
final CharSequence credentialButtonText;
- final @Utils.CredentialType int credentialType =
- Utils.getCredentialType(mContext, mEffectiveUserId);
+ @Utils.CredentialType final int credentialType =
+ Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId);
switch (credentialType) {
case Utils.CREDENTIAL_PIN:
credentialButtonText =
@@ -731,9 +719,6 @@
getResources().getString(R.string.biometric_dialog_use_pattern);
break;
case Utils.CREDENTIAL_PASSWORD:
- credentialButtonText =
- getResources().getString(R.string.biometric_dialog_use_password);
- break;
default:
credentialButtonText =
getResources().getString(R.string.biometric_dialog_use_password);
@@ -749,7 +734,6 @@
}
setTextOrHide(mSubtitleView, mPromptInfo.getSubtitle());
-
setTextOrHide(mDescriptionView, mPromptInfo.getDescription());
if (mSavedState == null) {
@@ -774,6 +758,8 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ mIconController.setDeactivated(true);
+
// Empty the handler, otherwise things like ACTION_AUTHENTICATED may be duplicated once
// the new dialog is restored.
mHandler.removeCallbacksAndMessages(null /* all */);
@@ -856,15 +842,7 @@
@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- onLayoutInternal();
- }
- /**
- * Contains all the testable logic that should be invoked when
- * {@link #onLayout(boolean, int, int, int, int)}, is invoked.
- */
- @VisibleForTesting
- void onLayoutInternal() {
// Start with initial size only once. Subsequent layout changes don't matter since we
// only care about the initial icon position.
if (mIconOriginalY == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 21edb24..6b6af4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -16,9 +16,10 @@
package com.android.systemui.biometrics;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -52,13 +53,16 @@
import android.widget.ScrollView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Top level container/controller for the BiometricPrompt UI.
@@ -66,54 +70,52 @@
public class AuthContainerView extends LinearLayout
implements AuthDialog, WakefulnessLifecycle.Observer {
- private static final String TAG = "BiometricPrompt/AuthContainerView";
- private static final int ANIMATION_DURATION_SHOW_MS = 250;
- private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
+ private static final String TAG = "AuthContainerView";
- static final int STATE_UNKNOWN = 0;
- static final int STATE_ANIMATING_IN = 1;
- static final int STATE_PENDING_DISMISS = 2;
- static final int STATE_SHOWING = 3;
- static final int STATE_ANIMATING_OUT = 4;
- static final int STATE_GONE = 5;
+ private static final int ANIMATION_DURATION_SHOW_MS = 250;
+ private static final int ANIMATION_DURATION_AWAY_MS = 350;
+
+ private static final int STATE_UNKNOWN = 0;
+ private static final int STATE_ANIMATING_IN = 1;
+ private static final int STATE_PENDING_DISMISS = 2;
+ private static final int STATE_SHOWING = 3;
+ private static final int STATE_ANIMATING_OUT = 4;
+ private static final int STATE_GONE = 5;
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING,
STATE_ANIMATING_OUT, STATE_GONE})
- @interface ContainerState {}
+ private @interface ContainerState {}
- final Config mConfig;
- final int mEffectiveUserId;
- @Nullable private final List<FingerprintSensorPropertiesInternal> mFpProps;
- @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
+ private final Config mConfig;
+ private final int mEffectiveUserId;
private final Handler mHandler;
- private final Injector mInjector;
private final IBinder mWindowToken = new Binder();
private final WindowManager mWindowManager;
- private final AuthPanelController mPanelController;
private final Interpolator mLinearOutSlowIn;
- @VisibleForTesting final BiometricCallback mBiometricCallback;
private final CredentialCallback mCredentialCallback;
-
- @VisibleForTesting final FrameLayout mFrameLayout;
- @VisibleForTesting @Nullable AuthBiometricView mBiometricView;
- @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
-
- @VisibleForTesting final ImageView mBackgroundView;
- @VisibleForTesting final ScrollView mBiometricScrollView;
- private final View mPanelView;
-
- private final float mTranslationY;
-
+ private final LockPatternUtils mLockPatternUtils;
private final WakefulnessLifecycle mWakefulnessLifecycle;
- @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
+ @VisibleForTesting final BiometricCallback mBiometricCallback;
+
+ @Nullable private AuthBiometricView mBiometricView;
+ @Nullable private AuthCredentialView mCredentialView;
+ private final AuthPanelController mPanelController;
+ private final FrameLayout mFrameLayout;
+ private final ImageView mBackgroundView;
+ private final ScrollView mBiometricScrollView;
+ private final View mPanelView;
+ private final float mTranslationY;
+ @ContainerState private int mContainerState = STATE_UNKNOWN;
+ private final Set<Integer> mFailedModalities = new HashSet<Integer>();
// Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
- @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
+ @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
// HAT received from LockSettingsService when credential is verified.
- @Nullable byte[] mCredentialAttestation;
+ @Nullable private byte[] mCredentialAttestation;
+ @VisibleForTesting
static class Config {
Context mContext;
AuthDialogCallback mCallback;
@@ -122,11 +124,11 @@
int mUserId;
String mOpPackageName;
int[] mSensorIds;
- boolean mCredentialAllowed;
boolean mSkipIntro;
long mOperationId;
long mRequestId;
- @BiometricMultiSensorMode int mMultiSensorConfig;
+ boolean mSkipAnimation = false;
+ @BiometricMultiSensorMode int mMultiSensorConfig = BIOMETRIC_MULTI_SENSOR_DEFAULT;
}
public static class Builder {
@@ -167,7 +169,7 @@
return this;
}
- public Builder setOperationId(long operationId) {
+ public Builder setOperationId(@DurationMillisLong long operationId) {
mConfig.mOperationId = operationId;
return this;
}
@@ -178,55 +180,27 @@
return this;
}
+ @VisibleForTesting
+ public Builder setSkipAnimationDuration(boolean skip) {
+ mConfig.mSkipAnimation = skip;
+ return this;
+ }
+
/** The multi-sensor mode. */
public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
mConfig.mMultiSensorConfig = multiSensorConfig;
return this;
}
- public AuthContainerView build(int[] sensorIds, boolean credentialAllowed,
+ public AuthContainerView build(int[] sensorIds,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull UserManager userManager,
+ @NonNull LockPatternUtils lockPatternUtils) {
mConfig.mSensorIds = sensorIds;
- mConfig.mCredentialAllowed = credentialAllowed;
- return new AuthContainerView(
- mConfig, new Injector(), fpProps, faceProps, wakefulnessLifecycle);
- }
- }
-
- public static class Injector {
- ScrollView getBiometricScrollView(FrameLayout parent) {
- return parent.findViewById(R.id.biometric_scrollview);
- }
-
- FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
- return (FrameLayout) factory.inflate(
- R.layout.auth_container_view, root, false /* attachToRoot */);
- }
-
- AuthPanelController getPanelController(Context context, View panelView) {
- return new AuthPanelController(context, panelView);
- }
-
- ImageView getBackgroundView(FrameLayout parent) {
- return parent.findViewById(R.id.background);
- }
-
- View getPanelView(FrameLayout parent) {
- return parent.findViewById(R.id.panel);
- }
-
- int getAnimateCredentialStartDelayMs() {
- return AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS;
- }
-
- UserManager getUserManager(Context context) {
- return UserManager.get(context);
- }
-
- int getCredentialType(Context context, int effectiveUserId) {
- return Utils.getCredentialType(context, effectiveUserId);
+ return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
+ userManager, lockPatternUtils, new Handler(Looper.getMainLooper()));
}
}
@@ -246,6 +220,7 @@
animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
break;
case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
+ mFailedModalities.clear();
mConfig.mCallback.onTryAgainPressed();
break;
case AuthBiometricView.Callback.ACTION_ERROR:
@@ -255,10 +230,7 @@
mConfig.mCallback.onDeviceCredentialPressed();
mHandler.postDelayed(() -> {
addCredentialView(false /* animatePanel */, true /* animateContents */);
- }, mInjector.getAnimateCredentialStartDelayMs());
- break;
- case AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR:
- mConfig.mCallback.onStartFingerprintNow();
+ }, mConfig.mSkipAnimation ? 0 : AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS);
break;
default:
Log.e(TAG, "Unhandled action: " + action);
@@ -275,21 +247,19 @@
}
@VisibleForTesting
- AuthContainerView(Config config, Injector injector,
+ AuthContainerView(Config config,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull UserManager userManager,
+ @NonNull LockPatternUtils lockPatternUtils,
+ @NonNull Handler mainHandler) {
super(config.mContext);
mConfig = config;
- mInjector = injector;
- mFpProps = fpProps;
- mFaceProps = faceProps;
-
- mEffectiveUserId = mInjector.getUserManager(mContext)
- .getCredentialOwnerProfile(mConfig.mUserId);
-
- mHandler = new Handler(Looper.getMainLooper());
+ mLockPatternUtils = lockPatternUtils;
+ mEffectiveUserId = userManager.getCredentialOwnerProfile(mConfig.mUserId);
+ mHandler = mainHandler;
mWindowManager = mContext.getSystemService(WindowManager.class);
mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -299,100 +269,42 @@
mBiometricCallback = new BiometricCallback();
mCredentialCallback = new CredentialCallback();
- final LayoutInflater factory = LayoutInflater.from(mContext);
- mFrameLayout = mInjector.inflateContainerView(factory, this);
-
- mPanelView = mInjector.getPanelView(mFrameLayout);
- mPanelController = mInjector.getPanelController(mContext, mPanelView);
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ mFrameLayout = (FrameLayout) layoutInflater.inflate(
+ R.layout.auth_container_view, this, false /* attachToRoot */);
+ addView(mFrameLayout);
+ mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
+ mBackgroundView = mFrameLayout.findViewById(R.id.background);
+ mPanelView = mFrameLayout.findViewById(R.id.panel);
+ mPanelController = new AuthPanelController(mContext, mPanelView);
// Inflate biometric view only if necessary.
- final int sensorCount = config.mSensorIds.length;
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
- if (sensorCount == 1) {
- final int singleSensorAuthId = config.mSensorIds[0];
- if (Utils.containsSensorId(mFpProps, singleSensorAuthId)) {
- FingerprintSensorPropertiesInternal sensorProps = null;
- for (FingerprintSensorPropertiesInternal prop : mFpProps) {
- if (prop.sensorId == singleSensorAuthId) {
- sensorProps = prop;
- break;
- }
- }
+ final FingerprintSensorPropertiesInternal fpProperties =
+ Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds);
+ final FaceSensorPropertiesInternal faceProperties =
+ Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds);
- if (sensorProps.isAnyUdfpsType()) {
- AuthBiometricUdfpsView udfpsView = (AuthBiometricUdfpsView) factory
- .inflate(R.layout.auth_biometric_udfps_view, null, false);
- udfpsView.setSensorProps(sensorProps);
- mBiometricView = udfpsView;
- } else {
- mBiometricView = (AuthBiometricFingerprintView) factory
- .inflate(R.layout.auth_biometric_fingerprint_view, null, false);
- }
- } else if (Utils.containsSensorId(mFaceProps, singleSensorAuthId)) {
- mBiometricView = (AuthBiometricFaceView)
- factory.inflate(R.layout.auth_biometric_face_view, null, false);
- } else {
- // Unknown sensorId
- Log.e(TAG, "Unknown sensorId: " + singleSensorAuthId);
- mBiometricView = null;
- mBackgroundView = null;
- mBiometricScrollView = null;
- return;
- }
- } else if (sensorCount == 2) {
- final int[] allSensors = findFaceAndFingerprintSensors();
- final int faceSensorId = allSensors[0];
- final int fingerprintSensorId = allSensors[1];
-
- if (fingerprintSensorId == -1 || faceSensorId == -1) {
- Log.e(TAG, "Missing fingerprint or face for dual-sensor config");
- mBiometricView = null;
- mBackgroundView = null;
- mBiometricScrollView = null;
- return;
- }
-
- FingerprintSensorPropertiesInternal fingerprintSensorProps = null;
- for (FingerprintSensorPropertiesInternal prop : mFpProps) {
- if (prop.sensorId == fingerprintSensorId) {
- fingerprintSensorProps = prop;
- break;
- }
- }
-
- if (fingerprintSensorProps != null) {
- final AuthBiometricFaceToFingerprintView faceToFingerprintView =
- (AuthBiometricFaceToFingerprintView) factory.inflate(
- R.layout.auth_biometric_face_to_fingerprint_view, null, false);
- faceToFingerprintView.setFingerprintSensorProps(fingerprintSensorProps);
- faceToFingerprintView.setModalityListener(new ModalityListener() {
- @Override
- public void onModalitySwitched(int oldModality, int newModality) {
- maybeUpdatePositionForUdfps(true /* invalidate */);
- }
- });
- mBiometricView = faceToFingerprintView;
- } else {
- Log.e(TAG, "Fingerprint props not found for sensor ID: " + fingerprintSensorId);
- mBiometricView = null;
- mBackgroundView = null;
- mBiometricScrollView = null;
- return;
- }
+ if (fpProperties != null && faceProperties != null) {
+ final AuthBiometricFingerprintAndFaceView fingerprintAndFaceView =
+ (AuthBiometricFingerprintAndFaceView) layoutInflater.inflate(
+ R.layout.auth_biometric_fingerprint_and_face_view, null, false);
+ fingerprintAndFaceView.setSensorProperties(fpProperties);
+ mBiometricView = fingerprintAndFaceView;
+ } else if (fpProperties != null) {
+ final AuthBiometricFingerprintView fpView =
+ (AuthBiometricFingerprintView) layoutInflater.inflate(
+ R.layout.auth_biometric_fingerprint_view, null, false);
+ fpView.setSensorProperties(fpProperties);
+ mBiometricView = fpView;
+ } else if (faceProperties != null) {
+ mBiometricView = (AuthBiometricFaceView) layoutInflater.inflate(
+ R.layout.auth_biometric_face_view, null, false);
} else {
- Log.e(TAG, "Unsupported sensor array, length: " + sensorCount);
- mBiometricView = null;
- mBackgroundView = null;
- mBiometricScrollView = null;
- return;
+ Log.e(TAG, "No sensors found!");
}
}
- mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout);
- mBackgroundView = mInjector.getBackgroundView(mFrameLayout);
-
- addView(mFrameLayout);
-
// init view before showing
if (mBiometricView != null) {
mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
@@ -431,10 +343,6 @@
return Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo);
}
- private void addBiometricView() {
- mBiometricScrollView.addView(mBiometricView);
- }
-
/**
* Adds the credential view. When going from biometric to credential view, the biometric
* view starts the panel expansion animation. If the credential view is being shown first,
@@ -444,8 +352,8 @@
private void addCredentialView(boolean animatePanel, boolean animateContents) {
final LayoutInflater factory = LayoutInflater.from(mContext);
- final @Utils.CredentialType int credentialType = mInjector.getCredentialType(
- mContext, mEffectiveUserId);
+ @Utils.CredentialType final int credentialType = Utils.getCredentialType(
+ mLockPatternUtils, mEffectiveUserId);
switch (credentialType) {
case Utils.CREDENTIAL_PATTERN:
@@ -493,15 +401,11 @@
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
- onAttachedToWindowInternal();
- }
- @VisibleForTesting
- void onAttachedToWindowInternal() {
mWakefulnessLifecycle.addObserver(this);
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
- addBiometricView();
+ mBiometricScrollView.addView(mBiometricView);
} else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
addCredentialView(true /* animatePanel */, false /* animateContents */);
} else {
@@ -521,17 +425,18 @@
mBiometricScrollView.setY(mTranslationY);
setAlpha(0f);
+ final long animateDuration = mConfig.mSkipAnimation ? 0 : ANIMATION_DURATION_SHOW_MS;
postOnAnimation(() -> {
mPanelView.animate()
.translationY(0)
- .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.withEndAction(this::onDialogAnimatedIn)
.start();
mBiometricScrollView.animate()
.translationY(0)
- .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
@@ -539,14 +444,14 @@
mCredentialView.setY(mTranslationY);
mCredentialView.animate()
.translationY(0)
- .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
}
animate()
.alpha(1f)
- .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
@@ -555,15 +460,8 @@
}
private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
- if (view instanceof AuthBiometricUdfpsView) {
- return true;
- }
-
- if (view instanceof AuthBiometricFaceToFingerprintView) {
- AuthBiometricFaceToFingerprintView faceToFingerprintView =
- (AuthBiometricFaceToFingerprintView) view;
- return faceToFingerprintView.getActiveSensorType() == TYPE_FINGERPRINT
- && faceToFingerprintView.isFingerprintUdfps();
+ if (view instanceof AuthBiometricFingerprintView) {
+ return ((AuthBiometricFingerprintView) view).isUdfps();
}
return false;
@@ -652,12 +550,13 @@
}
@Override
- public void onAuthenticationSucceeded() {
- mBiometricView.onAuthenticationSucceeded();
+ public void onAuthenticationSucceeded(@Modality int modality) {
+ mBiometricView.onAuthenticationSucceeded(modality);
}
@Override
public void onAuthenticationFailed(@Modality int modality, String failureReason) {
+ mFailedModalities.add(modality);
mBiometricView.onAuthenticationFailed(modality, failureReason);
}
@@ -672,8 +571,17 @@
}
@Override
+ public void onPointerDown() {
+ if (mBiometricView.onPointerDown(mFailedModalities)) {
+ Log.d(TAG, "retrying failed modalities (pointer down)");
+ mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+ }
+ }
+
+ @Override
public void onSaveState(@NonNull Bundle outState) {
- outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
+ outState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY,
+ mContainerState == STATE_ANIMATING_OUT);
// In the case where biometric and credential are both allowed, we can assume that
// biometric isn't showing if credential is showing since biometric is shown first.
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
@@ -695,8 +603,7 @@
mBiometricView.startTransitionToCredentialUI();
}
- @VisibleForTesting
- void animateAway(int reason) {
+ void animateAway(@AuthDialogCallback.DismissedReason int reason) {
animateAway(true /* sendReason */, reason);
}
@@ -724,31 +631,32 @@
removeWindowIfAttached();
};
+ final long animateDuration = mConfig.mSkipAnimation ? 0 : ANIMATION_DURATION_AWAY_MS;
postOnAnimation(() -> {
mPanelView.animate()
.translationY(mTranslationY)
- .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.withEndAction(endActionRunnable)
.start();
mBiometricScrollView.animate()
.translationY(mTranslationY)
- .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
mCredentialView.animate()
.translationY(mTranslationY)
- .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
}
animate()
.alpha(0f)
- .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
@@ -773,8 +681,7 @@
mWindowManager.removeView(this);
}
- @VisibleForTesting
- void onDialogAnimatedIn() {
+ private void onDialogAnimatedIn() {
if (mContainerState == STATE_PENDING_DISMISS) {
Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
@@ -788,8 +695,7 @@
}
@VisibleForTesting
- static WindowManager.LayoutParams getLayoutParams(IBinder windowToken,
- CharSequence title) {
+ static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_SECURE;
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
@@ -805,24 +711,4 @@
lp.token = windowToken;
return lp;
}
-
- // returns [face, fingerprint] sensor ids (id is -1 if not present)
- private int[] findFaceAndFingerprintSensors() {
- int faceSensorId = -1;
- int fingerprintSensorId = -1;
-
- for (final int sensorId : mConfig.mSensorIds) {
- if (Utils.containsSensorId(mFpProps, sensorId)) {
- fingerprintSensorId = sensorId;
- } else if (Utils.containsSensorId(mFaceProps, sensorId)) {
- faceSensorId = sensorId;
- }
-
- if (fingerprintSensorId != -1 && faceSensorId != -1) {
- break;
- }
- }
-
- return new int[] {faceSensorId, fingerprintSensorId};
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index dfb8c18..64c2d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -51,6 +51,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.MotionEvent;
@@ -59,6 +60,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.CoreStartable;
import com.android.systemui.assist.ui.DisplayUtils;
import com.android.systemui.dagger.SysUISingleton;
@@ -123,8 +125,6 @@
@Nullable private SidefpsController mSidefpsController;
@Nullable private IBiometricContextListener mBiometricContextListener;
@VisibleForTesting
- TaskStackListener mTaskStackListener;
- @VisibleForTesting
IBiometricSysuiReceiver mReceiver;
@VisibleForTesting
@NonNull final BiometricDisplayListener mOrientationListener;
@@ -137,13 +137,16 @@
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllAuthenticatorsRegistered;
+ @NonNull private final UserManager mUserManager;
+ @NonNull private final LockPatternUtils mLockPatternUtils;
- private class BiometricTaskStackListener extends TaskStackListener {
+ @VisibleForTesting
+ final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onTaskStackChanged() {
mHandler.post(AuthController.this::handleTaskStackChanged);
}
- }
+ };
private final IFingerprintAuthenticatorsRegisteredCallback
mFingerprintAuthenticatorsRegisteredCallback =
@@ -256,6 +259,17 @@
mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null;
if (mUdfpsProps != null) {
mUdfpsController = mUdfpsControllerFactory.get();
+ mUdfpsController.addCallback(new UdfpsController.Callback() {
+ @Override
+ public void onFingerUp() {}
+
+ @Override
+ public void onFingerDown() {
+ if (mCurrentDialog != null) {
+ mCurrentDialog.onPointerDown();
+ }
+ }
+ });
}
mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null;
if (mSidefpsProps != null) {
@@ -360,20 +374,6 @@
}
@Override
- public void onStartFingerprintNow() {
- if (mReceiver == null) {
- Log.e(TAG, "onStartUdfpsNow: Receiver is null");
- return;
- }
-
- try {
- mReceiver.onStartFingerprintNow();
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
- }
- }
-
- @Override
public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) {
switch (reason) {
case AuthDialogCallback.DISMISSED_USER_CANCELED:
@@ -503,12 +503,16 @@
Provider<UdfpsController> udfpsControllerFactory,
Provider<SidefpsController> sidefpsControllerFactory,
@NonNull DisplayManager displayManager,
- WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull UserManager userManager,
+ @NonNull LockPatternUtils lockPatternUtils,
@NonNull StatusBarStateController statusBarStateController,
@Main Handler handler) {
super(context);
mExecution = execution;
mWakefulnessLifecycle = wakefulnessLifecycle;
+ mUserManager = userManager;
+ mLockPatternUtils = lockPatternUtils;
mHandler = handler;
mCommandQueue = commandQueue;
mActivityTaskManager = activityTaskManager;
@@ -583,7 +587,6 @@
mFingerprintAuthenticatorsRegisteredCallback);
}
- mTaskStackListener = new BiometricTaskStackListener();
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
}
@@ -668,11 +671,11 @@
* example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}.
*/
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(@Modality int modality) {
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
if (mCurrentDialog != null) {
- mCurrentDialog.onAuthenticationSucceeded();
+ mCurrentDialog.onAuthenticationSucceeded(modality);
} else {
Log.w(TAG, "onBiometricAuthenticated callback but dialog gone");
}
@@ -827,7 +830,7 @@
final String opPackageName = (String) args.arg6;
final long operationId = args.argl1;
final long requestId = args.argl2;
- final @BiometricMultiSensorMode int multiSensorConfig = args.argi2;
+ @BiometricMultiSensorMode final int multiSensorConfig = args.argi2;
// Create a new dialog but do not replace the current one yet.
final AuthDialog newDialog = buildDialog(
@@ -835,13 +838,14 @@
requireConfirmation,
userId,
sensorIds,
- credentialAllowed,
opPackageName,
skipAnimation,
operationId,
requestId,
multiSensorConfig,
- mWakefulnessLifecycle);
+ mWakefulnessLifecycle,
+ mUserManager,
+ mLockPatternUtils);
if (newDialog == null) {
Log.e(TAG, "Unsupported type configuration");
@@ -902,8 +906,7 @@
// Only show the dialog if necessary. If it was animating out, the dialog is supposed
// to send its pending callback immediately.
- if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
- != AuthContainerView.STATE_ANIMATING_OUT) {
+ if (!savedState.getBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false)) {
final boolean credentialShowing =
savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);
if (credentialShowing) {
@@ -927,10 +930,12 @@
}
protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
- int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
+ int userId, int[] sensorIds, String opPackageName,
boolean skipIntro, long operationId, long requestId,
@BiometricMultiSensorMode int multiSensorConfig,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull UserManager userManager,
+ @NonNull LockPatternUtils lockPatternUtils) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
.setPromptInfo(promptInfo)
@@ -941,7 +946,8 @@
.setOperationId(operationId)
.setRequestId(requestId)
.setMultiSensorConfig(multiSensorConfig)
- .build(sensorIds, credentialAllowed, mFpProps, mFaceProps, wakefulnessLifecycle);
+ .build(sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, userManager,
+ lockPatternUtils);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index fa5213e..59ed156 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -31,7 +31,7 @@
*/
public interface AuthDialog {
- String KEY_CONTAINER_STATE = "container_state";
+ String KEY_CONTAINER_GOING_AWAY = "container_going_away";
String KEY_BIOMETRIC_SHOWING = "biometric_showing";
String KEY_CREDENTIAL_SHOWING = "credential_showing";
@@ -64,7 +64,7 @@
@interface DialogSize {}
/**
- * Parameters used when laying out {@link AuthBiometricView}, its sublclasses, and
+ * Parameters used when laying out {@link AuthBiometricView}, its subclasses, and
* {@link AuthPanelController}.
*/
class LayoutParams {
@@ -113,7 +113,7 @@
/**
* Biometric authenticated. May be pending user confirmation, or completed.
*/
- void onAuthenticationSucceeded();
+ void onAuthenticationSucceeded(@Modality int modality);
/**
* Authentication failed (reject, timeout). Dialog stays showing.
@@ -136,6 +136,9 @@
*/
void onError(@Modality int modality, String error);
+ /** UDFPS pointer down event. */
+ void onPointerDown();
+
/**
* Save the current state.
* @param outState
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
index 9f40ca7..a7d2901 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -70,9 +70,4 @@
* Notifies when the dialog has finished animating.
*/
void onDialogAnimatedIn();
-
- /**
- * Notifies that the fingerprint sensor should be started now.
- */
- void onStartFingerprintNow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
index 6607915..242601d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt
@@ -23,7 +23,7 @@
*
* Currently doesn't draw anything.
*
- * Note that [AuthBiometricUdfpsView] also shows UDFPS animations. At some point we should
+ * Note that [AuthBiometricFingerprintViewController] also shows UDFPS animations. At some point we should
* de-dupe this if necessary.
*/
class UdfpsBpView(context: Context, attrs: AttributeSet?) : UdfpsAnimationView(context, attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 590963b..086894d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -300,7 +300,10 @@
when (context.display!!.rotation) {
Surface.ROTATION_90 -> {
if (!shouldRotate(animation)) {
- Log.v(TAG, "skip rotating udfps location ROTATION_90")
+ Log.v(TAG, "skip rotating udfps location ROTATION_90" +
+ " animation=$animation" +
+ " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
+ " isOccluded=${keyguardStateController.isOccluded}")
} else {
Log.v(TAG, "rotate udfps location ROTATION_90")
x = (location.sensorLocationY - location.sensorRadius - paddingX)
@@ -309,7 +312,10 @@
}
Surface.ROTATION_270 -> {
if (!shouldRotate(animation)) {
- Log.v(TAG, "skip rotating udfps location ROTATION_270")
+ Log.v(TAG, "skip rotating udfps location ROTATION_270" +
+ " animation=$animation" +
+ " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
+ " isOccluded=${keyguardStateController.isOccluded}")
} else {
Log.v(TAG, "rotate udfps location ROTATION_270")
x = (p.x - location.sensorLocationY - location.sensorRadius - paddingX)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
deleted file mode 100644
index 6989547..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
-import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.biometrics.PromptInfo;
-import android.hardware.biometrics.SensorPropertiesInternal;
-import android.os.UserManager;
-import android.util.DisplayMetrics;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.widget.LockPatternUtils;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-
-public class Utils {
-
- public static final int CREDENTIAL_PIN = 1;
- public static final int CREDENTIAL_PATTERN = 2;
- public static final int CREDENTIAL_PASSWORD = 3;
-
- /** Base set of layout flags for fingerprint overlay widgets. */
- public static final int FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD})
- @interface CredentialType {}
-
- static float dpToPixels(Context context, float dp) {
- return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
- / DisplayMetrics.DENSITY_DEFAULT);
- }
-
- static void notifyAccessibilityContentChanged(AccessibilityManager am, ViewGroup view) {
- if (!am.isEnabled()) {
- return;
- }
- AccessibilityEvent event = AccessibilityEvent.obtain();
- event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE);
- view.sendAccessibilityEventUnchecked(event);
- view.notifySubtreeAccessibilityStateChanged(view, view, CONTENT_CHANGE_TYPE_SUBTREE);
- }
-
- static boolean isDeviceCredentialAllowed(PromptInfo promptInfo) {
- @Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
- return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
- }
-
- static boolean isBiometricAllowed(PromptInfo promptInfo) {
- @Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
- return (authenticators & Authenticators.BIOMETRIC_WEAK) != 0;
- }
-
- static @CredentialType int getCredentialType(Context context, int userId) {
- final LockPatternUtils lpu = new LockPatternUtils(context);
- switch (lpu.getKeyguardStoredPasswordQuality(userId)) {
- case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
- return CREDENTIAL_PATTERN;
- case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
- case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
- return CREDENTIAL_PIN;
- case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
- case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
- case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
- case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
- return CREDENTIAL_PASSWORD;
- default:
- return CREDENTIAL_PASSWORD;
- }
- }
-
- static boolean isManagedProfile(Context context, int userId) {
- final UserManager userManager = context.getSystemService(UserManager.class);
- return userManager.isManagedProfile(userId);
- }
-
- static boolean containsSensorId(@Nullable List<? extends SensorPropertiesInternal> properties,
- int sensorId) {
- if (properties == null) {
- return false;
- }
-
- for (SensorPropertiesInternal prop : properties) {
- if (prop.sensorId == sensorId) {
- return true;
- }
- }
-
- return false;
- }
-
- static boolean isSystem(@NonNull Context context, @Nullable String clientPackage) {
- final boolean hasPermission = context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
- == PackageManager.PERMISSION_GRANTED;
- return hasPermission && "android".equals(clientPackage);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
new file mode 100644
index 0000000..d0d6f4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 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.biometrics
+
+import android.Manifest
+import android.annotation.IntDef
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+import android.content.Context
+import android.content.pm.PackageManager
+import android.hardware.biometrics.BiometricManager.Authenticators
+import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.SensorPropertiesInternal
+import android.os.UserManager
+import android.util.DisplayMetrics
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import com.android.internal.widget.LockPatternUtils
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+
+object Utils {
+ const val CREDENTIAL_PIN = 1
+ const val CREDENTIAL_PATTERN = 2
+ const val CREDENTIAL_PASSWORD = 3
+
+ /** Base set of layout flags for fingerprint overlay widgets. */
+ const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
+ (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
+
+ @JvmStatic
+ fun dpToPixels(context: Context, dp: Float): Float {
+ val density = context.resources.displayMetrics.densityDpi.toFloat()
+ return dp * (density / DisplayMetrics.DENSITY_DEFAULT)
+ }
+
+ @JvmStatic
+ fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) {
+ if (!am.isEnabled) {
+ return
+ }
+ val event = AccessibilityEvent.obtain()
+ event.eventType = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+ event.contentChangeTypes =
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+ view.sendAccessibilityEventUnchecked(event)
+ view.notifySubtreeAccessibilityStateChanged(
+ view,
+ view,
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+ )
+ }
+
+ @JvmStatic
+ fun isDeviceCredentialAllowed(promptInfo: PromptInfo): Boolean =
+ (promptInfo.authenticators and Authenticators.DEVICE_CREDENTIAL) != 0
+
+ @JvmStatic
+ fun isBiometricAllowed(promptInfo: PromptInfo): Boolean =
+ (promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0
+
+ @JvmStatic
+ @CredentialType
+ fun getCredentialType(utils: LockPatternUtils, userId: Int): Int =
+ when (utils.getKeyguardStoredPasswordQuality(userId)) {
+ PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN
+ PASSWORD_QUALITY_NUMERIC,
+ PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN
+ PASSWORD_QUALITY_ALPHABETIC,
+ PASSWORD_QUALITY_ALPHANUMERIC,
+ PASSWORD_QUALITY_COMPLEX,
+ PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD
+ else -> CREDENTIAL_PASSWORD
+ }
+
+ @JvmStatic
+ fun isManagedProfile(context: Context, userId: Int): Boolean =
+ context.getSystemService(UserManager::class.java)?.isManagedProfile(userId) ?: false
+
+ @JvmStatic
+ fun <T : SensorPropertiesInternal> findFirstSensorProperties(
+ properties: List<T>?,
+ sensorIds: IntArray
+ ): T? = properties?.firstOrNull { sensorIds.contains(it.sensorId) }
+
+ @JvmStatic
+ fun isSystem(context: Context, clientPackage: String?): Boolean {
+ val hasPermission =
+ (context.checkCallingOrSelfPermission(Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ == PackageManager.PERMISSION_GRANTED)
+ return hasPermission && "android" == clientPackage
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
+ internal annotation class CredentialType
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index d2ded71..5b38e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -97,7 +97,11 @@
}
bindTryCount++
try {
- context.bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user)
+ val bound = context
+ .bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user)
+ if (!bound) {
+ context.unbindService(serviceConnection)
+ }
} catch (e: SecurityException) {
Log.e(TAG, "Failed to bind to service", e)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 4819bf5..a4f9f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -54,7 +54,7 @@
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.DelayableExecutor
-import kotlin.reflect.KClass
+import java.util.function.Supplier
/**
* Wraps the widgets that make up the UI representation of a {@link Control}. Updates to the view
@@ -90,20 +90,20 @@
status: Int,
template: ControlTemplate,
deviceType: Int
- ): KClass<out Behavior> {
+ ): Supplier<out Behavior> {
return when {
- status != Control.STATUS_OK -> StatusBehavior::class
- template == ControlTemplate.NO_TEMPLATE -> TouchBehavior::class
- template is ThumbnailTemplate -> ThumbnailBehavior::class
+ status != Control.STATUS_OK -> Supplier { StatusBehavior() }
+ template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() }
+ template is ThumbnailTemplate -> Supplier { ThumbnailBehavior() }
// Required for legacy support, or where cameras do not use the new template
- deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class
- template is ToggleTemplate -> ToggleBehavior::class
- template is StatelessTemplate -> TouchBehavior::class
- template is ToggleRangeTemplate -> ToggleRangeBehavior::class
- template is RangeTemplate -> ToggleRangeBehavior::class
- template is TemperatureControlTemplate -> TemperatureControlBehavior::class
- else -> DefaultBehavior::class
+ deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() }
+ template is ToggleTemplate -> Supplier { ToggleBehavior() }
+ template is StatelessTemplate -> Supplier { TouchBehavior() }
+ template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() }
+ template is RangeTemplate -> Supplier { ToggleRangeBehavior() }
+ template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() }
+ else -> Supplier { DefaultBehavior() }
}
}
}
@@ -253,13 +253,14 @@
fun bindBehavior(
existingBehavior: Behavior?,
- clazz: KClass<out Behavior>,
+ supplier: Supplier<out Behavior>,
offset: Int = 0
): Behavior {
- val behavior = if (existingBehavior == null || existingBehavior!!::class != clazz) {
+ val newBehavior = supplier.get()
+ val behavior = if (existingBehavior == null ||
+ existingBehavior::class != newBehavior::class) {
// Behavior changes can signal a change in template from the app or
// first time setup
- val newBehavior = clazz.java.newInstance()
newBehavior.initialize(this)
// let behaviors define their own, if necessary, and clear any existing ones
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 7e1fce2..ebc7666 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -177,9 +179,26 @@
}
mDreamOverlayContainerViewController.init();
+ // Make extra sure the container view has been removed from its old parent (otherwise we
+ // risk an IllegalStateException in some cases when setting the container view as the
+ // window's content view and the container view hasn't been properly removed previously).
+ removeContainerViewFromParent();
mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
}
+
+ private void removeContainerViewFromParent() {
+ View containerView = mDreamOverlayContainerViewController.getContainerView();
+ if (containerView == null) {
+ return;
+ }
+ ViewGroup parentView = (ViewGroup) containerView.getParent();
+ if (parentView == null) {
+ return;
+ }
+ Log.w(TAG, "Removing dream overlay container view parent!");
+ parentView.removeView(containerView);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 582965a..35f29b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -23,6 +23,7 @@
import android.graphics.Matrix
import android.graphics.Rect
import android.os.Handler
+import android.util.Log
import android.view.RemoteAnimationTarget
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
@@ -47,6 +48,8 @@
import javax.inject.Inject
import kotlin.math.min
+const val TAG = "KeyguardUnlock"
+
/**
* Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
* in during keyguard exit.
@@ -584,8 +587,16 @@
* animation.
*/
fun hideKeyguardViewAfterRemoteAnimation() {
- // Hide the keyguard, with no fade out since we animated it away during the unlock.
- keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 0 /* fadeOutDuration */)
+ if (keyguardViewController.isShowing) {
+ // Hide the keyguard, with no fade out since we animated it away during the unlock.
+ keyguardViewController.hide(
+ surfaceBehindRemoteAnimationStartTime,
+ 0 /* fadeOutDuration */
+ )
+ } else {
+ Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+ "showing. Ignoring...")
+ }
}
private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index b323586..ae7a671 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -154,6 +154,28 @@
return factory.create("SwipeStatusBarAwayLog", 30);
}
+ /**
+ * Provides a logging buffer for logs related to the media tap-to-transfer chip on the sender
+ * device. See {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}.
+ */
+ @Provides
+ @SysUISingleton
+ @MediaTttSenderLogBuffer
+ public static LogBuffer provideMediaTttSenderLogBuffer(LogBufferFactory factory) {
+ return factory.create("MediaTttSender", 20);
+ }
+
+ /**
+ * Provides a logging buffer for logs related to the media tap-to-transfer chip on the receiver
+ * device. See {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}.
+ */
+ @Provides
+ @SysUISingleton
+ @MediaTttReceiverLogBuffer
+ public static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) {
+ return factory.create("MediaTttReceiver", 20);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
new file mode 100644
index 0000000..5c572e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MediaTttReceiverLogBuffer {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
new file mode 100644
index 0000000..edab8c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MediaTttSenderLogBuffer {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index c3b4354..66c036c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -17,6 +17,9 @@
package com.android.systemui.media.dagger;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer;
+import com.android.systemui.log.dagger.MediaTttSenderLogBuffer;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaFlags;
import com.android.systemui.media.MediaHierarchyManager;
@@ -27,8 +30,11 @@
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
+import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
import java.util.Optional;
@@ -112,6 +118,24 @@
return Optional.of(controllerReceiverLazy.get());
}
+ @Provides
+ @SysUISingleton
+ @MediaTttSenderLogger
+ static MediaTttLogger providesMediaTttSenderLogger(
+ @MediaTttSenderLogBuffer LogBuffer buffer
+ ) {
+ return new MediaTttLogger("Sender", buffer);
+ }
+
+ @Provides
+ @SysUISingleton
+ @MediaTttReceiverLogger
+ static MediaTttLogger providesMediaTttReceiverLogger(
+ @MediaTttReceiverLogBuffer LogBuffer buffer
+ ) {
+ return new MediaTttLogger("Receiver", buffer);
+ }
+
/** */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 3cd3905..0b23ad5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -54,6 +54,7 @@
MediaOutputDialog mediaOutputDialog) {
super(controller);
mMediaOutputDialog = mediaOutputDialog;
+ setHasStableIds(true);
}
@Override
@@ -79,6 +80,20 @@
}
@Override
+ public long getItemId(int position) {
+ final int size = mController.getMediaDevices().size();
+ if (position == size && mController.isZeroMode()) {
+ return -1;
+ } else if (position < size) {
+ return ((List<MediaDevice>) (mController.getMediaDevices()))
+ .get(position).getId().hashCode();
+ } else if (DEBUG) {
+ Log.d(TAG, "Incorrect position for item id: " + position);
+ }
+ return position;
+ }
+
+ @Override
public int getItemCount() {
if (mController.isZeroMode()) {
// Add extra one for "pair new" or dynamic group
@@ -159,7 +174,7 @@
onCheckBoxClicked(false, device);
});
setCheckBoxColor(mCheckBox, mController.getColorActiveItem());
- initSessionSeekbar();
+ initSeekbar(device);
} else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) {
mStatusIcon.setImageDrawable(
mContext.getDrawable(R.drawable.media_output_status_check));
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 1f11d0c..c96aca3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -249,7 +249,7 @@
mSeekBar.setMin(0);
final int currentVolume = device.getCurrentVolume();
if (mSeekBar.getProgress() != currentVolume) {
- mSeekBar.setProgress(currentVolume);
+ mSeekBar.setProgress(currentVolume, true);
}
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
@@ -278,7 +278,7 @@
mSeekBar.setMin(0);
final int currentVolume = mController.getSessionVolume();
if (mSeekBar.getProgress() != currentVolume) {
- mSeekBar.setProgress(currentVolume);
+ mSeekBar.setProgress(currentVolume, true);
}
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 15b8f13..9c4b39d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -43,6 +43,7 @@
*/
abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
internal val context: Context,
+ internal val logger: MediaTttLogger,
private val windowManager: WindowManager,
private val viewUtil: ViewUtil,
@Main private val mainExecutor: DelayableExecutor,
@@ -93,18 +94,26 @@
// Cancel and re-set the chip timeout each time we get a new state.
cancelChipViewTimeout?.run()
- cancelChipViewTimeout = mainExecutor.executeDelayed(this::removeChip, TIMEOUT_MILLIS)
+ cancelChipViewTimeout = mainExecutor.executeDelayed(
+ { removeChip(MediaTttRemovalReason.REASON_TIMEOUT) },
+ chipState.getTimeoutMs()
+ )
}
- /** Hides the chip. */
- fun removeChip() {
- // TODO(b/203800347): We may not want to hide the chip if we're currently in a
- // TransferTriggered state: Once the user has initiated the transfer, they should be able
- // to move away from the receiver device but still see the status of the transfer.
+ /**
+ * Hides the chip.
+ *
+ * @param removalReason a short string describing why the chip was removed (timeout, state
+ * change, etc.)
+ */
+ open fun removeChip(removalReason: String) {
if (chipView == null) { return }
+ logger.logChipRemoval(removalReason)
tapGestureDetector.removeOnGestureDetectedCallback(TAG)
windowManager.removeView(chipView)
chipView = null
+ // No need to time the chip out since it's already gone
+ cancelChipViewTimeout?.run()
}
/**
@@ -136,7 +145,7 @@
// If the tap is within the chip bounds, we shouldn't hide the chip (in case users think the
// chip is tappable).
if (!viewUtil.touchIsWithinView(view, e.x, e.y)) {
- removeChip()
+ removeChip(MediaTttRemovalReason.REASON_SCREEN_TAP)
}
}
}
@@ -145,5 +154,9 @@
// UpdateMediaTapToTransferReceiverDisplayTest
private const val WINDOW_TITLE = "Media Transfer Chip View"
private val TAG = MediaTttChipControllerCommon::class.simpleName!!
-@VisibleForTesting
-const val TIMEOUT_MILLIS = 3000L
+
+object MediaTttRemovalReason {
+ const val REASON_TIMEOUT = "TIMEOUT"
+ const val REASON_SCREEN_TAP = "SCREEN_TAP"
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
index 2da48ce..6f60181 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt
@@ -52,6 +52,14 @@
null
}
}
+
+ /**
+ * Returns the amount of time this chip should display on the screen before it times out and
+ * disappears. [MediaTttChipControllerCommon] will ensure that the timeout resets each time we
+ * receive a new state.
+ */
+ open fun getTimeoutMs(): Long = DEFAULT_TIMEOUT_MILLIS
}
+private const val DEFAULT_TIMEOUT_MILLIS = 3000L
private val TAG = MediaTttChipState::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
new file mode 100644
index 0000000..d3b5bc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.common
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.MediaTttSenderLogBuffer
+
+/**
+ * A logger for media tap-to-transfer events.
+ *
+ * @property deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
+ */
+class MediaTttLogger(
+ private val deviceTypeTag: String,
+ @MediaTttSenderLogBuffer private val buffer: LogBuffer
+){
+ /** Logs a change in the chip state for the given [mediaRouteId]. */
+ fun logStateChange(stateName: String, mediaRouteId: String) {
+ buffer.log(
+ BASE_TAG + deviceTypeTag,
+ LogLevel.DEBUG,
+ {
+ str1 = stateName
+ str2 = mediaRouteId
+ },
+ { "State changed to $str1 for ID=$str2" }
+ )
+ }
+
+ /** Logs that we removed the chip for the given [reason]. */
+ fun logChipRemoval(reason: String) {
+ buffer.log(
+ BASE_TAG + deviceTypeTag,
+ LogLevel.DEBUG,
+ { str1 = reason },
+ { "Chip removed due to $str1" }
+ )
+ }
+}
+
+private const val BASE_TAG = "MediaTtt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 3d43ebe..1a96ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -28,6 +28,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -43,6 +44,7 @@
class MediaTttChipControllerReceiver @Inject constructor(
commandQueue: CommandQueue,
context: Context,
+ @MediaTttReceiverLogger logger: MediaTttLogger,
windowManager: WindowManager,
viewUtil: ViewUtil,
mainExecutor: DelayableExecutor,
@@ -50,6 +52,7 @@
@Main private val mainHandler: Handler,
) : MediaTttChipControllerCommon<ChipStateReceiver>(
context,
+ logger,
windowManager,
viewUtil,
mainExecutor,
@@ -79,6 +82,7 @@
appIcon: Icon?,
appName: CharSequence?
) {
+ logger.logStateChange(stateIntToString(displayState), routeInfo.id)
when(displayState) {
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> {
val packageName = routeInfo.packageName
@@ -97,7 +101,8 @@
)
}
}
- StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER -> removeChip()
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER ->
+ removeChip(removalReason = FAR_FROM_SENDER)
else ->
Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
}
@@ -106,6 +111,16 @@
override fun updateChipView(chipState: ChipStateReceiver, currentChipView: ViewGroup) {
setIcon(chipState, currentChipView)
}
+
+ private fun stateIntToString(@StatusBarManager.MediaTransferReceiverState state: Int): String {
+ return when (state) {
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> CLOSE_TO_SENDER
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER -> FAR_FROM_SENDER
+ else -> "INVALID: $state"
+ }
+ }
}
private const val RECEIVER_TAG = "MediaTapToTransferRcvr"
+private const val CLOSE_TO_SENDER = "CLOSE_TO_SENDER"
+private const val FAR_FROM_SENDER = "FAR_FROM_SENDER"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
new file mode 100644
index 0000000..54fc48d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.receiver
+
+import java.lang.annotation.Documented
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import javax.inject.Qualifier
+
+@Qualifier
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+annotation class MediaTttReceiverLogger
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 9b537fb..22424a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -97,6 +97,7 @@
}
override fun showLoading() = true
+ override fun getTimeoutMs() = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
}
/**
@@ -111,6 +112,7 @@
}
override fun showLoading() = true
+ override fun getTimeoutMs() = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
}
/**
@@ -194,3 +196,8 @@
return context.getString(R.string.media_transfer_failed)
}
}
+
+// Give the Transfer*Triggered states a longer timeout since those states represent an active
+// process and we should keep the user informed about it as long as possible (but don't allow it to
+// continue indefinitely).
+private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 15000L
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 180e4ee..da2aac4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -29,6 +29,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -43,13 +45,22 @@
class MediaTttChipControllerSender @Inject constructor(
commandQueue: CommandQueue,
context: Context,
+ @MediaTttSenderLogger logger: MediaTttLogger,
windowManager: WindowManager,
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
tapGestureDetector: TapGestureDetector,
) : MediaTttChipControllerCommon<ChipStateSender>(
- context, windowManager, viewUtil, mainExecutor, tapGestureDetector, R.layout.media_ttt_chip
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ mainExecutor,
+ tapGestureDetector,
+ R.layout.media_ttt_chip
) {
+ private var currentlyDisplayedChipState: ChipStateSender? = null
+
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferSenderDisplay(
@StatusBarManager.MediaTransferSenderState displayState: Int,
@@ -71,6 +82,7 @@
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?
) {
+ logger.logStateChange(stateIntToString(displayState), routeInfo.id)
val appPackageName = routeInfo.packageName
val otherDeviceName = routeInfo.name.toString()
val chipState = when(displayState) {
@@ -90,7 +102,7 @@
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED ->
TransferFailed(appPackageName)
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER -> {
- removeChip()
+ removeChip(removalReason = FAR_FROM_RECEIVER)
null
}
else -> {
@@ -106,6 +118,8 @@
/** Displays the chip view for the given state. */
override fun updateChipView(chipState: ChipStateSender, currentChipView: ViewGroup) {
+ currentlyDisplayedChipState = chipState
+
// App icon
setIcon(chipState, currentChipView)
@@ -129,6 +143,43 @@
currentChipView.requireViewById<View>(R.id.failure_icon).visibility =
if (showFailure) { View.VISIBLE } else { View.GONE }
}
+
+ override fun removeChip(removalReason: String) {
+ // Don't remove the chip if we're mid-transfer since the user should still be able to
+ // see the status of the transfer. (But do remove it if it's finally timed out.)
+ if ((currentlyDisplayedChipState is TransferToReceiverTriggered ||
+ currentlyDisplayedChipState is TransferToThisDeviceTriggered)
+ && removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
+ return
+ }
+ super.removeChip(removalReason)
+ currentlyDisplayedChipState = null
+ }
+
+ private fun stateIntToString(@StatusBarManager.MediaTransferSenderState state: Int): String {
+ return when(state) {
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST ->
+ "ALMOST_CLOSE_TO_START_CAST"
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST ->
+ "ALMOST_CLOSE_TO_END_CAST"
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED ->
+ "TRANSFER_TO_RECEIVER_TRIGGERED"
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED ->
+ "TRANSFER_TO_THIS_DEVICE_TRIGGERED"
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED ->
+ "TRANSFER_TO_RECEIVER_SUCCEEDED"
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED ->
+ "TRANSFER_TO_THIS_DEVICE_SUCCEEDED"
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED ->
+ "TRANSFER_TO_RECEIVER_FAILED"
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED ->
+ "TRANSFER_TO_THIS_DEVICE_FAILED"
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER ->
+ FAR_FROM_RECEIVER
+ else -> "INVALID: $state"
+ }
+ }
}
const val SENDER_TAG = "MediaTapToTransferSender"
+private const val FAR_FROM_RECEIVER = "FAR_FROM_RECEIVER"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
new file mode 100644
index 0000000..4393af9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.sender
+
+import java.lang.annotation.Documented
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import javax.inject.Qualifier
+
+@Qualifier
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+annotation class MediaTttSenderLogger
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 3ab1216..ec6094d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -757,7 +757,8 @@
| WindowManager.LayoutParams.FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId());
- mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION
+ | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
mWindowManager.addView(mOrientationHandle, mOrientationParams);
mOrientationHandle.setVisibility(View.GONE);
mOrientationParams.setFitInsetsTypes(0 /* types*/);
@@ -1565,7 +1566,8 @@
}
lp.token = new Binder();
lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
+ | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.windowAnimations = 0;
lp.setTitle("NavigationBar" + mContext.getDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9ea2763..4dacf5d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -263,6 +263,15 @@
// Notify FalsingManager that an intentional gesture has occurred.
// TODO(b/186519446): use a different method than isFalseTouch
mFalsingManager.isFalseTouch(BACK_GESTURE);
+ // Only inject back keycodes when ahead-of-time back dispatching is disabled.
+ if (mBackAnimation == null) {
+ boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+ boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+ if (DEBUG_MISSING_GESTURE) {
+ Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down="
+ + sendDown + ", up=" + sendUp);
+ }
+ }
mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
(int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
@@ -936,6 +945,9 @@
public void setBackAnimation(BackAnimation backAnimation) {
mBackAnimation = backAnimation;
+ if (mEdgeBackPlugin != null && mEdgeBackPlugin instanceof NavigationBarEdgePanel) {
+ ((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index a6bad15..a6919e8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -280,7 +280,7 @@
}
};
private BackCallback mBackCallback;
- private final BackAnimation mBackAnimation;
+ private BackAnimation mBackAnimation;
public NavigationBarEdgePanel(Context context,
BackAnimation backAnimation) {
@@ -385,6 +385,10 @@
mShowProtection = !isPrimaryDisplay;
}
+ public void setBackAnimation(BackAnimation backAnimation) {
+ mBackAnimation = backAnimation;
+ }
+
@Override
public void onDestroy() {
cancelFailsafe();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 32e0805..a49d3fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -204,6 +204,9 @@
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
| Context.BIND_WAIVE_PRIORITY,
mUser);
+ if (!mIsBound) {
+ mContext.unbindService(this);
+ }
} catch (SecurityException e) {
Log.e(TAG, "Failed to bind to service", e);
mIsBound = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 8a02e59..5932a64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -310,11 +310,11 @@
long requestId, @BiometricMultiSensorMode int multiSensorConfig) {
}
- /** @see IStatusBar#onBiometricAuthenticated() */
- default void onBiometricAuthenticated() {
+ /** @see IStatusBar#onBiometricAuthenticated(int) */
+ default void onBiometricAuthenticated(@Modality int modality) {
}
- /** @see IStatusBar#onBiometricHelp(String) */
+ /** @see IStatusBar#onBiometricHelp(int, String) */
default void onBiometricHelp(@Modality int modality, String message) {
}
@@ -963,9 +963,11 @@
}
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(@Modality int modality) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = modality;
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
}
}
@@ -1465,9 +1467,11 @@
break;
}
case MSG_BIOMETRIC_AUTHENTICATED: {
+ SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onBiometricAuthenticated();
+ mCallbacks.get(i).onBiometricAuthenticated(someArgs.argi1 /* modality */);
}
+ someArgs.recycle();
break;
}
case MSG_BIOMETRIC_HELP: {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0509a7c..ccec0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -50,6 +50,7 @@
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -122,7 +123,7 @@
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
private final KeyguardStateController mKeyguardStateController;
- private final StatusBarStateController mStatusBarStateController;
+ protected final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
@@ -138,6 +139,7 @@
private final IActivityManager mIActivityManager;
private final FalsingManager mFalsingManager;
private final KeyguardBypassController mKeyguardBypassController;
+ private final Handler mHandler;
protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
private BroadcastReceiver mBroadcastReceiver;
@@ -194,7 +196,9 @@
* Creates a new KeyguardIndicationController and registers callbacks.
*/
@Inject
- public KeyguardIndicationController(Context context,
+ public KeyguardIndicationController(
+ Context context,
+ @Main Looper mainLooper,
WakeLock.Builder wakeLockBuilder,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
@@ -230,6 +234,19 @@
mKeyguardBypassController = keyguardBypassController;
mScreenLifecycle = screenLifecycle;
mScreenLifecycle.addObserver(mScreenObserver);
+
+ mHandler = new Handler(mainLooper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_HIDE_TRANSIENT) {
+ hideTransientIndication();
+ } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
+ showActionToUnlock();
+ } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
+ hideBiometricMessage();
+ }
+ }
+ };
}
/** Call this after construction to finish setting up the instance. */
@@ -242,7 +259,6 @@
mDockManager.addAlignmentStateListener(
alignState -> mHandler.post(() -> handleAlignStateChanged(alignState)));
mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback());
- mKeyguardUpdateMonitor.registerCallback(mTickReceiver);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
@@ -260,7 +276,7 @@
mLockScreenIndicationView,
mExecutor,
mStatusBarStateController);
- updateIndication(false /* animate */);
+ updateDeviceEntryIndication(false /* animate */);
updateOrganizedOwnedDevice();
if (mBroadcastReceiver == null) {
// Update the disclosure proactively to avoid IPC on the critical path.
@@ -288,7 +304,7 @@
}
if (!alignmentIndication.equals(mAlignmentIndication)) {
mAlignmentIndication = alignmentIndication;
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
}
@@ -309,28 +325,30 @@
return mUpdateMonitorCallback;
}
- /**
- * This method also doesn't update transient messages like biometrics since those messages
- * are also updated separately.
- */
- private void updatePersistentIndications(boolean animate, int userId) {
- updateDisclosure();
- updateOwnerInfo();
- updateBattery(animate);
- updateUserLocked(userId);
- updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
- updateAlignment();
- updateLogoutView();
- updateResting();
+ private void updateLockScreenIndications(boolean animate, int userId) {
+ // update transient messages:
+ updateBiometricMessage();
+ updateTransient();
+
+ // Update persistent messages. The following methods should only be called if we're on the
+ // lock screen:
+ updateLockScreenDisclosureMsg();
+ updateLockScreenOwnerInfo();
+ updateLockScreenBatteryMsg(animate);
+ updateLockScreenUserLockedMsg(userId);
+ updateLockScreenTrustMsg(userId, getTrustGrantedIndication(), getTrustManagedIndication());
+ updateLockScreenAlignmentMsg();
+ updateLockScreenLogoutView();
+ updateLockScreenRestingMsg();
}
private void updateOrganizedOwnedDevice() {
// avoid calling this method since it has an IPC
mOrganizationOwnedDevice = whitelistIpcs(this::isOrganizationOwnedDevice);
- updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser());
+ updateDeviceEntryIndication(false);
}
- private void updateDisclosure() {
+ private void updateLockScreenDisclosureMsg() {
if (mOrganizationOwnedDevice) {
mBackgroundExecutor.execute(() -> {
final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
@@ -374,7 +392,7 @@
}
}
- private void updateOwnerInfo() {
+ private void updateLockScreenOwnerInfo() {
// Check device owner info on a bg thread.
// It makes multiple IPCs that could block the thread it's run on.
mBackgroundExecutor.execute(() -> {
@@ -406,7 +424,7 @@
});
}
- private void updateBattery(boolean animate) {
+ private void updateLockScreenBatteryMsg(boolean animate) {
if (mPowerPluggedIn || mEnableBatteryDefender) {
String powerIndication = computePowerIndication();
if (DEBUG_CHARGING_SPEED) {
@@ -426,7 +444,7 @@
}
}
- private void updateUserLocked(int userId) {
+ private void updateLockScreenUserLockedMsg(int userId) {
if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_USER_LOCKED,
@@ -442,6 +460,11 @@
}
private void updateBiometricMessage() {
+ if (mDozing) {
+ updateDeviceEntryIndication(false);
+ return;
+ }
+
if (!TextUtils.isEmpty(mBiometricMessage)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_BIOMETRIC_MESSAGE,
@@ -455,25 +478,22 @@
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
}
-
- if (mDozing) {
- updateIndication(false);
- }
}
private void updateTransient() {
+ if (mDozing) {
+ updateDeviceEntryIndication(false);
+ return;
+ }
+
if (!TextUtils.isEmpty(mTransientIndication)) {
mRotateTextViewController.showTransient(mTransientIndication);
} else {
mRotateTextViewController.hideTransient();
}
-
- if (mDozing) {
- updateIndication(false);
- }
}
- private void updateTrust(int userId, CharSequence trustGrantedIndication,
+ private void updateLockScreenTrustMsg(int userId, CharSequence trustGrantedIndication,
CharSequence trustManagedIndication) {
if (!TextUtils.isEmpty(trustGrantedIndication)
&& mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
@@ -499,7 +519,7 @@
}
}
- private void updateAlignment() {
+ private void updateLockScreenAlignmentMsg() {
if (!TextUtils.isEmpty(mAlignmentIndication)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_ALIGNMENT,
@@ -514,7 +534,7 @@
}
}
- private void updateResting() {
+ private void updateLockScreenRestingMsg() {
if (!TextUtils.isEmpty(mRestingIndication)
&& !mRotateTextViewController.hasIndications()) {
mRotateTextViewController.updateIndication(
@@ -529,7 +549,7 @@
}
}
- private void updateLogoutView() {
+ private void updateLockScreenLogoutView() {
final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
&& KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
if (shouldShowLogout) {
@@ -608,7 +628,7 @@
if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
hideTransientIndication();
}
- updateIndication(false);
+ updateDeviceEntryIndication(false);
} else if (!visible) {
// If we unlock and return to keyguard quickly, previous error should not be shown
hideTransientIndication();
@@ -620,7 +640,7 @@
*/
public void setRestingIndication(String restingIndication) {
mRestingIndication = restingIndication;
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
/**
@@ -697,6 +717,10 @@
* Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
*/
private void showBiometricMessage(CharSequence biometricMessage) {
+ if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
+ return;
+ }
+
mBiometricMessage = biometricMessage;
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
@@ -725,7 +749,12 @@
}
}
- protected final void updateIndication(boolean animate) {
+ /**
+ * Updates message shown to the user. If the device is dozing, a single message with the highest
+ * precedence is shown. If the device is not dozing (on the lock screen), then several messages
+ * may continuously be cycled through.
+ */
+ protected final void updateDeviceEntryIndication(boolean animate) {
if (!mVisible) {
return;
}
@@ -734,44 +763,37 @@
mIndicationArea.setVisibility(VISIBLE);
// Walk down a precedence-ordered list of what indication
- // should be shown based on user or device state
- // AoD
+ // should be shown based on device state
if (mDozing) {
mLockScreenIndicationView.setVisibility(View.GONE);
mTopIndicationView.setVisibility(VISIBLE);
// When dozing we ignore any text color and use white instead, because
// colors can be hard to read in low brightness.
mTopIndicationView.setTextColor(Color.WHITE);
+
+ CharSequence newIndication = null;
if (!TextUtils.isEmpty(mBiometricMessage)) {
- mWakeLock.setAcquired(true);
- mTopIndicationView.switchIndication(mBiometricMessage, null,
- true, () -> mWakeLock.setAcquired(false));
+ newIndication = mBiometricMessage;
} else if (!TextUtils.isEmpty(mTransientIndication)) {
- mWakeLock.setAcquired(true);
- mTopIndicationView.switchIndication(mTransientIndication, null,
- true, () -> mWakeLock.setAcquired(false));
+ newIndication = mTransientIndication;
} else if (!mBatteryPresent) {
// If there is no battery detected, hide the indication and bail
mIndicationArea.setVisibility(GONE);
+ return;
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
- mTopIndicationView.switchIndication(mAlignmentIndication, null,
- false /* animate */, null /* onAnimationEndCallback */);
+ newIndication = mAlignmentIndication;
mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
- String indication = computePowerIndication();
- if (animate) {
- mWakeLock.setAcquired(true);
- mTopIndicationView.switchIndication(indication, null, true /* animate */,
- () -> mWakeLock.setAcquired(false));
- } else {
- mTopIndicationView.switchIndication(indication, null, false /* animate */,
- null /* onAnimationEndCallback */);
- }
+ newIndication = computePowerIndication();
} else {
- String percentage = NumberFormat.getPercentInstance()
+ newIndication = NumberFormat.getPercentInstance()
.format(mBatteryLevel / 100f);
- mTopIndicationView.switchIndication(percentage, null /* indication */,
- false /* animate */, null /* onAnimationEnd*/);
+ }
+
+ if (!TextUtils.equals(mTopIndicationView.getText(), newIndication)) {
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(newIndication, null,
+ true, () -> mWakeLock.setAcquired(false));
}
return;
}
@@ -780,7 +802,7 @@
mTopIndicationView.setVisibility(GONE);
mTopIndicationView.setText(null);
mLockScreenIndicationView.setVisibility(View.VISIBLE);
- updatePersistentIndications(animate, KeyguardUpdateMonitor.getCurrentUser());
+ updateLockScreenIndications(animate, KeyguardUpdateMonitor.getCurrentUser());
}
protected String computePowerIndication() {
@@ -842,29 +864,6 @@
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
}
- private final KeyguardUpdateMonitorCallback mTickReceiver =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onTimeChanged() {
- if (mVisible) {
- updateIndication(false /* animate */);
- }
- }
- };
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_HIDE_TRANSIENT) {
- hideTransientIndication();
- } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
- showActionToUnlock();
- } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
- hideBiometricMessage();
- }
- }
- };
-
/**
* Show message on the keyguard for how the user can unlock/enter their device.
*/
@@ -929,7 +928,7 @@
pw.println(" mBiometricMessage: " + mBiometricMessage);
pw.println(" mBatteryLevel: " + mBatteryLevel);
pw.println(" mBatteryPresent: " + mBatteryPresent);
- pw.println(" mTextView.getText(): " + (
+ pw.println(" AOD text: " + (
mTopIndicationView == null ? null : mTopIndicationView.getText()));
pw.println(" computePowerIndication(): " + computePowerIndication());
pw.println(" trustGrantedIndication: " + getTrustGrantedIndication());
@@ -940,6 +939,13 @@
public static final int HIDE_DELAY_MS = 5000;
@Override
+ public void onTimeChanged() {
+ if (mVisible) {
+ updateDeviceEntryIndication(false /* animate */);
+ }
+ }
+
+ @Override
public void onRefreshBatteryInfo(BatteryStatus status) {
boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
|| status.isCharged();
@@ -962,7 +968,7 @@
Log.e(TAG, "Error calling IBatteryStats: ", e);
mChargingTimeRemaining = -1;
}
- updateIndication(!wasPluggedIn && mPowerPluggedInWired);
+ updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
if (mDozing) {
if (!wasPluggedIn && mPowerPluggedIn) {
showTransientIndication(computePowerIndication());
@@ -1084,14 +1090,13 @@
if (KeyguardUpdateMonitor.getCurrentUser() != userId) {
return;
}
- updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
+ updateDeviceEntryIndication(false);
}
@Override
public void showTrustGrantedMessage(CharSequence message) {
mTrustGrantedIndication = message;
- updateTrust(KeyguardUpdateMonitor.getCurrentUser(), getTrustGrantedIndication(),
- getTrustManagedIndication());
+ updateDeviceEntryIndication(false);
}
@Override
@@ -1125,21 +1130,21 @@
@Override
public void onUserSwitchComplete(int userId) {
if (mVisible) {
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
}
@Override
public void onUserUnlocked() {
if (mVisible) {
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
}
@Override
public void onLogoutEnabledChanged() {
if (mVisible) {
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
}
@@ -1167,7 +1172,7 @@
if (mDozing) {
hideBiometricMessage();
}
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
};
@@ -1175,7 +1180,7 @@
new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
- updateIndication(false);
+ updateDeviceEntryIndication(false);
}
@Override
@@ -1185,7 +1190,7 @@
mTopIndicationView.clearMessages();
mRotateTextViewController.clearMessages();
} else {
- updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser());
+ updateDeviceEntryIndication(false);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index de2346a..cc16ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -3978,8 +3978,14 @@
mActivityLaunchAnimator.startPendingIntentWithAnimation(
controller, animate, intent.getCreatorPackage(),
- (animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null,
- null, getActivityOptions(mDisplayId, animationAdapter)));
+ (animationAdapter) -> {
+ ActivityOptions options = new ActivityOptions(
+ getActivityOptions(mDisplayId, animationAdapter));
+ // TODO b/221255671: restrict this to only be set for notifications
+ options.setEligibleForLegacyPermissionPrompt(true);
+ return intent.sendAndReturnResult(null, 0, null, null, null,
+ null, options.toBundle());
+ });
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
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 ebe91c7..8b25c2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -292,20 +292,11 @@
}
public void updateControlScreenOff() {
- final boolean controlScreenOff = shouldControlUnlockedScreenOff()
- || (!getDisplayNeedsBlanking() && getAlwaysOn() && mKeyguardShowing);
- setControlScreenOffAnimation(controlScreenOff);
- }
-
- /**
- * Whether we're capable of controlling the screen off animation if we want to. This isn't
- * possible if AOD isn't even enabled or if the flag is disabled, or if the display needs
- * blanking.
- */
- public boolean canControlUnlockedScreenOff() {
- return getAlwaysOn()
- && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
- && !getDisplayNeedsBlanking();
+ if (!getDisplayNeedsBlanking()) {
+ final boolean controlScreenOff =
+ getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
+ setControlScreenOffAnimation(controlScreenOff);
+ }
}
/**
@@ -318,7 +309,8 @@
* disabled for a11y.
*/
public boolean shouldControlUnlockedScreenOff() {
- return mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
+ return canControlUnlockedScreenOff()
+ && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
}
public boolean shouldDelayKeyguardShow() {
@@ -350,6 +342,16 @@
return getAlwaysOn() && mKeyguardShowing;
}
+ /**
+ * Whether we're capable of controlling the screen off animation if we want to. This isn't
+ * possible if AOD isn't even enabled or if the flag is disabled.
+ */
+ public boolean canControlUnlockedScreenOff() {
+ return getAlwaysOn()
+ && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
+ && !getDisplayNeedsBlanking();
+ }
+
private boolean getBoolean(String propName, int resId) {
return SystemProperties.getBoolean(propName, mResources.getBoolean(resId));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 8b0eaec..c11d450 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -61,14 +61,6 @@
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private lateinit var mCentralSurfaces: CentralSurfaces
-
- /**
- * Whether or not [initialize] has been called to provide us with the StatusBar,
- * NotificationPanelViewController, and LightRevealSrim so that we can run the unlocked screen
- * off animation.
- */
- private var initialized = false
-
private lateinit var lightRevealScrim: LightRevealScrim
private var animatorDurationScale = 1f
@@ -124,7 +116,6 @@
centralSurfaces: CentralSurfaces,
lightRevealScrim: LightRevealScrim
) {
- this.initialized = true
this.lightRevealScrim = lightRevealScrim
this.mCentralSurfaces = centralSurfaces
@@ -271,18 +262,6 @@
* on the current state of the device.
*/
fun shouldPlayUnlockedScreenOffAnimation(): Boolean {
- // If we haven't been initialized yet, we don't have a StatusBar/LightRevealScrim yet, so we
- // can't perform the animation.
- if (!initialized) {
- return false
- }
-
- // If the device isn't in a state where we can control unlocked screen off (no AOD enabled,
- // power save, etc.) then we shouldn't try to do so.
- if (!dozeParameters.get().canControlUnlockedScreenOff()) {
- return false
- }
-
// If we explicitly already decided not to play the screen off animation, then never change
// our mind.
if (decidedToAnimateGoingToSleep == false) {
@@ -325,7 +304,7 @@
}
override fun shouldDelayDisplayDozeTransition(): Boolean =
- shouldPlayUnlockedScreenOffAnimation()
+ dozeParameters.get().shouldControlUnlockedScreenOff()
/**
* Whether we're doing the light reveal animation or we're done with that and animating in the
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index 796af11..58b4af4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -134,6 +134,16 @@
}
@Test
+ public void moveWindowMagnifierToPosition() throws RemoteException {
+ mIWindowMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY,
+ 100f, 200f, mAnimationCallback);
+ waitForIdleSync();
+
+ verify(mWindowMagnificationController).moveWindowMagnifierToPosition(
+ eq(100f), eq(200f), any(IRemoteMagnificationAnimationCallback.class));
+ }
+
+ @Test
public void showMagnificationButton() throws RemoteException {
mIWindowMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
new file mode 100644
index 0000000..30bff09
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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 android.os.RemoteException;
+import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MockMagnificationAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub {
+
+ private final CountDownLatch mCountDownLatch;
+ private final AtomicInteger mSuccessCount;
+ private final AtomicInteger mFailedCount;
+
+ MockMagnificationAnimationCallback(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ mSuccessCount = new AtomicInteger();
+ mFailedCount = new AtomicInteger();
+ }
+
+ public int getSuccessCount() {
+ return mSuccessCount.get();
+ }
+
+ public int getFailedCount() {
+ return mFailedCount.get();
+ }
+
+ @Override
+ public void onResult(boolean success) throws RemoteException {
+ mCountDownLatch.countDown();
+ if (success) {
+ mSuccessCount.getAndIncrement();
+ } else {
+ mFailedCount.getAndIncrement();
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 3cc177d..21c3d6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -60,6 +60,8 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@Ignore
@@ -218,6 +220,29 @@
}
@Test
+ public void enableWindowMagnificationWithUnchanged_enabling_expectedValuesToDefault()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(2);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+
+ enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ animationCallback);
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ Float.NaN, Float.NaN, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // The callback in 2nd enableWindowMagnification will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // The callback in 1st enableWindowMagnification will return false
+ assertEquals(1, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+ }
+
+ @Test
public void enableWindowMagnificationWithScaleOne_enabled_AnimationAndInvokeCallback()
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
@@ -425,6 +450,102 @@
}
@Test
+ public void moveWindowMagnifierToPosition_enabled_expectedValues()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ final float targetCenterX = DEFAULT_CENTER_X + 100;
+ final float targetCenterY = DEFAULT_CENTER_Y + 100;
+ enableWindowMagnificationWithoutAnimation();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ assertEquals(1, animationCallback.getSuccessCount());
+ assertEquals(0, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPositionMultipleTimes_enabled_expectedValuesToLastOne()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(4);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ enableWindowMagnificationWithoutAnimation();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, animationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, animationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, animationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // only the last one callback will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // the others will return false
+ assertEquals(3, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPosition_enabling_expectedValuesToLastOne()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(2);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ final float targetCenterX = DEFAULT_CENTER_X + 100;
+ final float targetCenterY = DEFAULT_CENTER_Y + 100;
+
+ enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ animationCallback);
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // The callback in moveWindowMagnifierToPosition will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // The callback in enableWindowMagnification will return false
+ assertEquals(1, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPositionWithCenterUnchanged_enabling_expectedValuesToDefault()
+ throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(2);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+
+ enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ animationCallback);
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ Float.NaN, Float.NaN, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // The callback in moveWindowMagnifierToPosition will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // The callback in enableWindowMagnification will return false
+ assertEquals(1, animationCallback.getFailedCount());
+ verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+ }
+
+ @Test
public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback()
throws RemoteException {
enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
@@ -569,6 +690,20 @@
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 100f, DEFAULT_CENTER_Y + 100f);
}
+ @Test
+ public void moveWindowMagnifierToPosition_enabled() {
+ final float targetCenterX = DEFAULT_CENTER_X + 100;
+ final float targetCenterY = DEFAULT_CENTER_Y + 100;
+ enableWindowMagnificationWithoutAnimation();
+
+ mInstrumentation.runOnMainSync(
+ () -> mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
+ mAnimationCallback));
+ SystemClock.sleep(mWaitingAnimationPeriod);
+
+ verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
+ }
+
private void verifyFinalSpec(float expectedScale, float expectedCenterX,
float expectedCenterY) {
assertEquals(expectedScale, mController.getScale(), 0f);
@@ -663,6 +798,13 @@
}
@Override
+ void moveWindowMagnifierToPosition(float positionX, float positionY,
+ IRemoteMagnificationAnimationCallback callback) {
+ super.moveWindowMagnifierToPosition(positionX, positionY, callback);
+ mSpyController.moveWindowMagnifierToPosition(positionX, positionY, callback);
+ }
+
+ @Override
void setScale(float scale) {
super.setScale(scale);
mSpyController.setScale(scale);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 6e5926d..a49c4d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -40,6 +40,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -88,6 +89,8 @@
import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@LargeTest
@@ -96,12 +99,16 @@
public class WindowMagnificationControllerTest extends SysuiTestCase {
private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
+ private static final long ANIMATION_DURATION_MS = 300;
+ private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS;
@Mock
private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@Mock
private MirrorWindowControl mMirrorWindowControl;
@Mock
private WindowMagnifierCallback mWindowMagnifierCallback;
+ @Mock
+ IRemoteMagnificationAnimationCallback mAnimationCallback;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -287,6 +294,82 @@
}
@Test
+ public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
+ throws InterruptedException {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN, 0, 0, null);
+ });
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
+ final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ assertEquals(1, animationCallback.getSuccessCount());
+ assertEquals(0, animationCallback.getFailedCount());
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ assertEquals(mWindowMagnificationController.getCenterX(),
+ sourceBoundsCaptor.getValue().exactCenterX(), 0);
+ assertEquals(mWindowMagnificationController.getCenterY(),
+ sourceBoundsCaptor.getValue().exactCenterY(), 0);
+ assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0);
+ assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
+ throws InterruptedException {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN, 0, 0, null);
+ });
+ final CountDownLatch countDownLatch = new CountDownLatch(4);
+ final MockMagnificationAnimationCallback animationCallback =
+ new MockMagnificationAnimationCallback(countDownLatch);
+ final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
+ final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 10, centerY + 10, animationCallback);
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 20, centerY + 20, animationCallback);
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 30, centerY + 30, animationCallback);
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 40, centerY + 40, animationCallback);
+ });
+
+ assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ // only the last one callback will return true
+ assertEquals(1, animationCallback.getSuccessCount());
+ // the others will return false
+ assertEquals(3, animationCallback.getFailedCount());
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ assertEquals(mWindowMagnificationController.getCenterX(),
+ sourceBoundsCaptor.getValue().exactCenterX(), 0);
+ assertEquals(mWindowMagnificationController.getCenterY(),
+ sourceBoundsCaptor.getValue().exactCenterY(), 0);
+ assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0);
+ assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0);
+ }
+
+ @Test
public void setScale_enabled_expectedValueAndUpdateStateDescription() {
mInstrumentation.runOnMainSync(
() -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
@@ -484,6 +567,7 @@
mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
assertTrue(
mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
+ verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index d3f30c50..ccf2f8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -148,13 +148,13 @@
}
@Test
- public void onDrag_enabled_notifyCallback() throws RemoteException {
+ public void onMove_enabled_notifyCallback() throws RemoteException {
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
- mWindowMagnification.onDrag(TEST_DISPLAY);
+ mWindowMagnification.onMove(TEST_DISPLAY);
- verify(mConnectionCallback).onDrag(TEST_DISPLAY);
+ verify(mConnectionCallback).onMove(TEST_DISPLAY);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
deleted file mode 100644
index 619d48d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Bundle;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-@SmallTest
-public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
-
- @Mock AuthBiometricView.Callback mCallback;
-
- private AuthBiometricFaceToFingerprintView mFaceToFpView;
-
- @Mock private Button mNegativeButton;
- @Mock private Button mCancelButton;
- @Mock private Button mConfirmButton;
- @Mock private Button mUseCredentialButton;
- @Mock private Button mTryAgainButton;
-
- @Mock private TextView mTitleView;
- @Mock private TextView mSubtitleView;
- @Mock private TextView mDescriptionView;
- @Mock private TextView mIndicatorView;
- @Mock private ImageView mIconView;
- @Mock private View mIconHolderView;
- @Mock private AuthBiometricFaceView.IconController mFaceIconController;
- @Mock private AuthBiometricFaceToFingerprintView.UdfpsIconController mUdfpsIconController;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mFaceToFpView = new TestableView(mContext);
- mFaceToFpView.mFaceIconController = mFaceIconController;
- mFaceToFpView.mUdfpsIconController = mUdfpsIconController;
- mFaceToFpView.setCallback(mCallback);
-
- mFaceToFpView.mNegativeButton = mNegativeButton;
- mFaceToFpView.mCancelButton = mCancelButton;
- mFaceToFpView.mUseCredentialButton = mUseCredentialButton;
- mFaceToFpView.mConfirmButton = mConfirmButton;
- mFaceToFpView.mTryAgainButton = mTryAgainButton;
- mFaceToFpView.mIndicatorView = mIndicatorView;
- }
-
- @Test
- public void testStateUpdated_whenDialogAnimatedIn() {
- mFaceToFpView.onDialogAnimatedIn();
- verify(mFaceToFpView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
- verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
- }
-
- @Test
- public void testIconUpdatesState_whenDialogStateUpdated() {
- mFaceToFpView.onDialogAnimatedIn();
- verify(mFaceToFpView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
- verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
-
- mFaceToFpView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
- verify(mFaceToFpView.mFaceIconController).updateState(
- eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING),
- eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED));
- verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt());
-
- assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED, mFaceToFpView.mState);
- }
-
- @Test
- public void testStateUpdated_whenSwitchToFingerprint() {
- mFaceToFpView.onDialogAnimatedIn();
- verify(mFaceToFpView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
-
- verify(mFaceToFpView.mFaceIconController).deactivate();
- verify(mFaceToFpView.mUdfpsIconController).updateState(
- eq(AuthBiometricFaceToFingerprintView.STATE_IDLE));
- verify(mConfirmButton).setVisibility(eq(View.GONE));
-
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
-
- verify(mFaceToFpView.mUdfpsIconController).updateState(
- eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
- }
-
- @Test
- public void testStateUpdated_whenSwitchToFingerprint_invokesCallbacks() {
- class TestModalityListener implements ModalityListener {
- public int switchCount = 0;
-
- @Override
- public void onModalitySwitched(int oldModality, int newModality) {
- assertEquals(TYPE_FINGERPRINT, newModality);
- assertEquals(TYPE_FACE, oldModality);
- switchCount++;
- }
- }
- final TestModalityListener modalityListener = new TestModalityListener();
-
- mFaceToFpView.onDialogAnimatedIn();
- mFaceToFpView.setModalityListener(modalityListener);
-
- assertEquals(0, modalityListener.switchCount);
-
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
-
- assertEquals(1, modalityListener.switchCount);
- }
-
- @Test
- @Ignore("flaky, b/189031816")
- public void testModeUpdated_onSoftError_whenSwitchToFingerprint() {
- mFaceToFpView.onDialogAnimatedIn();
- mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
- waitForIdleSync();
-
- verify(mIndicatorView).setText(
- eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
- verify(mCallback).onAction(
- eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
-
- // First we enter the error state, since we need to show the error animation/text. The
- // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
- assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
- }
-
- @Test
- @Ignore("flaky, b/189031816")
- public void testModeUpdated_onHardError_whenSwitchToFingerprint() {
- mFaceToFpView.onDialogAnimatedIn();
- mFaceToFpView.onError(TYPE_FACE, "oh no!");
- waitForIdleSync();
-
- verify(mIndicatorView).setText(
- eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
- verify(mCallback).onAction(
- eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
-
- // First we enter the error state, since we need to show the error animation/text. The
- // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING.
- assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState);
- }
-
- @Test
- public void testFingerprintOnlyStartsOnFirstError() {
- mFaceToFpView.onDialogAnimatedIn();
- verify(mFaceToFpView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
-
- mFaceToFpView.onDialogAnimatedIn();
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
- mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
-
- reset(mCallback);
-
- mFaceToFpView.onError(TYPE_FACE, "oh no!");
- mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
-
- verify(mCallback, never()).onAction(
- eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
- }
-
- @Test
- public void testOnSaveState() {
- final FingerprintSensorPropertiesInternal sensorProps = createFingerprintSensorProps();
- mFaceToFpView.setFingerprintSensorProps(sensorProps);
-
- final Bundle savedState = new Bundle();
- mFaceToFpView.onSaveState(savedState);
-
- assertEquals(savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE),
- mFaceToFpView.getActiveSensorType());
- assertEquals(savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS), sensorProps);
- }
-
- @Test
- public void testRestoreState() {
- final Bundle savedState = new Bundle();
- savedState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FINGERPRINT);
- savedState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS,
- createFingerprintSensorProps());
-
- mFaceToFpView.restoreState(savedState);
-
- assertEquals(mFaceToFpView.getActiveSensorType(), TYPE_FINGERPRINT);
- assertTrue(mFaceToFpView.isFingerprintUdfps());
- }
-
- @NonNull
- private static FingerprintSensorPropertiesInternal createFingerprintSensorProps() {
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- componentInfo.add(new ComponentInfoInternal("componentId", "hardwareVersion",
- "firmwareVersion", "serialNumber", "softwareVersion"));
-
- return new FingerprintSensorPropertiesInternal(
- 0 /* sensorId */,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- componentInfo,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- true /* resetLockoutRequiresHardwareAuthToken */,
- List.of(new SensorLocationInternal("" /* displayId */,
- 540 /* sensorLocationX */,
- 1600 /* sensorLocationY */,
- 100 /* sensorRadius */)));
- }
-
- public class TestableView extends AuthBiometricFaceToFingerprintView {
- public TestableView(Context context) {
- super(context, null, new MockInjector());
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return 0;
- }
- }
-
- private class MockInjector extends AuthBiometricView.Injector {
- @Override
- public Button getNegativeButton() {
- return mNegativeButton;
- }
-
- @Override
- public Button getCancelButton() {
- return mCancelButton;
- }
-
- @Override
- public Button getUseCredentialButton() {
- return mUseCredentialButton;
- }
-
- @Override
- public Button getConfirmButton() {
- return mConfirmButton;
- }
-
- @Override
- public Button getTryAgainButton() {
- return mTryAgainButton;
- }
-
- @Override
- public TextView getTitleView() {
- return mTitleView;
- }
-
- @Override
- public TextView getSubtitleView() {
- return mSubtitleView;
- }
-
- @Override
- public TextView getDescriptionView() {
- return mDescriptionView;
- }
-
- @Override
- public TextView getIndicatorView() {
- return mIndicatorView;
- }
-
- @Override
- public ImageView getIconView() {
- return mIconView;
- }
-
- @Override
- public View getIconHolderView() {
- return mIconHolderView;
- }
-
- @Override
- public int getDelayAfterError() {
- return 0;
- }
-
- @Override
- public int getMediumToLargeAnimationDurationMs() {
- return 0;
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
deleted file mode 100644
index b93381d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import com.android.systemui.R;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class AuthBiometricFaceViewTest extends SysuiTestCase {
-
- @Mock
- AuthBiometricView.Callback mCallback;
-
- private TestableFaceView mFaceView;
-
- @Mock private Button mNegativeButton;
- @Mock private Button mCancelButton;
- @Mock private Button mUseCredentialButton;
-
- @Mock private Button mConfirmButton;
- @Mock private Button mTryAgainButton;
-
- @Mock private TextView mErrorView;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mFaceView = new TestableFaceView(mContext);
- mFaceView.mFaceIconController = mock(TestableFaceView.TestableIconController.class);
- mFaceView.setCallback(mCallback);
-
- mFaceView.mNegativeButton = mNegativeButton;
- mFaceView.mCancelButton = mCancelButton;
- mFaceView.mUseCredentialButton = mUseCredentialButton;
-
- mFaceView.mConfirmButton = mConfirmButton;
- mFaceView.mTryAgainButton = mTryAgainButton;
-
- mFaceView.mIndicatorView = mErrorView;
- }
-
- @Test
- public void testStateUpdated_whenDialogAnimatedIn() {
- mFaceView.onDialogAnimatedIn();
- verify(mFaceView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
- }
-
- @Test
- public void testIconUpdatesState_whenDialogStateUpdated() {
- mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATING);
- verify(mFaceView.mFaceIconController)
- .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
-
- mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
- verify(mFaceView.mFaceIconController).updateState(
- eq(AuthBiometricFaceView.STATE_AUTHENTICATING),
- eq(AuthBiometricFaceView.STATE_AUTHENTICATED));
- }
-
- public class TestableFaceView extends AuthBiometricFaceView {
-
- public class TestableIconController extends IconController {
- TestableIconController(Context context, ImageView iconView) {
- super(context, iconView, mock(TextView.class));
- }
-
- public void startPulsing() {
- // Stub for testing
- }
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return 0; // Keep this at 0 for tests to invoke callback immediately.
- }
-
- public TestableFaceView(Context context) {
- super(context);
- }
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index f8e38e4..9418b50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -20,137 +20,109 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
+import static com.android.systemui.biometrics.AuthBiometricView.Callback.ACTION_AUTHENTICATED;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import android.content.Context;
-import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.PromptInfo;
import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-import android.util.AttributeSet;
+import android.testing.ViewUtils;
+import android.view.LayoutInflater;
import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+@Ignore
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
public class AuthBiometricViewTest extends SysuiTestCase {
- @Mock private AuthBiometricView.Callback mCallback;
- @Mock private AuthPanelController mPanelController;
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
- @Mock private Button mNegativeButton;
- @Mock private Button mCancelButton;
- @Mock private Button mUseCredentialButton;
+ @Mock
+ private AuthBiometricView.Callback mCallback;
+ @Mock
+ private AuthPanelController mPanelController;
- @Mock private Button mPositiveButton;
- @Mock private Button mTryAgainButton;
-
- @Mock private TextView mTitleView;
- @Mock private TextView mSubtitleView;
- @Mock private TextView mDescriptionView;
- @Mock private TextView mIndicatorView;
- @Mock private ImageView mIconView;
- @Mock private View mIconHolderView;
-
- private TestableBiometricView mBiometricView;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
+ private AuthBiometricView mBiometricView;
@Test
public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
// The onAuthenticated runnable is posted when authentication succeeds.
- mBiometricView.onAuthenticationSucceeded();
+ mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
waitForIdleSync();
assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
- verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+ verify(mCallback).onAction(ACTION_AUTHENTICATED);
}
@Test
public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
- final Button negativeButton = new Button(mContext);
- final Button cancelButton = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getNegativeButton() {
- return negativeButton;
- }
-
- @Override
- public Button getCancelButton() {
- return cancelButton;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.setRequireConfirmation(true);
- mBiometricView.onAuthenticationSucceeded();
+ mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
waitForIdleSync();
- assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
- verify(mCallback, never()).onAction(anyInt());
- assertEquals(View.GONE, negativeButton.getVisibility());
- assertEquals(View.VISIBLE, cancelButton.getVisibility());
- assertTrue(cancelButton.isEnabled());
+ // TODO: this should be tested in the subclasses
+ if (mBiometricView.supportsRequireConfirmation()) {
+ assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
- verify(mBiometricView.mConfirmButton).setEnabled(eq(true));
- verify(mIndicatorView).setText(eq(R.string.biometric_dialog_tap_confirm));
- verify(mIndicatorView).setVisibility(eq(View.VISIBLE));
+ verify(mCallback, never()).onAction(anyInt());
+
+ assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
+ assertEquals(View.VISIBLE, mBiometricView.mCancelButton.getVisibility());
+ assertTrue(mBiometricView.mCancelButton.isEnabled());
+
+ assertTrue(mBiometricView.mConfirmButton.isEnabled());
+ assertEquals(mContext.getText(R.string.biometric_dialog_tap_confirm),
+ mBiometricView.mIndicatorView.getText());
+ assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
+ } else {
+ assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
+ verify(mCallback).onAction(eq(ACTION_AUTHENTICATED));
+ }
+
}
@Test
public void testPositiveButton_sendsActionAuthenticated() {
- Button button = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getConfirmButton() {
- return button;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback);
- button.performClick();
+ mBiometricView.mConfirmButton.performClick();
waitForIdleSync();
- verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+ verify(mCallback).onAction(ACTION_AUTHENTICATED);
assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
}
@Test
public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
- Button button = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getNegativeButton() {
- return button;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.onDialogAnimatedIn();
- button.performClick();
+ mBiometricView.mNegativeButton.performClick();
waitForIdleSync();
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
@@ -158,25 +130,14 @@
@Test
public void testCancelButton_whenPendingConfirmation_sendsActionUserCanceled() {
- Button cancelButton = new Button(mContext);
- Button negativeButton = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getNegativeButton() {
- return negativeButton;
- }
- @Override
- public Button getCancelButton() {
- return cancelButton;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.setRequireConfirmation(true);
- mBiometricView.onAuthenticationSucceeded();
+ mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
- assertEquals(View.GONE, negativeButton.getVisibility());
+ assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
- cancelButton.performClick();
+ mBiometricView.mCancelButton.performClick();
waitForIdleSync();
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED);
@@ -184,15 +145,9 @@
@Test
public void testTryAgainButton_sendsActionTryAgain() {
- Button button = new Button(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getTryAgainButton() {
- return button;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback);
- button.performClick();
+ mBiometricView.mTryAgainButton.performClick();
waitForIdleSync();
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
@@ -202,7 +157,7 @@
@Test
@Ignore("flaky, b/189031816")
public void testError_sendsActionError() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
final String testError = "testError";
mBiometricView.onError(TYPE_FACE, testError);
waitForIdleSync();
@@ -213,7 +168,7 @@
@Test
public void testBackgroundClicked_sendsActionUserCanceled() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
View view = new View(mContext);
mBiometricView.setBackgroundView(view);
@@ -223,18 +178,18 @@
@Test
public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
View view = new View(mContext);
mBiometricView.setBackgroundView(view);
- mBiometricView.onAuthenticationSucceeded();
+ mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
view.performClick();
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
}
@Test
public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.mLayoutParams = new AuthDialog.LayoutParams(0, 0);
mBiometricView.updateSize(AuthDialog.SIZE_SMALL);
@@ -246,7 +201,7 @@
@Test
public void testIgnoresUselessHelp() {
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+ initDialog(false /* allowDeviceCredential */, mCallback);
mBiometricView.onDialogAnimatedIn();
waitForIdleSync();
@@ -256,33 +211,16 @@
mBiometricView.onHelp(TYPE_FINGERPRINT, "");
waitForIdleSync();
- verify(mIndicatorView, never()).setText(any());
+ assertEquals("", mBiometricView.mIndicatorView.getText());
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR));
assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
}
@Test
public void testRestoresState() {
- final boolean requireConfirmation = true; // set/init from AuthController
+ final boolean requireConfirmation = true;
- Button tryAgainButton = new Button(mContext);
- TextView indicatorView = new TextView(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getTryAgainButton() {
- return tryAgainButton;
- }
- @Override
- public TextView getIndicatorView() {
- return indicatorView;
- }
-
- @Override
- public int getDelayAfterError() {
- // keep a real delay to test saving in the error state
- return BiometricPrompt.HIDE_DIALOG_DELAY;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback, null, 10000);
final String failureMessage = "testFailureMessage";
mBiometricView.setRequireConfirmation(requireConfirmation);
@@ -292,8 +230,8 @@
Bundle state = new Bundle();
mBiometricView.onSaveState(state);
- assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
- assertEquals(View.VISIBLE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
+ assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility());
+ assertEquals(View.GONE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
assertEquals(AuthBiometricView.STATE_ERROR, state.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
@@ -307,25 +245,12 @@
// TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
// Create new dialog and restore the previous state into it
- Button tryAgainButton2 = new Button(mContext);
- TextView indicatorView2 = new TextView(mContext);
- initDialog(mContext, false /* allowDeviceCredential */, mCallback, state,
- new MockInjector() {
- @Override
- public Button getTryAgainButton() {
- return tryAgainButton2;
- }
-
- @Override
- public TextView getIndicatorView() {
- return indicatorView2;
- }
- });
+ initDialog(false /* allowDeviceCredential */, mCallback, state, 10000);
+ mBiometricView.mAnimationDurationHideDialog = 10000;
mBiometricView.setRequireConfirmation(requireConfirmation);
waitForIdleSync();
- // Test restored state
- assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
+ assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility());
assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
@@ -334,23 +259,12 @@
}
@Test
- public void testCredentialButton_whenDeviceCredentialAllowed() {
- final Button negativeButton = new Button(mContext);
- final Button useCredentialButton = new Button(mContext);
- initDialog(mContext, true /* allowDeviceCredential */, mCallback, new MockInjector() {
- @Override
- public Button getNegativeButton() {
- return negativeButton;
- }
+ public void testCredentialButton_whenDeviceCredentialAllowed() throws InterruptedException {
+ initDialog(true /* allowDeviceCredential */, mCallback);
- @Override
- public Button getUseCredentialButton() {
- return useCredentialButton;
- }
- });
-
- assertEquals(View.GONE, negativeButton.getVisibility());
- useCredentialButton.performClick();
+ assertEquals(View.VISIBLE, mBiometricView.mUseCredentialButton.getVisibility());
+ assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
+ mBiometricView.mUseCredentialButton.performClick();
waitForIdleSync();
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
@@ -369,120 +283,30 @@
return promptInfo;
}
- private void initDialog(Context context, boolean allowDeviceCredential,
- AuthBiometricView.Callback callback,
- Bundle savedState, MockInjector injector) {
- mBiometricView = new TestableBiometricView(context, null, injector);
+ private void initDialog(boolean allowDeviceCredential, AuthBiometricView.Callback callback) {
+ initDialog(allowDeviceCredential, callback,
+ null /* savedState */, 0 /* hideDelay */);
+ }
+
+ private void initDialog(boolean allowDeviceCredential,
+ AuthBiometricView.Callback callback, Bundle savedState, int hideDelay) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ mBiometricView = (AuthBiometricView) inflater.inflate(
+ R.layout.auth_biometric_view, null, false);
+ mBiometricView.mAnimationDurationLong = 0;
+ mBiometricView.mAnimationDurationShort = 0;
+ mBiometricView.mAnimationDurationHideDialog = hideDelay;
mBiometricView.setPromptInfo(buildPromptInfo(allowDeviceCredential));
mBiometricView.setCallback(callback);
mBiometricView.restoreState(savedState);
- mBiometricView.onFinishInflateInternal();
- mBiometricView.onAttachedToWindowInternal();
-
+ ViewUtils.attachView(mBiometricView);
mBiometricView.setPanelController(mPanelController);
+ waitForIdleSync();
}
- private void initDialog(Context context, boolean allowDeviceCredential,
- AuthBiometricView.Callback callback, MockInjector injector) {
- initDialog(context, allowDeviceCredential, callback, null /* savedState */, injector);
- }
-
- private class MockInjector extends AuthBiometricView.Injector {
- @Override
- public Button getNegativeButton() {
- return mNegativeButton;
- }
-
- @Override
- public Button getCancelButton() {
- return mCancelButton;
- }
-
- @Override
- public Button getUseCredentialButton() {
- return mUseCredentialButton;
- }
-
- @Override
- public Button getConfirmButton() {
- return mPositiveButton;
- }
-
- @Override
- public Button getTryAgainButton() {
- return mTryAgainButton;
- }
-
- @Override
- public TextView getTitleView() {
- return mTitleView;
- }
-
- @Override
- public TextView getSubtitleView() {
- return mSubtitleView;
- }
-
- @Override
- public TextView getDescriptionView() {
- return mDescriptionView;
- }
-
- @Override
- public TextView getIndicatorView() {
- return mIndicatorView;
- }
-
- @Override
- public ImageView getIconView() {
- return mIconView;
- }
-
- @Override
- public View getIconHolderView() {
- return mIconHolderView;
- }
-
- @Override
- public int getDelayAfterError() {
- return 0; // Keep this at 0 for tests to invoke callback immediately.
- }
-
- @Override
- public int getMediumToLargeAnimationDurationMs() {
- return 0;
- }
- }
-
- private class TestableBiometricView extends AuthBiometricView {
- TestableBiometricView(Context context, AttributeSet attrs,
- Injector injector) {
- super(context, attrs, injector);
- }
-
- @Override
- protected int getDelayAfterAuthenticatedDurationMs() {
- return 0; // Keep this at 0 for tests to invoke callback immediately.
- }
-
- @Override
- protected int getStateForAfterError() {
- return 0;
- }
-
- @Override
- protected void handleResetAfterError() {
-
- }
-
- @Override
- protected void handleResetAfterHelp() {
-
- }
-
- @Override
- protected boolean supportsSmallDialog() {
- return false;
- }
+ @Override
+ protected void waitForIdleSync() {
+ TestableLooper.get(this).processAllMessages();
+ super.waitForIdleSync();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
deleted file mode 100644
index ae1268d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2019 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.biometrics;
-
-import static android.hardware.biometrics.BiometricManager.Authenticators;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.PromptInfo;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.IBinder;
-import android.os.UserManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ScrollView;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class AuthContainerViewTest extends SysuiTestCase {
-
- private TestableAuthContainer mAuthContainer;
-
- private @Mock AuthDialogCallback mCallback;
- private @Mock UserManager mUserManager;
- private @Mock WakefulnessLifecycle mWakefulnessLifecycle;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testActionAuthenticated_sendsDismissedAuthenticated() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_AUTHENTICATED);
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testActionUserCanceled_sendsDismissedUserCanceled() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_USER_CANCELED);
- verify(mCallback).onSystemEvent(eq(
- BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL));
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testActionButtonNegative_sendsDismissedButtonNegative() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testActionTryAgain_sendsTryAgain() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
- verify(mCallback).onTryAgainPressed();
- }
-
- @Test
- public void testActionError_sendsDismissedError() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_ERROR);
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_ERROR),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
- initializeContainer(
- Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
-
- mAuthContainer.mBiometricCallback.onAction(
- AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
- verify(mCallback).onDeviceCredentialPressed();
-
- // Credential view is attached to the frame layout
- waitForIdleSync();
- assertNotNull(mAuthContainer.mCredentialView);
- verify(mAuthContainer.mFrameLayout).addView(eq(mAuthContainer.mCredentialView));
- }
-
- @Test
- public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
- initializeContainer(
- Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
-
- mAuthContainer.mBiometricView = mock(AuthBiometricView.class);
- mAuthContainer.animateToCredentialUI();
- verify(mAuthContainer.mBiometricView).startTransitionToCredentialUI();
- }
-
- @Test
- public void testShowBiometricUI() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
-
- assertNotEquals(null, mAuthContainer.mBiometricView);
-
- mAuthContainer.onAttachedToWindowInternal();
- verify(mAuthContainer.mBiometricScrollView).addView(mAuthContainer.mBiometricView);
- // Credential view is not added
- verify(mAuthContainer.mFrameLayout, never()).addView(any());
- }
-
- @Test
- public void testShowCredentialUI_doesNotInflateBiometricUI() {
- initializeContainer(Authenticators.DEVICE_CREDENTIAL);
-
- mAuthContainer.onAttachedToWindowInternal();
-
- assertNull(null, mAuthContainer.mBiometricView);
- assertNotNull(mAuthContainer.mCredentialView);
- verify(mAuthContainer.mFrameLayout).addView(mAuthContainer.mCredentialView);
- }
-
- @Test
- public void testCredentialViewUsesEffectiveUserId() {
- final int dummyEffectiveUserId = 200;
- when(mUserManager.getCredentialOwnerProfile(anyInt())).thenReturn(dummyEffectiveUserId);
-
- initializeContainer(Authenticators.DEVICE_CREDENTIAL);
- mAuthContainer.onAttachedToWindowInternal();
- assertTrue(mAuthContainer.mCredentialView instanceof AuthCredentialPatternView);
- assertEquals(dummyEffectiveUserId, mAuthContainer.mCredentialView.mEffectiveUserId);
- assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType);
- }
-
- @Test
- public void testCredentialUI_disablesClickingOnBackground() {
- // In the credential view, clicking on the background (to cancel authentication) is not
- // valid. Thus, the listener should be null, and it should not be in the accessibility
- // hierarchy.
- initializeContainer(Authenticators.DEVICE_CREDENTIAL);
-
- mAuthContainer.onAttachedToWindowInternal();
-
- verify(mAuthContainer.mBackgroundView).setOnClickListener(eq(null));
- verify(mAuthContainer.mBackgroundView).setImportantForAccessibility(
- eq(View.IMPORTANT_FOR_ACCESSIBILITY_NO));
- }
-
- @Test
- public void testOnDialogAnimatedIn_sendsCancelReason_whenPendingDismiss() {
- initializeContainer(Authenticators.BIOMETRIC_WEAK);
- mAuthContainer.mContainerState = AuthContainerView.STATE_PENDING_DISMISS;
- mAuthContainer.onDialogAnimatedIn();
- verify(mCallback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
- eq(null) /* credentialAttestation */);
- }
-
- @Test
- public void testLayoutParams_hasSecureWindowFlag() {
- final IBinder windowToken = mock(IBinder.class);
- final WindowManager.LayoutParams layoutParams =
- AuthContainerView.getLayoutParams(windowToken, "");
- assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0);
- }
-
- @Test
- public void testLayoutParams_excludesImeInsets() {
- final IBinder windowToken = mock(IBinder.class);
- final WindowManager.LayoutParams layoutParams =
- AuthContainerView.getLayoutParams(windowToken, "");
- assertTrue((layoutParams.getFitInsetsTypes() & WindowInsets.Type.ime()) == 0);
- }
-
- private void initializeContainer(int authenticators) {
- AuthContainerView.Config config = new AuthContainerView.Config();
- config.mContext = mContext;
- config.mCallback = mCallback;
- config.mSensorIds = new int[] {0};
- config.mCredentialAllowed = false;
-
- PromptInfo promptInfo = new PromptInfo();
- promptInfo.setAuthenticators(authenticators);
- config.mPromptInfo = promptInfo;
-
- final List<FingerprintSensorPropertiesInternal> fpProps = new ArrayList<>();
-
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */));
- componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */));
-
- fpProps.add(new FingerprintSensorPropertiesInternal(0,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- componentInfo,
- FingerprintSensorProperties.TYPE_REAR,
- false /* resetLockoutRequiresHardwareAuthToken */));
- mAuthContainer = new TestableAuthContainer(config, fpProps, null /* faceProps */,
- mWakefulnessLifecycle);
- }
-
- private class TestableAuthContainer extends AuthContainerView {
- TestableAuthContainer(AuthContainerView.Config config,
- @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
- @Nullable List<FaceSensorPropertiesInternal> faceProps,
- WakefulnessLifecycle wakefulnessLifecycle) {
-
- super(config, new MockInjector(), fpProps, faceProps, wakefulnessLifecycle);
- }
-
- @Override
- public void animateAway(int reason) {
- // TODO: Credential attestation should be testable/tested
- mConfig.mCallback.onDismissed(reason, null /* credentialAttestation */);
- }
- }
-
- private final class MockInjector extends AuthContainerView.Injector {
- @Override
- public ScrollView getBiometricScrollView(FrameLayout parent) {
- return mock(ScrollView.class);
- }
-
- @Override
- public FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
- return mock(FrameLayout.class);
- }
-
- @Override
- public AuthPanelController getPanelController(Context context, View view) {
- return mock(AuthPanelController.class);
- }
-
- @Override
- public ImageView getBackgroundView(FrameLayout parent) {
- return mock(ImageView.class);
- }
-
- @Override
- public View getPanelView(FrameLayout parent) {
- return mock(View.class);
- }
-
- @Override
- public int getAnimateCredentialStartDelayMs() {
- return 0;
- }
-
- @Override
- public UserManager getUserManager(Context context) {
- return mUserManager;
- }
-
- @Override
- public @Utils.CredentialType int getCredentialType(Context context, int effectiveUserId) {
- return Utils.CREDENTIAL_PATTERN;
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
new file mode 100644
index 0000000..6f0a8a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.app.admin.DevicePolicyManager
+import android.hardware.biometrics.BiometricConstants
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.SensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.os.Handler
+import android.os.IBinder
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.ScrollView
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
+
+@Ignore
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@SmallTest
+class AuthContainerViewTest : SysuiTestCase() {
+
+ @JvmField @Rule
+ var rule = MockitoJUnit.rule()
+
+ @Mock
+ lateinit var callback: AuthDialogCallback
+ @Mock
+ lateinit var userManager: UserManager
+ @Mock
+ lateinit var lockPatternUtils: LockPatternUtils
+ @Mock
+ lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock
+ lateinit var windowToken: IBinder
+
+ private lateinit var authContainer: TestAuthContainerView
+
+ @Test
+ fun testActionAuthenticated_sendsDismissedAuthenticated() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_AUTHENTICATED
+ )
+ waitForIdleSync()
+
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
+ eq<ByteArray?>(null) /* credentialAttestation */
+ )
+ assertThat(authContainer.parent).isNull()
+ }
+
+ @Test
+ fun testActionUserCanceled_sendsDismissedUserCanceled() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_USER_CANCELED
+ )
+ waitForIdleSync()
+
+ verify(callback).onSystemEvent(
+ eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL)
+ )
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq<ByteArray?>(null) /* credentialAttestation */
+ )
+ assertThat(authContainer.parent).isNull()
+ }
+
+ @Test
+ fun testActionButtonNegative_sendsDismissedButtonNegative() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE
+ )
+ waitForIdleSync()
+
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
+ eq<ByteArray?>(null) /* credentialAttestation */
+ )
+ assertThat(authContainer.parent).isNull()
+ }
+
+ @Test
+ fun testActionTryAgain_sendsTryAgain() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN
+ )
+ waitForIdleSync()
+
+ verify(callback).onTryAgainPressed()
+ }
+
+ @Test
+ fun testActionError_sendsDismissedError() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_ERROR
+ )
+ waitForIdleSync()
+
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_ERROR),
+ eq<ByteArray?>(null) /* credentialAttestation */
+ )
+ assertThat(authContainer.parent).isNull()
+ }
+
+ @Test
+ fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
+ initializeContainer(
+ BiometricManager.Authenticators.BIOMETRIC_WEAK or
+ BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ authContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL
+ )
+ waitForIdleSync()
+
+ verify(callback).onDeviceCredentialPressed()
+ assertThat(authContainer.hasCredentialView()).isTrue()
+ }
+
+ @Test
+ fun testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
+ initializeContainer(
+ BiometricManager.Authenticators.BIOMETRIC_WEAK or
+ BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ authContainer.animateToCredentialUI()
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialView()).isTrue()
+ }
+
+ @Test
+ fun testShowBiometricUI() {
+ initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialView()).isFalse()
+ assertThat(authContainer.hasBiometricPrompt()).isTrue()
+ }
+
+ @Test
+ fun testShowCredentialUI() {
+ initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialView()).isTrue()
+ assertThat(authContainer.hasBiometricPrompt()).isFalse()
+ }
+
+ @Test
+ fun testCredentialViewUsesEffectiveUserId() {
+ whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200)
+ whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200))).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ )
+
+ initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialPatternView()).isTrue()
+ assertThat(authContainer.hasBiometricPrompt()).isFalse()
+ }
+
+ @Test
+ fun testCredentialUI_disablesClickingOnBackground() {
+ whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+ whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ )
+
+ // In the credential view, clicking on the background (to cancel authentication) is not
+ // valid. Thus, the listener should be null, and it should not be in the accessibility
+ // hierarchy.
+ initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialPasswordView()).isTrue()
+ assertThat(authContainer.hasBiometricPrompt()).isFalse()
+ assertThat(
+ authContainer.findViewById<View>(R.id.background)?.isImportantForAccessibility
+ ).isFalse()
+
+ authContainer.findViewById<View>(R.id.background)?.performClick()
+ waitForIdleSync()
+
+ assertThat(authContainer.hasCredentialPasswordView()).isTrue()
+ assertThat(authContainer.hasBiometricPrompt()).isFalse()
+ }
+
+ @Test
+ fun testLayoutParams_hasSecureWindowFlag() {
+ val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0).isTrue()
+ }
+
+ @Test
+ fun testLayoutParams_excludesImeInsets() {
+ val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
+ }
+
+ private fun initializeContainer(authenticators: Int) {
+ val config = AuthContainerView.Config()
+ config.mContext = mContext
+ config.mCallback = callback
+ config.mSensorIds = intArrayOf(0)
+ config.mSkipAnimation = true
+ config.mPromptInfo = PromptInfo()
+ config.mPromptInfo.authenticators = authenticators
+ val componentInfo = listOf(
+ ComponentInfoInternal(
+ "faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */
+ ),
+ ComponentInfoInternal(
+ "matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */
+ )
+ )
+ val fpProps = listOf(
+ FingerprintSensorPropertiesInternal(
+ 0,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FingerprintSensorProperties.TYPE_REAR,
+ false /* resetLockoutRequiresHardwareAuthToken */
+ )
+ )
+ authContainer = TestAuthContainerView(
+ config,
+ fpProps,
+ listOf(),
+ wakefulnessLifecycle,
+ userManager,
+ lockPatternUtils,
+ Handler(TestableLooper.get(this).looper)
+ )
+ ViewUtils.attachView(authContainer)
+ }
+
+ private inner class TestAuthContainerView(
+ config: Config,
+ fpProps: List<FingerprintSensorPropertiesInternal>,
+ faceProps: List<FaceSensorPropertiesInternal>,
+ wakefulnessLifecycle: WakefulnessLifecycle,
+ userManager: UserManager,
+ lockPatternUtils: LockPatternUtils,
+ mainHandler: Handler
+ ) : AuthContainerView(
+ config, fpProps, faceProps,
+ wakefulnessLifecycle, userManager, lockPatternUtils, mainHandler
+ ) {
+ override fun postOnAnimation(runnable: Runnable) {
+ runnable.run()
+ }
+ }
+
+ override fun waitForIdleSync() {
+ TestableLooper.get(this).processAllMessages()
+ super.waitForIdleSync()
+ }
+}
+
+private fun AuthContainerView.hasBiometricPrompt() =
+ (findViewById<ScrollView>(R.id.biometric_scrollview)?.childCount ?: 0) > 0
+
+private fun AuthContainerView.hasCredentialView() =
+ hasCredentialPatternView() || hasCredentialPasswordView()
+
+private fun AuthContainerView.hasCredentialPatternView() =
+ findViewById<View>(R.id.lockPattern) != null
+
+private fun AuthContainerView.hasCredentialPasswordView() =
+ findViewById<View>(R.id.lockPassword) != null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index c37e966..cfac9651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -16,8 +16,9 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
@@ -62,6 +63,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -71,6 +73,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -79,6 +82,8 @@
import com.android.systemui.util.concurrency.FakeExecution;
import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
@@ -86,7 +91,8 @@
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
@@ -94,11 +100,15 @@
import javax.inject.Provider;
+@Ignore
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
public class AuthControllerTest extends SysuiTestCase {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
@Mock
private PackageManager mPackageManager;
@Mock
@@ -128,6 +138,10 @@
@Mock
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
+ private UserManager mUserManager;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ @Mock
private StatusBarStateController mStatusBarStateController;
@Captor
ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
@@ -144,8 +158,6 @@
@Before
public void setup() throws RemoteException {
- MockitoAnnotations.initMocks(this);
-
mContextSpy = spy(mContext);
mExecution = new FakeExecution();
mTestableLooper = TestableLooper.get(this);
@@ -343,8 +355,8 @@
@Test
public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- mAuthController.onBiometricAuthenticated();
- verify(mDialog1).onAuthenticationSucceeded();
+ mAuthController.onBiometricAuthenticated(TYPE_FINGERPRINT);
+ verify(mDialog1).onAuthenticationSucceeded(eq(TYPE_FINGERPRINT));
}
@Test
@@ -528,8 +540,7 @@
doAnswer(invocation -> {
Object[] args = invocation.getArguments();
Bundle savedState = (Bundle) args[0];
- savedState.putInt(
- AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
+ savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
return null; // onSaveState returns void
}).when(mDialog1).onSaveState(any());
@@ -558,8 +569,7 @@
doAnswer(invocation -> {
Object[] args = invocation.getArguments();
Bundle savedState = (Bundle) args[0];
- savedState.putInt(
- AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
+ savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
savedState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, true);
return null; // onSaveState returns void
}).when(mDialog1).onSaveState(any());
@@ -697,7 +707,7 @@
0 /* operationId */,
"testPackage",
1 /* requestId */,
- BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT);
+ BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
}
private PromptInfo createTestPromptInfo() {
@@ -739,15 +749,16 @@
super(context, execution, commandQueue, activityTaskManager, windowManager,
fingerprintManager, faceManager, udfpsControllerFactory,
sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
- statusBarStateController, mHandler);
+ mUserManager, mLockPatternUtils, statusBarStateController, mHandler);
}
@Override
protected AuthDialog buildDialog(PromptInfo promptInfo,
- boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
+ boolean requireConfirmation, int userId, int[] sensorIds,
String opPackageName, boolean skipIntro, long operationId, long requestId,
@BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ WakefulnessLifecycle wakefulnessLifecycle, UserManager userManager,
+ LockPatternUtils lockPatternUtils) {
mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 254fc59..839c0ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -17,10 +17,10 @@
package com.android.systemui.biometrics
import android.animation.Animator
-import android.graphics.Insets
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.content.ComponentName
+import android.graphics.Insets
import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
@@ -65,8 +65,8 @@
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.anyLong
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 066a866..ef82c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -57,9 +57,9 @@
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 2cd470e..3d8d128 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -38,11 +38,11 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.nullable
import org.mockito.Mockito.never
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.nullable
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
private const val DISPLAY_ID = "" // default display id
private const val SENSOR_X = 50
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
index 12096bc..af3f24a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -210,4 +210,25 @@
eq(actionCallbackService))
assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
}
+
+ @Test
+ fun testFalseBindCallsUnbind() {
+ val falseContext = mock(Context::class.java)
+ `when`(falseContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false)
+ val manager = ControlsProviderLifecycleManager(
+ falseContext,
+ executor,
+ actionCallbackService,
+ UserHandle.of(0),
+ componentName
+ )
+ manager.bindService()
+ executor.runAllReady()
+
+ val captor = ArgumentCaptor.forClass(
+ ServiceConnection::class.java
+ )
+ verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any())
+ verify(falseContext).unbindService(captor.value)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 2860b50..b3d5459 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -28,6 +28,7 @@
import android.service.dreams.IDreamOverlay;
import android.service.dreams.IDreamOverlayCallback;
import android.testing.AndroidTestingRunner;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -99,6 +100,9 @@
@Mock
DreamPreviewComplication mPreviewComplication;
+ @Mock
+ ViewGroup mDreamOverlayContainerViewParent;
+
DreamOverlayService mService;
@Before
@@ -152,6 +156,23 @@
}
@Test
+ public void testDreamOverlayContainerViewRemovedFromOldParentWhenInitialized()
+ throws Exception {
+ when(mDreamOverlayContainerView.getParent())
+ .thenReturn(mDreamOverlayContainerViewParent)
+ .thenReturn(null);
+
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ mMainExecutor.runAllReady();
+
+ verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView);
+ }
+
+ @Test
public void testShouldShowComplicationsFalseByDefault() {
mService.onBind(new Intent());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 809b890..adb59ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -55,6 +55,8 @@
private lateinit var appIconDrawable: Drawable
@Mock
+ private lateinit var logger: MediaTttLogger
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var viewUtil: ViewUtil
@@ -69,7 +71,7 @@
fakeExecutor = FakeExecutor(fakeClock)
controllerCommon = TestControllerCommon(
- context, windowManager, viewUtil, fakeExecutor, tapGestureDetector
+ context, logger, windowManager, viewUtil, fakeExecutor, tapGestureDetector
)
}
@@ -94,20 +96,22 @@
@Test
fun displayChip_chipDoesNotDisappearsBeforeTimeout() {
- controllerCommon.displayChip(getState())
+ val state = getState()
+ controllerCommon.displayChip(state)
reset(windowManager)
- fakeClock.advanceTime(TIMEOUT_MILLIS - 1)
+ fakeClock.advanceTime(state.getTimeoutMs() - 1)
verify(windowManager, never()).removeView(any())
}
@Test
fun displayChip_chipDisappearsAfterTimeout() {
- controllerCommon.displayChip(getState())
+ val state = getState()
+ controllerCommon.displayChip(state)
reset(windowManager)
- fakeClock.advanceTime(TIMEOUT_MILLIS + 1)
+ fakeClock.advanceTime(state.getTimeoutMs() + 1)
verify(windowManager).removeView(any())
}
@@ -115,7 +119,8 @@
@Test
fun displayChip_calledAgainBeforeTimeout_timeoutReset() {
// First, display the chip
- controllerCommon.displayChip(getState())
+ val state = getState()
+ controllerCommon.displayChip(state)
// After some time, re-display the chip
val waitTime = 1000L
@@ -123,7 +128,7 @@
controllerCommon.displayChip(getState())
// Wait until the timeout for the first display would've happened
- fakeClock.advanceTime(TIMEOUT_MILLIS - waitTime + 1)
+ fakeClock.advanceTime(state.getTimeoutMs() - waitTime + 1)
// Verify we didn't hide the chip
verify(windowManager, never()).removeView(any())
@@ -132,33 +137,36 @@
@Test
fun displayChip_calledAgainBeforeTimeout_eventuallyTimesOut() {
// First, display the chip
- controllerCommon.displayChip(getState())
+ val state = getState()
+ controllerCommon.displayChip(state)
// After some time, re-display the chip
fakeClock.advanceTime(1000L)
controllerCommon.displayChip(getState())
// Ensure we still hide the chip eventually
- fakeClock.advanceTime(TIMEOUT_MILLIS + 1)
+ fakeClock.advanceTime(state.getTimeoutMs() + 1)
verify(windowManager).removeView(any())
}
@Test
- fun removeChip_chipRemovedAndGestureDetectionStopped() {
+ fun removeChip_chipRemovedAndGestureDetectionStoppedAndRemovalLogged() {
// First, add the chip
controllerCommon.displayChip(getState())
// Then, remove it
- controllerCommon.removeChip()
+ val reason = "test reason"
+ controllerCommon.removeChip(reason)
verify(windowManager).removeView(any())
verify(tapGestureDetector).removeOnGestureDetectedCallback(any())
+ verify(logger).logChipRemoval(reason)
}
@Test
fun removeChip_noAdd_viewNotRemoved() {
- controllerCommon.removeChip()
+ controllerCommon.removeChip("reason")
verify(windowManager, never()).removeView(any())
}
@@ -222,12 +230,19 @@
inner class TestControllerCommon(
context: Context,
+ logger: MediaTttLogger,
windowManager: WindowManager,
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
tapGestureDetector: TapGestureDetector,
) : MediaTttChipControllerCommon<MediaTttChipState>(
- context, windowManager, viewUtil, mainExecutor, tapGestureDetector, R.layout.media_ttt_chip
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ mainExecutor,
+ tapGestureDetector,
+ R.layout.media_ttt_chip
) {
override fun updateChipView(chipState: MediaTttChipState, currentChipView: ViewGroup) {
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
new file mode 100644
index 0000000..d95e5c4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.common
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.LogcatEchoTracker
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.mock
+import java.io.PrintWriter
+import java.io.StringWriter
+
+@SmallTest
+class MediaTttLoggerTest : SysuiTestCase() {
+
+ private lateinit var buffer: LogBuffer
+ private lateinit var logger: MediaTttLogger
+
+ @Before
+ fun setUp () {
+ buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+ .create("buffer", 10)
+ logger = MediaTttLogger(DEVICE_TYPE_TAG, buffer)
+ }
+
+ @Test
+ fun logStateChange_bufferHasDeviceTypeTagAndStateNameAndId() {
+ val stateName = "test state name"
+ val id = "test id"
+
+ logger.logStateChange(stateName, id)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains(DEVICE_TYPE_TAG)
+ assertThat(actualString).contains(stateName)
+ assertThat(actualString).contains(id)
+ }
+
+ @Test
+ fun logChipRemoval_bufferHasDeviceTypeAndReason() {
+ val reason = "test reason"
+ logger.logChipRemoval(reason)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains(DEVICE_TYPE_TAG)
+ assertThat(actualString).contains(reason)
+ }
+}
+
+private const val DEVICE_TYPE_TAG = "TEST TYPE"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 86d4c1b..56f4589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -31,6 +31,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.util.concurrency.FakeExecutor
@@ -60,6 +61,8 @@
@Mock
private lateinit var applicationInfo: ApplicationInfo
@Mock
+ private lateinit var logger: MediaTttLogger
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var viewUtil: ViewUtil
@@ -83,6 +86,7 @@
controllerReceiver = MediaTttChipControllerReceiver(
commandQueue,
context,
+ logger,
windowManager,
viewUtil,
FakeExecutor(FakeSystemClock()),
@@ -142,6 +146,18 @@
}
@Test
+ fun receivesNewStateFromCommandQueue_isLogged() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+ routeInfo,
+ null,
+ null
+ )
+
+ verify(logger).logStateChange(any(), any())
+ }
+
+ @Test
fun displayChip_nullAppIconDrawable_iconIsFromPackageName() {
val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, "appName")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 88888f0..fd1d76a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -32,6 +32,7 @@
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.util.concurrency.FakeExecutor
@@ -62,6 +63,8 @@
@Mock
private lateinit var applicationInfo: ApplicationInfo
@Mock
+ private lateinit var logger: MediaTttLogger
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var viewUtil: ViewUtil
@@ -69,6 +72,8 @@
private lateinit var commandQueue: CommandQueue
private lateinit var commandQueueCallback: CommandQueue.Callbacks
private lateinit var fakeAppIconDrawable: Drawable
+ private lateinit var fakeClock: FakeSystemClock
+ private lateinit var fakeExecutor: FakeExecutor
@Before
fun setUp() {
@@ -82,12 +87,16 @@
)).thenReturn(applicationInfo)
context.setMockPackageManager(packageManager)
+ fakeClock = FakeSystemClock()
+ fakeExecutor = FakeExecutor(fakeClock)
+
controllerSender = MediaTttChipControllerSender(
commandQueue,
context,
+ logger,
windowManager,
viewUtil,
- FakeExecutor(FakeSystemClock()),
+ fakeExecutor,
TapGestureDetector(context)
)
@@ -223,6 +232,17 @@
}
@Test
+ fun receivesNewStateFromCommandQueue_isLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logStateChange(any(), any())
+ }
+
+ @Test
fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
val state = almostCloseToStartCast()
controllerSender.displayChip(state)
@@ -460,6 +480,52 @@
assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE)
}
+ @Test
+ fun transferToReceiverTriggeredThenRemoveChip_chipStillDisplayed() {
+ controllerSender.displayChip(transferToReceiverTriggered())
+ fakeClock.advanceTime(1000L)
+
+ controllerSender.removeChip("fakeRemovalReason")
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ }
+
+ @Test
+ fun transferToReceiverTriggeredThenFarFromReceiver_eventuallyTimesOut() {
+ val state = transferToReceiverTriggered()
+ controllerSender.displayChip(state)
+ fakeClock.advanceTime(1000L)
+ controllerSender.removeChip("fakeRemovalReason")
+
+ fakeClock.advanceTime(state.getTimeoutMs() + 1)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToThisDeviceTriggeredThenRemoveChip_chipStillDisplayed() {
+ controllerSender.displayChip(transferToThisDeviceTriggered())
+ fakeClock.advanceTime(1000L)
+
+ controllerSender.removeChip("fakeRemovalReason")
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ }
+
+ @Test
+ fun transferToThisDeviceTriggeredThenFarFromReceiver_eventuallyTimesOut() {
+ val state = transferToThisDeviceTriggered()
+ controllerSender.displayChip(state)
+ fakeClock.advanceTime(1000L)
+ controllerSender.removeChip("fakeRemovalReason")
+
+ fakeClock.advanceTime(state.getTimeoutMs() + 1)
+
+ verify(windowManager).removeView(any())
+ }
+
private fun LinearLayout.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
private fun LinearLayout.getChipText(): String =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index b559d18..04b50d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -34,6 +35,7 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
@@ -56,7 +58,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mockito;
+import org.mockito.ArgumentCaptor;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -64,10 +66,10 @@
private static final int TEST_FAIL_TIMEOUT = 5000;
private final PackageManagerAdapter mMockPackageManagerAdapter =
- Mockito.mock(PackageManagerAdapter.class);
+ mock(PackageManagerAdapter.class);
private final BroadcastDispatcher mMockBroadcastDispatcher =
- Mockito.mock(BroadcastDispatcher.class);
- private final IQSTileService.Stub mMockTileService = Mockito.mock(IQSTileService.Stub.class);
+ mock(BroadcastDispatcher.class);
+ private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class);
private ComponentName mTileServiceComponentName;
private Intent mTileServiceIntent;
private UserHandle mUser;
@@ -95,7 +97,7 @@
mThread.start();
mHandler = Handler.createAsync(mThread.getLooper());
mStateManager = new TileLifecycleManager(mHandler, mWrappedContext,
- Mockito.mock(IQSService.class),
+ mock(IQSService.class),
mMockPackageManagerAdapter,
mMockBroadcastDispatcher,
mTileServiceIntent,
@@ -269,6 +271,25 @@
assertTrue(mStateManager.isToggleableTile());
}
+ @Test
+ public void testFalseBindCallsUnbind() {
+ Context falseContext = mock(Context.class);
+ when(falseContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false);
+ TileLifecycleManager manager = new TileLifecycleManager(mHandler, falseContext,
+ mock(IQSService.class),
+ mMockPackageManagerAdapter,
+ mMockBroadcastDispatcher,
+ mTileServiceIntent,
+ mUser);
+
+ manager.setBindService(true);
+
+ ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any());
+
+ verify(falseContext).unbindService(captor.getValue());
+ }
+
private static class TestContextWrapper extends ContextWrapper {
private IntentFilter mLastIntentFilter;
private int mLastFlag;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 6c29ecc..11f76a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -448,9 +448,10 @@
@Test
public void testOnBiometricAuthenticated() {
- mCommandQueue.onBiometricAuthenticated();
+ final int id = 12;
+ mCommandQueue.onBiometricAuthenticated(id);
waitForIdleSync();
- verify(mCallbacks).onBiometricAuthenticated();
+ verify(mCallbacks).onBiometricAuthenticated(eq(id));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 466d954..3c1a73e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -45,7 +45,6 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,15 +65,17 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.ViewGroup;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.app.IBatteryStats;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -106,7 +107,8 @@
import java.util.Collections;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
public class KeyguardIndicationControllerTest extends SysuiTestCase {
private static final String ORGANIZATION_NAME = "organization";
@@ -164,11 +166,15 @@
@Captor
private ArgumentCaptor<KeyguardIndication> mKeyguardIndicationCaptor;
@Captor
+ private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
+ @Captor
private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
private KeyguardStateController.Callback mKeyguardStateControllerCallback;
+ private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
private StatusBarStateController.StateListener mStatusBarStateListener;
private BroadcastReceiver mBroadcastReceiver;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private TestableLooper mTestableLooper;
private KeyguardIndicationTextView mTextView; // AOD text
@@ -181,6 +187,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mTestableLooper = TestableLooper.get(this);
mTextView = new KeyguardIndicationTextView(mContext);
mTextView.setAnimationsEnabled(false);
@@ -226,7 +233,10 @@
Looper.prepare();
}
- mController = new KeyguardIndicationController(mContext, mWakeLockBuilder,
+ mController = new KeyguardIndicationController(
+ mContext,
+ mTestableLooper.getLooper(),
+ mWakeLockBuilder,
mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils,
@@ -245,6 +255,10 @@
mKeyguardStateControllerCallbackCaptor.capture());
mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ mKeyguardUpdateMonitorCallbackCaptor.capture());
+ mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+
mExecutor.runAllReady();
reset(mRotateTextViewController);
}
@@ -267,7 +281,7 @@
mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
});
mInstrumentation.waitForIdleSync();
-
+ mTestableLooper.processAllMessages();
verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
mContext.getResources().getString(R.string.dock_alignment_slow_charging));
@@ -285,6 +299,7 @@
mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
});
mInstrumentation.waitForIdleSync();
+ mTestableLooper.processAllMessages();
verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
mContext.getResources().getString(R.string.dock_alignment_not_charging));
@@ -303,6 +318,7 @@
mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
});
mInstrumentation.waitForIdleSync();
+ mTestableLooper.processAllMessages();
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(R.string.dock_alignment_slow_charging));
@@ -321,6 +337,7 @@
mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
});
mInstrumentation.waitForIdleSync();
+ mTestableLooper.processAllMessages();
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(R.string.dock_alignment_not_charging));
@@ -331,9 +348,12 @@
@Test
public void disclosure_unmanaged() {
createController();
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false);
+ reset(mRotateTextViewController);
+
sendUpdateDisclosureBroadcast();
mExecutor.runAllReady();
@@ -347,6 +367,7 @@
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
sendUpdateDisclosureBroadcast();
+ mController.setVisible(true);
mExecutor.runAllReady();
verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
@@ -355,6 +376,7 @@
@Test
public void disclosure_orgOwnedDeviceWithManagedProfile_noOrganizationName() {
createController();
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
@@ -369,6 +391,7 @@
@Test
public void disclosure_deviceOwner_withOrganizationName() {
createController();
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
@@ -381,6 +404,7 @@
@Test
public void disclosure_orgOwnedDeviceWithManagedProfile_withOrganizationName() {
createController();
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
@@ -397,6 +421,7 @@
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
createController();
+ mController.setVisible(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
@@ -424,7 +449,7 @@
@Test
public void disclosure_deviceOwner_financedDeviceWithOrganizationName() {
createController();
-
+ mController.setVisible(true);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
@@ -432,6 +457,7 @@
.thenReturn(DEVICE_OWNER_TYPE_FINANCED);
sendUpdateDisclosureBroadcast();
mExecutor.runAllReady();
+ mController.setVisible(true);
verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mFinancedDisclosureWithOrganization);
}
@@ -469,10 +495,10 @@
@Test
public void transientIndication_visibleWhenDozing() {
createController();
-
mController.setVisible(true);
- mController.showTransientIndication(TEST_STRING_RES);
+
mStatusBarStateListener.onDozingChanged(true);
+ mController.showTransientIndication(TEST_STRING_RES);
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(TEST_STRING_RES));
@@ -493,7 +519,7 @@
reset(mRotateTextViewController);
mStatusBarStateListener.onDozingChanged(true);
- verifyHideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+ assertThat(mTextView.getText()).isNotEqualTo(message);
}
@Test
@@ -604,10 +630,9 @@
}
@Test
- public void updateMonitor_listener() {
+ public void registersKeyguardStateCallback() {
createController();
verify(mKeyguardStateController).addCallback(any());
- verify(mKeyguardUpdateMonitor, times(2)).registerCallback(any());
}
@Test
@@ -695,13 +720,13 @@
@Test
public void onRefreshBatteryInfo_dozing_dischargingWithOverheat_presentBatteryPercentage() {
createController();
+ mController.setVisible(true);
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
90 /* level */, 0 /* plugged */, BatteryManager.BATTERY_HEALTH_OVERHEAT,
0 /* maxChargingWattage */, true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
mStatusBarStateListener.onDozingChanged(true);
- mController.setVisible(true);
String percentage = NumberFormat.getPercentInstance().format(90 / 100f);
assertThat(mTextView.getText()).isEqualTo(percentage);
@@ -710,9 +735,9 @@
@Test
public void onRequireUnlockForNfc_showsRequireUnlockForNfcIndication() {
createController();
+ mController.setVisible(true);
String message = mContext.getString(R.string.require_unlock_for_nfc);
mController.getKeyguardCallback().onRequireUnlockForNfc();
- mController.setVisible(true);
verifyTransientMessage(message);
}
@@ -778,6 +803,9 @@
@Test
public void testOnKeyguardShowingChanged_showing_updatesPersistentMessages() {
createController();
+ mController.setVisible(true);
+ mExecutor.runAllReady();
+ reset(mRotateTextViewController);
// GIVEN keyguard is showing
when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -799,6 +827,8 @@
@Test
public void onTrustGrantedMessageDoesNotShowUntilTrustGranted() {
createController();
+ mController.setVisible(true);
+ reset(mRotateTextViewController);
// GIVEN a trust granted message but trust isn't granted
final String trustGrantedMsg = "testing trust granted message";
@@ -808,7 +838,7 @@
// WHEN trust is granted
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
- mController.setVisible(true);
+ mKeyguardUpdateMonitorCallback.onTrustChanged(KeyguardUpdateMonitor.getCurrentUser());
// THEN verify the trust granted message shows
verifyIndicationMessage(
@@ -819,6 +849,7 @@
@Test
public void onTrustGrantedMessageDoesShowsOnTrustGranted() {
createController();
+ mController.setVisible(true);
// GIVEN trust is granted
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 077b41a..5f2bbd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -126,12 +126,6 @@
setAodEnabledForTest(true);
setShouldControlUnlockedScreenOffForTest(true);
setDisplayNeedsBlankingForTest(false);
-
- // Default to false here (with one test to make sure that when it returns true, we respect
- // that). We'll test the specific conditions for this to return true/false in the
- // UnlockedScreenOffAnimationController's tests.
- when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation())
- .thenReturn(false);
}
@Test
@@ -180,12 +174,9 @@
*/
@Test
public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() {
- mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true);
-
// If AOD is disabled, we shouldn't want to control screen off. Also, let's double check
// that when that value is updated, we called through to PowerManager.
setAodEnabledForTest(false);
-
assertFalse(mDozeParameters.shouldControlScreenOff());
assertTrue(mPowerManagerDozeAfterScreenOff);
@@ -197,6 +188,7 @@
@Test
public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
+ setShouldControlUnlockedScreenOffForTest(true);
when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(false);
assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 0936b77..050563a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -31,7 +31,6 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.GlobalSettings
-import junit.framework.Assert.assertFalse
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -134,7 +133,7 @@
*/
@Test
fun testAodUiShownIfNotInteractive() {
- `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
+ `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
`when`(powerManager.isInteractive).thenReturn(false)
val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -157,7 +156,7 @@
*/
@Test
fun testAodUiNotShownIfInteractive() {
- `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
+ `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
`when`(powerManager.isInteractive).thenReturn(true)
val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -168,13 +167,4 @@
verify(notificationPanelViewController, never()).showAodUi()
}
-
- @Test
- fun testNoAnimationPlaying_dozeParamsCanNotControlScreenOff() {
- `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(false)
-
- assertFalse(controller.shouldPlayUnlockedScreenOffAnimation())
- controller.startAnimation()
- assertFalse(controller.isAnimationPlaying())
- }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 9f9cb40..12cfb32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -14,9 +14,12 @@
package com.android.systemui.statusbar.policy;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -24,6 +27,7 @@
import android.app.AppOpsManager;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.location.LocationManager;
import android.os.Handler;
@@ -67,6 +71,7 @@
private DeviceConfigProxy mDeviceConfigProxy;
private UiEventLoggerFake mUiEventLogger;
+ @Mock private PackageManager mPackageManager;
@Mock private AppOpsController mAppOpsController;
@Mock private UserTracker mUserTracker;
@Mock private SecureSettings mSecureSettings;
@@ -82,6 +87,7 @@
mUiEventLogger = new UiEventLoggerFake();
mTestableLooper = TestableLooper.get(this);
+
mLocationController = new LocationControllerImpl(mContext,
mAppOpsController,
mDeviceConfigProxy,
@@ -90,7 +96,7 @@
mock(BroadcastDispatcher.class),
mock(BootCompleteCache.class),
mUserTracker,
- mContext.getPackageManager(),
+ mPackageManager,
mUiEventLogger,
mSecureSettings);
@@ -178,6 +184,8 @@
@Test
public void testCallbackNotified_additionalOps() {
+ // Return -1 for non system apps
+ when(mPackageManager.getPermissionFlags(any(), any(), any())).thenReturn(-1);
LocationChangeCallback callback = mock(LocationChangeCallback.class);
mLocationController.addCallback(callback);
mDeviceConfigProxy.setProperty(
@@ -190,10 +198,10 @@
when(mAppOpsController.getActiveAppOps())
.thenReturn(ImmutableList.of(
new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.googlequicksearchbox",
+ "com.third.party.app",
System.currentTimeMillis())));
mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.googlequicksearchbox", true);
+ "ccom.third.party.app", true);
mTestableLooper.processAllMessages();
@@ -201,14 +209,14 @@
when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.googlequicksearchbox", false);
+ "com.third.party.app", false);
mTestableLooper.processAllMessages();
verify(callback, times(1)).onLocationActiveChanged(false);
when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
mLocationController.onActiveStateChanged(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0,
- "com.google.android.googlequicksearchbox", true);
+ "com.third.party.app", true);
mTestableLooper.processAllMessages();
verify(callback, times(1)).onLocationActiveChanged(true);
}
@@ -227,10 +235,10 @@
when(mAppOpsController.getActiveAppOps())
.thenReturn(ImmutableList.of(
new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.gms",
+ "com.system.app",
System.currentTimeMillis())));
mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.gms", true);
+ "com.system.app", true);
mTestableLooper.processAllMessages();
@@ -258,10 +266,10 @@
when(mAppOpsController.getActiveAppOps())
.thenReturn(ImmutableList.of(
- new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.google.android.gms",
+ new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.system.app",
System.currentTimeMillis())));
mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.gms", true);
+ "com.system.app", true);
mTestableLooper.processAllMessages();
@@ -269,7 +277,7 @@
when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.gms", false);
+ "com.system.app", false);
mTestableLooper.processAllMessages();
verify(callback, times(1)).onLocationActiveChanged(false);
@@ -277,7 +285,7 @@
when(mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0))
.thenReturn(0);
mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.gms", true);
+ "com.system.app", true);
mTestableLooper.processAllMessages();
@@ -287,6 +295,11 @@
@Test
public void testCallbackNotified_verifyMetrics() {
+ // Return -1 for non system apps and 0 for system apps.
+ when(mPackageManager.getPermissionFlags(any(),
+ eq("com.system.app"), any())).thenReturn(0);
+ when(mPackageManager.getPermissionFlags(any(),
+ eq("com.third.party.app"), any())).thenReturn(-1);
LocationChangeCallback callback = mock(LocationChangeCallback.class);
mLocationController.addCallback(callback);
mDeviceConfigProxy.setProperty(
@@ -298,16 +311,16 @@
when(mAppOpsController.getActiveAppOps())
.thenReturn(ImmutableList.of(
- new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.google.android.gms",
+ new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.system.app",
System.currentTimeMillis()),
new AppOpItem(AppOpsManager.OP_COARSE_LOCATION, 0,
- "com.google.android.googlequicksearchbox",
+ "com.third.party.app",
System.currentTimeMillis()),
new AppOpItem(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0,
- "com.google.android.apps.maps",
+ "com.another.developer.app",
System.currentTimeMillis())));
mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.gms", true);
+ "com.system.app", true);
mTestableLooper.processAllMessages();
@@ -328,7 +341,7 @@
when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
- "com.google.android.gms", false);
+ "com.system.app", false);
mTestableLooper.processAllMessages();
verify(callback, times(1)).onLocationActiveChanged(false);
@@ -354,4 +367,4 @@
// No new callbacks
verify(callback).onLocationSettingsChanged(anyBoolean());
}
-}
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0e99265..0ea087d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2749,6 +2749,9 @@
}
private void updateWindowMagnificationConnectionIfNeeded(AccessibilityUserState userState) {
+ if (!mMagnificationController.supportWindowMagnification()) {
+ return;
+ }
final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
|| userState.isDisplayMagnificationEnabledLocked())
&& (userState.getMagnificationCapabilitiesLocked()
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a958209..fa32452 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -303,7 +303,7 @@
public void onImeWindowVisibilityChanged(boolean shown) {
final Message m = PooledLambda.obtainMessage(
FullScreenMagnificationController::notifyImeWindowVisibilityChanged,
- FullScreenMagnificationController.this, shown);
+ FullScreenMagnificationController.this, mDisplayId, shown);
mControllerCtx.getHandler().sendMessage(m);
}
@@ -1215,11 +1215,12 @@
/**
* Notifies that the IME window visibility changed.
*
+ * @param displayId the logical display id
* @param shown {@code true} means the IME window shows on the screen. Otherwise it's
* hidden.
*/
- void notifyImeWindowVisibilityChanged(boolean shown) {
- mMagnificationInfoChangedCallback.onImeWindowVisibilityChanged(shown);
+ void notifyImeWindowVisibilityChanged(int displayId, boolean shown) {
+ mMagnificationInfoChangedCallback.onImeWindowVisibilityChanged(displayId, shown);
}
/**
@@ -1609,17 +1610,19 @@
* Called when the state of the magnification activation is changed.
* It is for the logging data of the magnification activation state.
*
- * @param displayId The logical display id.
+ * @param displayId the logical display id
* @param activated {@code true} if the magnification is activated, otherwise {@code false}.
*/
void onFullScreenMagnificationActivationState(int displayId, boolean activated);
/**
* Called when the IME window visibility changed.
+ *
+ * @param displayId the logical display id
* @param shown {@code true} means the IME window shows on the screen. Otherwise it's
* hidden.
*/
- void onImeWindowVisibilityChanged(boolean shown);
+ void onImeWindowVisibilityChanged(int displayId, boolean shown);
/**
* Called when the magnification spec changed.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 09e82c7..62ba0c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility.magnification;
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
+import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
@@ -38,6 +39,7 @@
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.accessibility.MagnificationAnimationCallback;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
@@ -91,6 +93,8 @@
private FullScreenMagnificationController mFullScreenMagnificationController;
private WindowMagnificationManager mWindowMagnificationMgr;
private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+ /** Whether the platform supports window magnification feature. */
+ private final boolean mSupportWindowMagnification;
@GuardedBy("mLock")
private int mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
@@ -99,7 +103,7 @@
// Track the active user to reset the magnification and get the associated user settings.
private @UserIdInt int mUserId = UserHandle.USER_SYSTEM;
@GuardedBy("mLock")
- private boolean mImeWindowVisible = false;
+ private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
private long mWindowModeEnabledTime = 0;
private long mFullScreenModeEnabledTime = 0;
@@ -129,6 +133,8 @@
mScaleProvider = scaleProvider;
LocalServices.getService(WindowManagerInternal.class)
.getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
+ mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
+ FEATURE_WINDOW_MAGNIFICATION);
}
@VisibleForTesting
@@ -185,6 +191,11 @@
}
}
+ /** Returns {@code true} if the platform supports window magnification feature. */
+ public boolean supportWindowMagnification() {
+ return mSupportWindowMagnification;
+ }
+
/**
* Transitions to the target Magnification mode with current center of the magnification mode
* if it is available.
@@ -377,7 +388,7 @@
setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
mLastActivatedMode = mActivatedMode;
}
- logMagnificationModeWithImeOnIfNeeded();
+ logMagnificationModeWithImeOnIfNeeded(displayId);
disableFullScreenMagnificationIfNeeded(displayId);
} else {
logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
@@ -432,7 +443,7 @@
setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
mLastActivatedMode = mActivatedMode;
}
- logMagnificationModeWithImeOnIfNeeded();
+ logMagnificationModeWithImeOnIfNeeded(displayId);
disableWindowMagnificationIfNeeded(displayId);
} else {
logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
@@ -454,12 +465,12 @@
}
@Override
- public void onImeWindowVisibilityChanged(boolean shown) {
+ public void onImeWindowVisibilityChanged(int displayId, boolean shown) {
synchronized (mLock) {
- mImeWindowVisible = shown;
+ mIsImeVisibleArray.put(displayId, shown);
}
- getWindowMagnificationMgr().onImeWindowVisibilityChanged(shown);
- logMagnificationModeWithImeOnIfNeeded();
+ getWindowMagnificationMgr().onImeWindowVisibilityChanged(displayId, shown);
+ logMagnificationModeWithImeOnIfNeeded(displayId);
}
/**
@@ -575,11 +586,12 @@
}
}
- private void logMagnificationModeWithImeOnIfNeeded() {
+ private void logMagnificationModeWithImeOnIfNeeded(int displayId) {
final int mode;
synchronized (mLock) {
- if (!mImeWindowVisible || mActivatedMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) {
+ if (!mIsImeVisibleArray.get(displayId, false)
+ || mActivatedMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) {
return;
}
mode = mActivatedMode;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 175182c..3e07b09 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -396,6 +396,10 @@
dumpTrackingTypingFocusEnabledState(pw, displayId, config.getMode());
}
+ pw.append(" SupportWindowMagnification="
+ + mController.supportWindowMagnification()).println();
+ pw.append(" WindowMagnificationConnectionState="
+ + mController.getWindowMagnificationMgr().getConnectionState()).println();
}
private int getIdOfLastServiceToMagnify(int mode, int displayId) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
index 25dcc2a..041eece 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -133,6 +133,25 @@
return true;
}
+ boolean moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+ @Nullable MagnificationAnimationCallback callback) {
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".moveWindowMagnifierToPosition",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId
+ + ";positionX=" + positionX + ";positionY=" + positionY);
+ }
+ try {
+ mConnection.moveWindowMagnifierToPosition(displayId, positionX, positionY,
+ transformToRemoteCallback(callback, mTrace));
+ } catch (RemoteException e) {
+ if (DBG) {
+ Slog.e(TAG, "Error calling moveWindowMagnifierToPosition()", e);
+ }
+ return false;
+ }
+ return true;
+ }
+
boolean showMagnificationButton(int displayId, int magnificationMode) {
if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
mTrace.logTrace(TAG + ".showMagnificationButton",
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 89910ea..aeb1112 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -36,8 +36,10 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.MotionEvent;
import android.view.accessibility.IWindowMagnificationConnection;
import android.view.accessibility.IWindowMagnificationConnectionCallback;
@@ -87,6 +89,30 @@
})
public @interface WindowPosition {}
+ /** Window magnification connection is connecting. */
+ private static final int CONNECTING = 0;
+ /** Window magnification connection is connected. */
+ private static final int CONNECTED = 1;
+ /** Window magnification connection is disconnecting. */
+ private static final int DISCONNECTING = 2;
+ /** Window magnification connection is disconnected. */
+ private static final int DISCONNECTED = 3;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CONNECTION_STATE"}, value = {
+ CONNECTING,
+ CONNECTED,
+ DISCONNECTING,
+ DISCONNECTED
+ })
+ private @interface ConnectionState {
+ }
+
+ @ConnectionState
+ private int mConnectionState = DISCONNECTED;
+
+ private static final int WAIT_CONNECTION_TIMEOUT_MILLIS = 100;
+
private final Object mLock;
private final Context mContext;
@VisibleForTesting
@@ -99,6 +125,7 @@
private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>();
// Whether the following typing focus feature for magnification is enabled.
private boolean mMagnificationFollowTypingEnabled = true;
+ private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
private boolean mReceiverRegistered = false;
@VisibleForTesting
@@ -178,7 +205,7 @@
*/
public void setConnection(@Nullable IWindowMagnificationConnection connection) {
if (DBG) {
- Slog.d(TAG, "setConnection :" + connection);
+ Slog.d(TAG, "setConnection :" + connection + " ,mConnectionState=" + mConnectionState);
}
synchronized (mLock) {
// Reset connectionWrapper.
@@ -189,6 +216,13 @@
}
mConnectionWrapper.unlinkToDeath(mConnectionCallback);
mConnectionWrapper = null;
+ // The connection is still connecting so it is no need to reset the
+ // connection state to disconnected.
+ // TODO b/220086369 will reset the connection immediately when requestConnection
+ // is called
+ if (mConnectionState != CONNECTING) {
+ setConnectionState(DISCONNECTED);
+ }
}
if (connection != null) {
mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection, mTrace);
@@ -199,9 +233,13 @@
mConnectionCallback = new ConnectionCallback();
mConnectionWrapper.linkToDeath(mConnectionCallback);
mConnectionWrapper.setConnectionCallback(mConnectionCallback);
+ setConnectionState(CONNECTED);
} catch (RemoteException e) {
Slog.e(TAG, "setConnection failed", e);
mConnectionWrapper = null;
+ setConnectionState(DISCONNECTED);
+ } finally {
+ mLock.notify();
}
}
}
@@ -229,10 +267,20 @@
if (DBG) {
Slog.d(TAG, "requestConnection :" + connect);
}
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".requestWindowMagnificationConnection",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect);
+ }
synchronized (mLock) {
- if (connect == isConnected()) {
+ if ((connect && (mConnectionState == CONNECTED || mConnectionState == CONNECTING))
+ || (!connect && (mConnectionState == DISCONNECTED
+ || mConnectionState == DISCONNECTING))) {
+ Slog.w(TAG,
+ "requestConnection duplicated request: connect=" + connect
+ + " ,mConnectionState=" + mConnectionState);
return false;
}
+
if (connect) {
final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
if (!mReceiverRegistered) {
@@ -247,19 +295,42 @@
}
}
}
- if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
- mTrace.logTrace(TAG + ".requestWindowMagnificationConnection",
- FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect);
+ if (requestConnectionInternal(connect)) {
+ setConnectionState(connect ? CONNECTING : DISCONNECTING);
+ return true;
+ } else {
+ setConnectionState(DISCONNECTED);
+ return false;
}
+ }
+
+ private boolean requestConnectionInternal(boolean connect) {
final long identity = Binder.clearCallingIdentity();
try {
final StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
- service.requestWindowMagnificationConnection(connect);
+ if (service != null) {
+ return service.requestWindowMagnificationConnection(connect);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
- return true;
+ return false;
+ }
+
+ /**
+ * Returns window magnification connection state.
+ */
+ public int getConnectionState() {
+ return mConnectionState;
+ }
+
+ private void setConnectionState(@ConnectionState int state) {
+ if (DBG) {
+ Slog.d(TAG, "setConnectionState : state=" + state + " ,mConnectionState="
+ + mConnectionState);
+ }
+ mConnectionState = state;
}
/**
@@ -314,9 +385,13 @@
float toCenterX = (float) (left + right) / 2;
float toCenterY = (float) (top + bottom) / 2;
- if (!isPositionInSourceBounds(displayId, toCenterX, toCenterY)
- && isTrackingTypingFocusEnabled(displayId)) {
- enableWindowMagnification(displayId, Float.NaN, toCenterX, toCenterY);
+ synchronized (mLock) {
+ if (mIsImeVisibleArray.get(displayId, false)
+ && !isPositionInSourceBounds(displayId, toCenterX, toCenterY)
+ && isTrackingTypingFocusEnabled(displayId)) {
+ moveWindowMagnifierToPositionInternal(displayId, toCenterX, toCenterY,
+ STUB_ANIMATION_CALLBACK);
+ }
}
}
@@ -357,7 +432,7 @@
* @param displayId The logical display id.
* @param trackingTypingFocusEnabled Enabled or disable the function of tracking typing focus.
*/
- private void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) {
+ void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) {
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null) {
@@ -384,7 +459,8 @@
*
* @param shown {@code true} means the IME window shows on the screen. Otherwise, it's hidden.
*/
- void onImeWindowVisibilityChanged(boolean shown) {
+ void onImeWindowVisibilityChanged(int displayId, boolean shown) {
+ mIsImeVisibleArray.put(displayId, shown);
if (shown) {
enableAllTrackingTypingFocus();
}
@@ -500,8 +576,11 @@
animationCallback, windowPosition, id);
}
- if (enabled && !previousEnabled) {
- mCallback.onWindowMagnificationActivationState(displayId, true);
+ if (enabled) {
+ setTrackingTypingFocusEnabled(displayId, true);
+ if (!previousEnabled) {
+ mCallback.onWindowMagnificationActivationState(displayId, true);
+ }
}
return enabled;
}
@@ -563,14 +642,13 @@
}
}
+ @GuardedBy("mLock")
boolean isPositionInSourceBounds(int displayId, float x, float y) {
- synchronized (mLock) {
- WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
- if (magnifier == null) {
- return false;
- }
- return magnifier.isPositionInSourceBounds(x, y);
+ WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
+ if (magnifier == null) {
+ return false;
}
+ return magnifier.isPositionInSourceBounds(x, y);
}
/**
@@ -829,10 +907,10 @@
}
@Override
- public void onDrag(int displayId) {
+ public void onMove(int displayId) {
if (mTrace.isA11yTracingEnabledForTypes(
FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
- mTrace.logTrace(TAG + "ConnectionCallback.onDrag",
+ mTrace.logTrace(TAG + "ConnectionCallback.onMove",
FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
"displayId=" + displayId);
}
@@ -849,6 +927,7 @@
mConnectionWrapper.unlinkToDeath(this);
mConnectionWrapper = null;
mConnectionCallback = null;
+ setConnectionState(DISCONNECTED);
resetWindowMagnifiers();
}
}
@@ -884,7 +963,6 @@
mWindowMagnificationManager = windowMagnificationManager;
}
- @GuardedBy("mLock")
boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
@Nullable MagnificationAnimationCallback animationCallback,
@WindowPosition int windowPosition, int id) {
@@ -962,7 +1040,6 @@
return mIdOfLastServiceToControl;
}
- @GuardedBy("mLock")
int pointersInWindow(MotionEvent motionEvent) {
int count = 0;
final int pointerCount = motionEvent.getPointerCount();
@@ -1025,11 +1102,22 @@
float centerY, float magnificationFrameOffsetRatioX,
float magnificationFrameOffsetRatioY,
MagnificationAnimationCallback animationCallback) {
+ // Wait for the connection with a timeout.
+ final long endMillis = SystemClock.uptimeMillis() + WAIT_CONNECTION_TIMEOUT_MILLIS;
+ while (mConnectionState == CONNECTING && (SystemClock.uptimeMillis() < endMillis)) {
+ try {
+ mLock.wait(endMillis - SystemClock.uptimeMillis());
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
if (mConnectionWrapper == null) {
- Slog.w(TAG, "enableWindowMagnificationInternal mConnectionWrapper is null");
+ Slog.w(TAG,
+ "enableWindowMagnificationInternal mConnectionWrapper is null. "
+ + "mConnectionState=" + mConnectionState);
return false;
}
- return mConnectionWrapper.enableWindowMagnification(
+ return mConnectionWrapper.enableWindowMagnification(
displayId, scale, centerX, centerY,
magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY,
animationCallback);
@@ -1050,8 +1138,16 @@
displayId, animationCallback);
}
+ @GuardedBy("mLock")
private boolean moveWindowMagnifierInternal(int displayId, float offsetX, float offsetY) {
return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifier(
displayId, offsetX, offsetY);
}
+
+ @GuardedBy("mLock")
+ private boolean moveWindowMagnifierToPositionInternal(int displayId, float positionX,
+ float positionY, MagnificationAnimationCallback animationCallback) {
+ return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifierToPosition(
+ displayId, positionX, positionY, animationCallback);
+ }
}
diff --git a/services/api/current.txt b/services/api/current.txt
index e46c972..440f66a 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -38,7 +38,7 @@
package com.android.server.am {
public interface ActivityManagerLocal {
- method public boolean bindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, int) throws android.os.RemoteException;
+ method public boolean bindSdkSandboxService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, int) throws android.os.RemoteException;
method public boolean canStartForegroundService(int, int, @NonNull String);
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a2b289c..0241c1e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -133,7 +133,7 @@
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
"service-permission.stubs.system_server",
- "service-supplementalprocess.stubs.system_server",
+ "service-sdksandbox.stubs.system_server",
],
required: [
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 111bd34..e6953f0 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -657,6 +657,11 @@
public abstract void notifyPackageUse(String packageName, int reason);
/**
+ * Notify the package is force stopped.
+ */
+ public abstract void onPackageProcessKilledForUninstall(String packageName);
+
+ /**
* Returns a package object for the given package name.
*/
public abstract @Nullable AndroidPackage getPackage(@NonNull String packageName);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 6986d3b..1f8ef82 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -18,14 +18,22 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemProperties;
@@ -42,6 +50,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
@@ -49,6 +58,7 @@
*/
public class BinaryTransparencyService extends SystemService {
private static final String TAG = "TransparencyService";
+ private static final String EXTRA_SERVICE = "service";
@VisibleForTesting
static final String VBMETA_DIGEST_UNINITIALIZED = "vbmeta-digest-uninitialized";
@@ -365,10 +375,80 @@
// we are only interested in doing things at PHASE_BOOT_COMPLETED
if (phase == PHASE_BOOT_COMPLETED) {
- // due to potentially long computation that holds up boot time, apex sha computations
- // are deferred to first call
Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
getVBMetaDigestInformation();
+
+ // due to potentially long computation that holds up boot time, computations for
+ // SHA256 digests of APEX and Module packages are scheduled here,
+ // but only executed when device is idle.
+ Slog.i(TAG, "Scheduling APEX and Module measurements to be updated.");
+ UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
+ BinaryTransparencyService.this);
+ }
+ }
+
+ /**
+ * JobService to update binary measurements and update internal cache.
+ */
+ public static class UpdateMeasurementsJobService extends JobService {
+ private static final int COMPUTE_APEX_MODULE_SHA256_JOB_ID =
+ BinaryTransparencyService.UpdateMeasurementsJobService.class.hashCode();
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ Slog.d(TAG, "Job to update binary measurements started.");
+ if (params.getJobId() != COMPUTE_APEX_MODULE_SHA256_JOB_ID) {
+ return false;
+ }
+
+ // we'll still update the measurements via threads to be mindful of low-end devices
+ // where this operation might take longer than expected, and so that we don't block
+ // system_server's main thread.
+ Executors.defaultThreadFactory().newThread(() -> {
+ // since we can't call updateBinaryMeasurements() directly, calling
+ // getApexInfo() achieves the same effect, and we simply discard the return
+ // value
+
+ IBinder b = ServiceManager.getService(Context.BINARY_TRANSPARENCY_SERVICE);
+ IBinaryTransparencyService iBtsService =
+ IBinaryTransparencyService.Stub.asInterface(b);
+ try {
+ iBtsService.getApexInfo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Updating binary measurements was interrupted.", e);
+ return;
+ }
+ jobFinished(params, false);
+ }).start();
+
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+
+ @SuppressLint("DefaultLocale")
+ static void scheduleBinaryMeasurements(Context context, BinaryTransparencyService service) {
+ Slog.i(TAG, "Scheduling APEX & Module SHA256 digest computation job");
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ Slog.e(TAG, "Failed to obtain an instance of JobScheduler.");
+ return;
+ }
+
+ final JobInfo jobInfo = new JobInfo.Builder(COMPUTE_APEX_MODULE_SHA256_JOB_ID,
+ new ComponentName(context, UpdateMeasurementsJobService.class))
+ .setRequiresDeviceIdle(true)
+ .build();
+ if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) {
+ Slog.e(TAG, "Failed to schedule job to update binary measurements.");
+ return;
+ }
+ Slog.d(TAG, String.format(
+ "Job %d to update binary measurements scheduled successfully.",
+ COMPUTE_APEX_MODULE_SHA256_JOB_ID));
}
}
@@ -380,7 +460,7 @@
@NonNull
private List<PackageInfo> getInstalledApexs() {
- List<PackageInfo> results = new ArrayList<PackageInfo>();
+ List<PackageInfo> results = new ArrayList<>();
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
Slog.e(TAG, "Error obtaining an instance of PackageManager.");
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index fcde533..1e534b7 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -36,12 +36,15 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.TimestampedValue;
import android.provider.Settings;
import android.util.LocalLog;
import android.util.Log;
import android.util.NtpTrustedTime;
+import android.util.NtpTrustedTime.TimeResult;
import android.util.TimeUtils;
import com.android.internal.util.DumpUtils;
@@ -152,6 +155,42 @@
}, new IntentFilter(ACTION_POLL));
}
+ /**
+ * Clears the cached NTP time. For use during tests to simulate when no NTP time is available.
+ *
+ * <p>This operation takes place in the calling thread rather than the service's handler thread.
+ */
+ void clearTimeForTests() {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.SET_TIME, "clear latest network time");
+
+ mTime.clearCachedTimeResult();
+
+ mLocalLog.log("clearTimeForTests");
+ }
+
+ /**
+ * Forces the service to refresh the NTP time.
+ *
+ * <p>This operation takes place in the calling thread rather than the service's handler thread.
+ * This method does not affect currently scheduled refreshes. If the NTP request is successful
+ * it will make an (asynchronously handled) suggestion to the time detector.
+ */
+ boolean forceRefreshForTests() {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.SET_TIME, "force network time refresh");
+
+ boolean success = mTime.forceRefresh();
+ mLocalLog.log("forceRefreshForTests: success=" + success);
+
+ if (success) {
+ makeNetworkTimeSuggestion(mTime.getCachedTimeResult(),
+ "Origin: NetworkTimeUpdateService: forceRefreshForTests");
+ }
+
+ return success;
+ }
+
private void onPollNetworkTime(int event) {
// If we don't have any default network, don't bother.
if (mDefaultNetwork == null) return;
@@ -193,12 +232,8 @@
resetAlarm(mPollingIntervalMs
- cachedNtpResult.getAgeMillis(currentElapsedRealtimeMillis));
- // Suggest the time to the time detector. It may choose use it to set the system clock.
- TimestampedValue<Long> timeSignal = new TimestampedValue<>(
- cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis());
- NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
- timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateService. event=" + event);
- mTimeDetector.suggestNetworkTime(timeSuggestion);
+ makeNetworkTimeSuggestion(cachedNtpResult,
+ "Origin: NetworkTimeUpdateService. event=" + event);
} else {
// No fresh fix; schedule retry
mTryAgainCounter++;
@@ -217,6 +252,15 @@
}
}
+ /** Suggests the time to the time detector. It may choose use it to set the system clock. */
+ private void makeNetworkTimeSuggestion(TimeResult ntpResult, String debugInfo) {
+ TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+ ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
+ NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
+ timeSuggestion.addDebugInfo(debugInfo);
+ mTimeDetector.suggestNetworkTime(timeSuggestion);
+ }
+
/**
* Cancel old alarm and starts a new one for the specified interval.
*
@@ -320,4 +364,11 @@
mLocalLog.dump(fd, pw, args);
pw.println();
}
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ new NetworkTimeUpdateServiceShellCommand(this).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
}
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java
new file mode 100644
index 0000000..dc93023
--- /dev/null
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 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;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/** Implements the shell command interface for {@link NetworkTimeUpdateService}. */
+class NetworkTimeUpdateServiceShellCommand extends ShellCommand {
+
+ /**
+ * The name of the service.
+ */
+ private static final String SHELL_COMMAND_SERVICE_NAME = "network_time_update_service";
+
+ /**
+ * A shell command that clears the time signal received from the network.
+ */
+ private static final String SHELL_COMMAND_CLEAR_TIME = "clear_time";
+
+ /**
+ * A shell command that forces the time signal to be refreshed from the network.
+ */
+ private static final String SHELL_COMMAND_FORCE_REFRESH = "force_refresh";
+
+ @NonNull
+ private final NetworkTimeUpdateService mNetworkTimeUpdateService;
+
+ NetworkTimeUpdateServiceShellCommand(NetworkTimeUpdateService networkTimeUpdateService) {
+ mNetworkTimeUpdateService = Objects.requireNonNull(networkTimeUpdateService);
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ switch (cmd) {
+ case SHELL_COMMAND_CLEAR_TIME:
+ return runClearTime();
+ case SHELL_COMMAND_FORCE_REFRESH:
+ return runForceRefresh();
+ default: {
+ return handleDefaultCommands(cmd);
+ }
+ }
+ }
+
+ private int runClearTime() {
+ mNetworkTimeUpdateService.clearTimeForTests();
+ return 0;
+ }
+
+ private int runForceRefresh() {
+ boolean success = mNetworkTimeUpdateService.forceRefreshForTests();
+ getOutPrintWriter().println(success);
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.printf("Network Time Update Service (%s) commands:\n", SHELL_COMMAND_SERVICE_NAME);
+ pw.printf(" help\n");
+ pw.printf(" Print this help text.\n");
+ pw.printf(" %s\n", SHELL_COMMAND_CLEAR_TIME);
+ pw.printf(" Clears the latest time.\n");
+ pw.printf(" %s\n", SHELL_COMMAND_FORCE_REFRESH);
+ pw.printf(" Refreshes the latest time. Prints whether it was successful.\n");
+ pw.println();
+ }
+}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 4129feb..6ff8e36 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -21,14 +21,13 @@
per-file *Battery* = file:/BATTERY_STATS_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
-per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
-per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS
+per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
per-file MmsServiceBroker.java = file:/telephony/OWNERS
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c194527..d4764a6 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -4747,7 +4747,7 @@
private int getMountModeInternal(int uid, String packageName) {
try {
// Get some easy cases out of the way first
- if (Process.isIsolated(uid) || Process.isSupplemental(uid)) {
+ if (Process.isIsolated(uid) || Process.isSdkSandboxUid(uid)) {
return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 092172a..d4ad718 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2721,7 +2721,7 @@
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, final IServiceConnection connection, int flags,
- String instanceName, boolean isSupplementalProcessService, int supplementedAppUid,
+ String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
String callingPackage, final int userId)
throws TransactionTooLargeException {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
@@ -2807,7 +2807,7 @@
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
- isSupplementalProcessService, supplementedAppUid, resolvedType, callingPackage,
+ isSdkSandboxService, sdkSandboxClientAppUid, resolvedType, callingPackage,
callingPid, callingUid, userId, true, callerFg, isBindExternal, allowInstant);
if (res == null) {
return 0;
@@ -3234,13 +3234,13 @@
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
- String instanceName, boolean isSupplementalProcessService, int supplementedAppUid,
+ String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
String resolvedType,
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant) {
- if (isSupplementalProcessService && instanceName == null) {
- throw new IllegalArgumentException("No instanceName provided for supplemental process");
+ if (isSdkSandboxService && instanceName == null) {
+ throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
}
ServiceRecord r = null;
@@ -3319,13 +3319,13 @@
}
if (instanceName != null
&& (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0
- && !isSupplementalProcessService) {
+ && !isSdkSandboxService) {
throw new IllegalArgumentException("Can't use instance name '" + instanceName
- + "' with non-isolated non-supplemental service '" + sInfo.name + "'");
+ + "' with non-isolated non-sdk sandbox service '" + sInfo.name + "'");
}
- if (isSupplementalProcessService
+ if (isSdkSandboxService
&& (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
- throw new IllegalArgumentException("Service cannot be both supplemental and "
+ throw new IllegalArgumentException("Service cannot be both sdk sandbox and "
+ "isolated");
}
@@ -3412,11 +3412,11 @@
final Intent.FilterComparison filter
= new Intent.FilterComparison(service.cloneFilter());
final ServiceRestarter res = new ServiceRestarter();
- String supplementalProcessName = isSupplementalProcessService ? instanceName
+ String sdkSandboxProcessName = isSdkSandboxService ? instanceName
: null;
r = new ServiceRecord(mAm, className, name, definingPackageName,
definingUid, filter, sInfo, callingFromFg, res,
- supplementalProcessName, supplementedAppUid);
+ sdkSandboxProcessName, sdkSandboxClientAppUid);
res.setService(r);
smap.mServicesByInstanceName.put(name, r);
smap.mServicesByIntent.put(filter, r);
@@ -4139,7 +4139,8 @@
final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
final String procName = r.processName;
- HostingRecord hostingRecord = new HostingRecord("service", r.instanceName);
+ HostingRecord hostingRecord = new HostingRecord("service", r.instanceName,
+ r.definingPackageName, r.definingUid, r.serviceInfo.processName);
ProcessRecord app;
if (!isolated) {
@@ -4177,11 +4178,12 @@
app = r.isolationHostProc;
if (WebViewZygote.isMultiprocessEnabled()
&& r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
- hostingRecord = HostingRecord.byWebviewZygote(r.instanceName);
+ hostingRecord = HostingRecord.byWebviewZygote(r.instanceName, r.definingPackageName,
+ r.definingUid, r.serviceInfo.processName);
}
if ((r.serviceInfo.flags & ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) {
hostingRecord = HostingRecord.byAppZygote(r.instanceName, r.definingPackageName,
- r.definingUid);
+ r.definingUid, r.serviceInfo.processName);
}
}
@@ -4190,9 +4192,9 @@
if (app == null && !permissionsReviewRequired && !packageFrozen) {
// TODO (chriswailes): Change the Zygote policy flags based on if the launch-for-service
// was initiated from a notification tap or not.
- if (r.supplemental) {
- final int uid = Process.toSupplementalUid(r.supplementedAppUid);
- app = mAm.startSupplementalProcessLocked(procName, r.appInfo, true, intentFlags,
+ if (r.isSdkSandbox) {
+ final int uid = Process.toSdkSandboxUid(r.sdkSandboxClientAppUid);
+ app = mAm.startSdkSandboxProcessLocked(procName, r.appInfo, true, intentFlags,
hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, uid);
r.isolationHostProc = app;
} else {
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 535340b..3226a2b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -65,15 +65,15 @@
void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
/**
- * Binds to a supplemental process service, creating it if needed. You can through the arguments
+ * Binds to a sdk sandbox service, creating it if needed. You can through the arguments
* here have the system bring up multiple concurrent processes hosting their own instance of
* that service. The {@code processName} you provide here identifies the different instances.
*
- * @param service Identifies the supplemental process service to connect to. The Intent must
+ * @param service Identifies the sdk sandbox process service to connect to. The Intent must
* specify an explicit component name. This value cannot be null.
* @param conn Receives information as the service is started and stopped.
* This must be a valid ServiceConnection object; it must not be null.
- * @param userAppUid Uid of the app for which the supplemental process needs to be spawned.
+ * @param clientAppUid Uid of the app for which the sdk sandbox process needs to be spawned.
* @param processName Unique identifier for the service instance. Each unique name here will
* result in a different service instance being created. Identifiers must only contain
* ASCII letters, digits, underscores, and periods.
@@ -86,7 +86,7 @@
* @see Context#bindService(Intent, ServiceConnection, int)
*/
@SuppressLint("RethrowRemoteException")
- boolean bindSupplementalProcessService(@NonNull Intent service, @NonNull ServiceConnection conn,
- int userAppUid, @NonNull String processName, @Context.BindServiceFlags int flags)
+ boolean bindSdkSandboxService(@NonNull Intent service, @NonNull ServiceConnection conn,
+ int clientAppUid, @NonNull String processName, @Context.BindServiceFlags int flags)
throws RemoteException;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 20a8509..47f9fd5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2789,13 +2789,13 @@
}
@GuardedBy("this")
- final ProcessRecord startSupplementalProcessLocked(String processName,
+ final ProcessRecord startSdkSandboxProcessLocked(String processName,
ApplicationInfo info, boolean knownToBeDead, int intentFlags,
- HostingRecord hostingRecord, int zygotePolicyFlags, int supplementalUid) {
+ HostingRecord hostingRecord, int zygotePolicyFlags, int sdkSandboxUid) {
return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
hostingRecord, zygotePolicyFlags, false /* allowWhileBooting */,
false /* isolated */, 0 /* isolatedUid */,
- true /* supplemental */, supplementalUid,
+ true /* isSdkSandbox */, sdkSandboxUid,
null /* ABI override */, null /* entryPoint */,
null /* entryPointArgs */, null /* crashHandler */);
}
@@ -2807,7 +2807,7 @@
boolean isolated) {
return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */,
- false /* supplemental */, 0 /* supplementalUid */,
+ false /* isSdkSandbox */, 0 /* sdkSandboxClientdAppUid */,
null /* ABI override */, null /* entryPoint */,
null /* entryPointArgs */, null /* crashHandler */);
}
@@ -12396,7 +12396,7 @@
private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags, String instanceName,
- boolean isSupplementalProcessService, int supplementedAppUid, String callingPackage,
+ boolean isSdkSandboxService, int sdkSandboxClientdAppUid, String callingPackage,
int userId)
throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
@@ -12410,7 +12410,7 @@
throw new IllegalArgumentException("callingPackage cannot be null");
}
- if (isSupplementalProcessService && instanceName == null) {
+ if (isSdkSandboxService && instanceName == null) {
throw new IllegalArgumentException("No instance name provided for isolated process");
}
@@ -12426,10 +12426,19 @@
}
}
- synchronized(this) {
- return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
- flags, instanceName, isSupplementalProcessService, supplementedAppUid,
- callingPackage, userId);
+ try {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ final ComponentName cn = service.getComponent();
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindService:"
+ + (cn != null ? cn.toShortString() : service.getAction()));
+ }
+ synchronized (this) {
+ return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
+ flags, instanceName, isSdkSandboxService, sdkSandboxClientdAppUid,
+ callingPackage, userId);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -13586,6 +13595,8 @@
intent.getIntExtra(Intent.EXTRA_UID, -1)),
false, true, true, false, fullUninstall, userId,
removed ? "pkg removed" : "pkg changed");
+ getPackageManagerInternal()
+ .onPackageProcessKilledForUninstall(ssp);
} else {
// Kill any app zygotes always, since they can't fork new
// processes with references to the old code
@@ -16006,7 +16017,7 @@
}
@Override
- public boolean bindSupplementalProcessService(Intent service, ServiceConnection conn,
+ public boolean bindSdkSandboxService(Intent service, ServiceConnection conn,
int userAppUid, String processName, int flags) throws RemoteException {
if (service == null) {
throw new IllegalArgumentException("intent is null");
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index 6bb5def..bbf5861 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -56,19 +56,27 @@
private final String mDefiningPackageName;
private final int mDefiningUid;
private final boolean mIsTopApp;
+ private final String mDefiningProcessName;
public HostingRecord(String hostingType) {
this(hostingType, null /* hostingName */, REGULAR_ZYGOTE, null /* definingPackageName */,
- -1 /* mDefiningUid */, false /* isTopApp */);
+ -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */);
}
public HostingRecord(String hostingType, ComponentName hostingName) {
this(hostingType, hostingName, REGULAR_ZYGOTE);
}
+ public HostingRecord(String hostingType, ComponentName hostingName, String definingPackageName,
+ int definingUid, String definingProcessName) {
+ this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, definingPackageName,
+ definingUid, false /* isTopApp */, definingProcessName);
+ }
+
public HostingRecord(String hostingType, ComponentName hostingName, boolean isTopApp) {
this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
- null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */);
+ null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */,
+ null /* definingProcessName */);
}
public HostingRecord(String hostingType, String hostingName) {
@@ -81,17 +89,19 @@
private HostingRecord(String hostingType, String hostingName, int hostingZygote) {
this(hostingType, hostingName, hostingZygote, null /* definingPackageName */,
- -1 /* mDefiningUid */, false /* isTopApp */);
+ -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */);
}
private HostingRecord(String hostingType, String hostingName, int hostingZygote,
- String definingPackageName, int definingUid, boolean isTopApp) {
+ String definingPackageName, int definingUid, boolean isTopApp,
+ String definingProcessName) {
mHostingType = hostingType;
mHostingName = hostingName;
mHostingZygote = hostingZygote;
mDefiningPackageName = definingPackageName;
mDefiningUid = definingUid;
mIsTopApp = isTopApp;
+ mDefiningProcessName = definingProcessName;
}
public String getType() {
@@ -127,12 +137,24 @@
}
/**
+ * Returns the processName of the component we want to start as specified in the defining app's
+ * manifest.
+ *
+ * @return the processName of the process in the hosting application
+ */
+ public String getDefiningProcessName() {
+ return mDefiningProcessName;
+ }
+
+ /**
* Creates a HostingRecord for a process that must spawn from the webview zygote
* @param hostingName name of the component to be hosted in this process
* @return The constructed HostingRecord
*/
- public static HostingRecord byWebviewZygote(ComponentName hostingName) {
- return new HostingRecord("", hostingName.toShortString(), WEBVIEW_ZYGOTE);
+ public static HostingRecord byWebviewZygote(ComponentName hostingName,
+ String definingPackageName, int definingUid, String definingProcessName) {
+ return new HostingRecord("", hostingName.toShortString(), WEBVIEW_ZYGOTE,
+ definingPackageName, definingUid, false /* isTopApp */, definingProcessName);
}
/**
@@ -143,9 +165,9 @@
* @return The constructed HostingRecord
*/
public static HostingRecord byAppZygote(ComponentName hostingName, String definingPackageName,
- int definingUid) {
+ int definingUid, String definingProcessName) {
return new HostingRecord("", hostingName.toShortString(), APP_ZYGOTE,
- definingPackageName, definingUid, false /* isTopApp */);
+ definingPackageName, definingUid, false /* isTopApp */, definingProcessName);
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 763bbee..2c2e7c4 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -70,7 +70,6 @@
import android.app.IProcessObserver;
import android.app.IUidObserver;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -363,59 +362,6 @@
private static final long LMKD_RECONNECT_DELAY_MS = 1000;
/**
- * Native heap allocations will now have a non-zero tag in the most significant byte.
- * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
- * Pointers</a>
- */
- @ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
- private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
-
- /**
- * Native heap allocations in AppZygote process and its descendants will now have a
- * non-zero tag in the most significant byte.
- * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
- * Pointers</a>
- */
- @ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
- private static final long NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE = 207557677;
-
- /**
- * Enable asynchronous (ASYNC) memory tag checking in this process. This
- * flag will only have an effect on hardware supporting the ARM Memory
- * Tagging Extension (MTE).
- */
- @ChangeId
- @Disabled
- private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id.
-
- /**
- * Enable synchronous (SYNC) memory tag checking in this process. This flag
- * will only have an effect on hardware supporting the ARM Memory Tagging
- * Extension (MTE). If both NATIVE_MEMTAG_ASYNC and this option is selected,
- * this option takes preference and MTE is enabled in SYNC mode.
- */
- @ChangeId
- @Disabled
- private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.
-
- /**
- * Enable automatic zero-initialization of native heap memory allocations.
- */
- @ChangeId
- @Disabled
- private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.
-
- /**
- * Enable sampled memory bug detection in the app.
- * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
- */
- @ChangeId
- @Disabled
- private static final long GWP_ASAN = 135634846; // This is a bug id.
-
- /**
* Apps have no access to the private data directories of any other app, even if the other
* app has made them world-readable.
*/
@@ -1681,136 +1627,6 @@
return gidArray;
}
- private int memtagModeToZygoteMemtagLevel(int memtagMode) {
- switch (memtagMode) {
- case ApplicationInfo.MEMTAG_ASYNC:
- return Zygote.MEMORY_TAG_LEVEL_ASYNC;
- case ApplicationInfo.MEMTAG_SYNC:
- return Zygote.MEMORY_TAG_LEVEL_SYNC;
- default:
- return Zygote.MEMORY_TAG_LEVEL_NONE;
- }
- }
-
- // Returns the requested memory tagging level.
- private int getRequestedMemtagLevel(ProcessRecord app) {
- // Look at the process attribute first.
- if (app.processInfo != null
- && app.processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
- return memtagModeToZygoteMemtagLevel(app.processInfo.memtagMode);
- }
-
- // Then at the application attribute.
- if (app.info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
- return memtagModeToZygoteMemtagLevel(app.info.getMemtagMode());
- }
-
- if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_SYNC, app.info)) {
- return Zygote.MEMORY_TAG_LEVEL_SYNC;
- }
-
- if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_ASYNC, app.info)) {
- return Zygote.MEMORY_TAG_LEVEL_ASYNC;
- }
-
- // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
- if (!app.info.allowsNativeHeapPointerTagging()) {
- return Zygote.MEMORY_TAG_LEVEL_NONE;
- }
-
- String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
- if ("sync".equals(defaultLevel)) {
- return Zygote.MEMORY_TAG_LEVEL_SYNC;
- } else if ("async".equals(defaultLevel)) {
- return Zygote.MEMORY_TAG_LEVEL_ASYNC;
- }
-
- // Check to see that the compat feature for TBI is enabled.
- if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
- return Zygote.MEMORY_TAG_LEVEL_TBI;
- }
-
- return Zygote.MEMORY_TAG_LEVEL_NONE;
- }
-
- private int decideTaggingLevel(ProcessRecord app) {
- // Get the desired tagging level (app manifest + compat features).
- int level = getRequestedMemtagLevel(app);
-
- // Take into account the hardware capabilities.
- if (Zygote.nativeSupportsMemoryTagging()) {
- // MTE devices can not do TBI, because the Zygote process already has live MTE
- // allocations. Downgrade TBI to NONE.
- if (level == Zygote.MEMORY_TAG_LEVEL_TBI) {
- level = Zygote.MEMORY_TAG_LEVEL_NONE;
- }
- } else if (Zygote.nativeSupportsTaggedPointers()) {
- // TBI-but-not-MTE devices downgrade MTE modes to TBI.
- // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
- // the "fake" pointer tagging (TBI).
- if (level == Zygote.MEMORY_TAG_LEVEL_ASYNC || level == Zygote.MEMORY_TAG_LEVEL_SYNC) {
- level = Zygote.MEMORY_TAG_LEVEL_TBI;
- }
- } else {
- // Otherwise disable all tagging.
- level = Zygote.MEMORY_TAG_LEVEL_NONE;
- }
-
- return level;
- }
-
- private int decideTaggingLevelForAppZygote(ProcessRecord app) {
- int level = decideTaggingLevel(app);
- // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
- if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE, app.info)
- && level == Zygote.MEMORY_TAG_LEVEL_TBI) {
- level = Zygote.MEMORY_TAG_LEVEL_NONE;
- }
- return level;
- }
-
- private int decideGwpAsanLevel(ProcessRecord app) {
- // Look at the process attribute first.
- if (app.processInfo != null
- && app.processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
- return app.processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS
- ? Zygote.GWP_ASAN_LEVEL_ALWAYS
- : Zygote.GWP_ASAN_LEVEL_NEVER;
- }
- // Then at the application attribute.
- if (app.info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
- return app.info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
- ? Zygote.GWP_ASAN_LEVEL_ALWAYS
- : Zygote.GWP_ASAN_LEVEL_NEVER;
- }
- // If the app does not specify gwpAsanMode, the default behavior is lottery among the
- // system apps, and disabled for user apps, unless overwritten by the compat feature.
- if (mPlatformCompat.isChangeEnabled(GWP_ASAN, app.info)) {
- return Zygote.GWP_ASAN_LEVEL_ALWAYS;
- }
- if ((app.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return Zygote.GWP_ASAN_LEVEL_LOTTERY;
- }
- return Zygote.GWP_ASAN_LEVEL_NEVER;
- }
-
- private boolean enableNativeHeapZeroInit(ProcessRecord app) {
- // Look at the process attribute first.
- if (app.processInfo != null
- && app.processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) {
- return app.processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED;
- }
- // Then at the application attribute.
- if (app.info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) {
- return app.info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED;
- }
- // Compat feature last.
- if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_ZERO_INIT, app.info)) {
- return true;
- }
- return false;
- }
-
/**
* @return {@code true} if process start is successful, false otherwise.
*/
@@ -1992,8 +1808,6 @@
runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
}
- runtimeFlags |= decideGwpAsanLevel(app);
-
String invokeWith = null;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// Debuggable apps may include a wrapper script with their library directory.
@@ -2024,23 +1838,21 @@
app.setRequiredAbi(requiredAbi);
app.setInstructionSet(instructionSet);
- // If instructionSet is non-null, this indicates that the system_server is spawning a
- // process with an ISA that may be different from its own. System (kernel and hardware)
- // compatibility for these features is checked in the decideTaggingLevel in the
- // system_server process (not the child process). As both MTE and TBI are only supported
- // in aarch64, we can simply ensure that the new process is also aarch64. This prevents
- // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should
- // enable some tagging variant. Theoretically, a 32-bit system server could exist that
- // spawns 64-bit processes, in which case the new process won't get any tagging. This is
- // fine as we haven't seen this configuration in practice, and we can reasonable assume
- // that if tagging is desired, the system server will be 64-bit.
- if (instructionSet == null || instructionSet.equals("arm64")) {
- runtimeFlags |= decideTaggingLevel(app);
+ // If this was an external service, the package name and uid in the passed in
+ // ApplicationInfo have been changed to match those of the calling package;
+ // that will incorrectly apply compat feature overrides for the calling package instead
+ // of the defining one.
+ ApplicationInfo definingAppInfo;
+ if (hostingRecord.getDefiningPackageName() != null) {
+ definingAppInfo = new ApplicationInfo(app.info);
+ definingAppInfo.packageName = hostingRecord.getDefiningPackageName();
+ definingAppInfo.uid = uid;
+ } else {
+ definingAppInfo = app.info;
}
- if (enableNativeHeapZeroInit(app)) {
- runtimeFlags |= Zygote.NATIVE_HEAP_ZERO_INIT;
- }
+ runtimeFlags |= Zygote.getMemorySafetyRuntimeFlags(
+ definingAppInfo, app.processInfo, instructionSet, mPlatformCompat);
// the per-user SELinux context must be set
if (TextUtils.isEmpty(app.info.seInfoUser)) {
@@ -2299,8 +2111,7 @@
// not the calling one.
appInfo.packageName = app.getHostingRecord().getDefiningPackageName();
appInfo.uid = uid;
- int runtimeFlags = decideTaggingLevelForAppZygote(app);
- appZygote = new AppZygote(appInfo, uid, firstUid, lastUid, runtimeFlags);
+ appZygote = new AppZygote(appInfo, app.processInfo, uid, firstUid, lastUid);
mAppZygotes.put(app.info.processName, uid, appZygote);
zygoteProcessList = new ArrayList<ProcessRecord>();
mAppZygoteProcesses.put(appZygote, zygoteProcessList);
@@ -2532,7 +2343,7 @@
ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid,
- boolean supplemental, int supplementalUid,
+ boolean isSdkSandbox, int sdkSandboxUid,
String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
long startTime = SystemClock.uptimeMillis();
ProcessRecord app;
@@ -2626,8 +2437,8 @@
if (app == null) {
checkSlow(startTime, "startProcess: creating new process record");
- app = newProcessRecordLocked(info, processName, isolated, isolatedUid, supplemental,
- supplementalUid, hostingRecord);
+ app = newProcessRecordLocked(info, processName, isolated, isolatedUid, isSdkSandbox,
+ sdkSandboxUid, hostingRecord);
if (app == null) {
Slog.w(TAG, "Failed making new process record for "
+ processName + "/" + info.uid + " isolated=" + isolated);
@@ -3122,13 +2933,13 @@
@GuardedBy("mService")
ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
- boolean isolated, int isolatedUid, boolean supplemental, int supplementalUid,
+ boolean isolated, int isolatedUid, boolean isSdkSandbox, int sdkSandboxUid,
HostingRecord hostingRecord) {
String proc = customProcess != null ? customProcess : info.processName;
final int userId = UserHandle.getUserId(info.uid);
int uid = info.uid;
- if (supplemental) {
- uid = supplementalUid;
+ if (isSdkSandbox) {
+ uid = sdkSandboxUid;
}
if (isolated) {
if (isolatedUid == 0) {
@@ -3158,7 +2969,8 @@
FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, info.uid, uid,
FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED);
}
- final ProcessRecord r = new ProcessRecord(mService, info, proc, uid);
+ final ProcessRecord r = new ProcessRecord(mService, info, proc, uid,
+ hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
final ProcessStateRecord state = r.mState;
if (!mService.mBooted && !mService.mBooting
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index be187e2..7672d10 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -492,24 +492,33 @@
ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
int _uid) {
+ this(_service, _info, _processName, _uid, -1, null);
+ }
+
+ ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
+ int _uid, int _definingUid, String _definingProcessName) {
mService = _service;
mProcLock = _service.mProcLock;
info = _info;
ProcessInfo procInfo = null;
if (_service.mPackageManagerInt != null) {
- ArrayMap<String, ProcessInfo> processes =
- _service.mPackageManagerInt.getProcessesForUid(_uid);
- if (processes != null) {
- procInfo = processes.get(_processName);
- if (procInfo != null && procInfo.deniedPermissions == null
- && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT
- && procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT
- && procInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_DEFAULT) {
- // If this process hasn't asked for permissions to be denied, or for a
- // non-default GwpAsan mode, or any other non-default setting, then we don't
- // care about it.
- procInfo = null;
- }
+ if (_definingUid > 0) {
+ ArrayMap<String, ProcessInfo> processes =
+ _service.mPackageManagerInt.getProcessesForUid(_definingUid);
+ if (processes != null) procInfo = processes.get(_definingProcessName);
+ } else {
+ ArrayMap<String, ProcessInfo> processes =
+ _service.mPackageManagerInt.getProcessesForUid(_uid);
+ if (processes != null) procInfo = processes.get(_processName);
+ }
+ if (procInfo != null && procInfo.deniedPermissions == null
+ && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT
+ && procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT
+ && procInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_DEFAULT) {
+ // If this process hasn't asked for permissions to be denied, or for a
+ // non-default GwpAsan mode, or any other non-default setting, then we don't
+ // care about it.
+ procInfo = null;
}
}
processInfo = procInfo;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index bf2876f..c53d4d6 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -94,8 +94,8 @@
final boolean exported; // from ServiceInfo.exported
final Runnable restarter; // used to schedule retries of starting the service
final long createRealTime; // when this service was created
- final boolean supplemental; // whether this is a supplemental service
- final int supplementedAppUid; // the app uid for which this supplemental service is running
+ final boolean isSdkSandbox; // whether this is a sdk sandbox service
+ final int sdkSandboxClientAppUid; // the app uid for which this sdk sandbox service is running
final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
= new ArrayMap<Intent.FilterComparison, IntentBindRecord>();
// All active bindings to the service.
@@ -105,7 +105,7 @@
ProcessRecord app; // where this service is running or null.
ProcessRecord isolationHostProc; // process which we've started for this service (used for
- // isolated and supplemental processes)
+ // isolated and sdk sandbox processes)
ServiceState tracker; // tracking service execution, may be null
ServiceState restartTracker; // tracking service restart
boolean allowlistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT?
@@ -579,7 +579,7 @@
ServiceRecord(ActivityManagerService ams, ComponentName name,
ComponentName instanceName, String definingPackageName, int definingUid,
Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
- Runnable restarter, String supplementalProcessName, int supplementedAppUid) {
+ Runnable restarter, String sdkSandboxProcessName, int sdkSandboxClientAppUid) {
this.ams = ams;
this.name = name;
this.instanceName = instanceName;
@@ -590,12 +590,12 @@
serviceInfo = sInfo;
appInfo = sInfo.applicationInfo;
packageName = sInfo.applicationInfo.packageName;
- supplemental = supplementalProcessName != null;
- this.supplementedAppUid = supplementedAppUid;
+ isSdkSandbox = sdkSandboxProcessName != null;
+ this.sdkSandboxClientAppUid = sdkSandboxClientAppUid;
if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
processName = sInfo.processName + ":" + instanceName.getClassName();
- } else if (supplementalProcessName != null) {
- processName = supplementalProcessName;
+ } else if (sdkSandboxProcessName != null) {
+ processName = sdkSandboxProcessName;
} else {
processName = sInfo.processName;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b6801fb..1095cf0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2613,7 +2613,7 @@
if (getStartedUserState(userId) == null) {
return false;
}
- if (!getUserInfo(userId).isManagedProfile()) {
+ if (!mInjector.getUserManager().isCredentialSharedWithParent(userId)) {
return false;
}
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 0edbea0..3efd8ad 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -1320,15 +1320,26 @@
// Make sure after resetting the game mode is still supported.
// If not, set the game mode to standard
int gameMode = getGameMode(packageName, userId);
- int newGameMode = gameMode;
GamePackageConfiguration config = null;
synchronized (mOverrideConfigLock) {
config = mOverrideConfigs.get(packageName);
}
- synchronized (mDeviceConfigLock) {
- config = mConfigs.get(packageName);
+ if (config == null) {
+ synchronized (mDeviceConfigLock) {
+ config = mConfigs.get(packageName);
+ }
}
+ final int newGameMode = getNewGameMode(gameMode, config);
+ if (gameMode != newGameMode) {
+ setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
+ return;
+ }
+ setGameMode(packageName, gameMode, userId);
+ }
+
+ private int getNewGameMode(int gameMode, GamePackageConfiguration config) {
+ int newGameMode = gameMode;
if (config != null) {
int modesBitfield = config.getAvailableGameModesBitfield();
// Remove UNSUPPORTED to simplify the logic here, since we really just
@@ -1350,11 +1361,7 @@
// UNSUPPORTED, then set to UNSUPPORTED
newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
}
- if (gameMode != newGameMode) {
- setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
- return;
- }
- setGameMode(packageName, gameMode, userId);
+ return newGameMode;
}
/**
@@ -1412,7 +1419,6 @@
}
for (final String packageName : packageNames) {
int gameMode = getGameMode(packageName, userId);
- int newGameMode = gameMode;
// Make sure the user settings and package configs don't conflict.
// I.e. the user setting is set to a mode that no longer available due to
// config/manifest changes.
@@ -1421,27 +1427,7 @@
synchronized (mDeviceConfigLock) {
config = mConfigs.get(packageName);
}
- if (config != null) {
- int modesBitfield = config.getAvailableGameModesBitfield();
- // Remove UNSUPPORTED to simplify the logic here, since we really just
- // want to check if we support selectable game modes
- modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
- if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) {
- if (bitFieldContainsModeBitmask(modesBitfield,
- GameManager.GAME_MODE_STANDARD)) {
- // If the current set mode isn't supported,
- // but we support STANDARD, then set the mode to STANDARD.
- newGameMode = GameManager.GAME_MODE_STANDARD;
- } else {
- // If we don't support any game modes, then set to UNSUPPORTED
- newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
- }
- }
- } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) {
- // If we have no config for the package, but the configured mode is not
- // UNSUPPORTED, then set to UNSUPPORTED
- newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
- }
+ final int newGameMode = getNewGameMode(gameMode, config);
if (newGameMode != gameMode) {
setGameMode(packageName, newGameMode, userId);
}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index e8d9dad..e4edf4e 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -50,6 +50,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
+import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
import com.android.server.wm.WindowManagerService;
@@ -64,6 +65,40 @@
private static final int CREATE_GAME_SESSION_TIMEOUT_MS = 10_000;
private static final boolean DEBUG = false;
+ private final ServiceLifecycleCallbacks<IGameService> mGameServiceLifecycleCallbacks =
+ new ServiceLifecycleCallbacks<IGameService>() {
+ @Override
+ public void onConnected(@NonNull IGameService service) {
+ try {
+ service.connected(mGameServiceController);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to send connected event", ex);
+ }
+ }
+
+ @Override
+ public void onDisconnected(@NonNull IGameService service) {
+ try {
+ service.disconnected();
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to send disconnected event", ex);
+ }
+ }
+ };
+
+ private final ServiceLifecycleCallbacks<IGameSessionService>
+ mGameSessionServiceLifecycleCallbacks =
+ new ServiceLifecycleCallbacks<IGameSessionService>() {
+ @Override
+ public void onBinderDied() {
+ mBackgroundExecutor.execute(() -> {
+ synchronized (mLock) {
+ destroyAndClearAllGameSessionsLocked();
+ }
+ });
+ }
+ };
+
private final TaskSystemBarsListener mTaskSystemBarsVisibilityListener =
new TaskSystemBarsListener() {
@Override
@@ -206,11 +241,11 @@
}
mIsRunning = true;
- // TODO(b/204503192): In cases where the connection to the game service fails retry with
- // back off mechanism.
- AndroidFuture<Void> unusedPostConnectedFuture = mGameServiceConnector.post(gameService -> {
- gameService.connected(mGameServiceController);
- });
+ mGameServiceConnector.setServiceLifecycleCallbacks(mGameServiceLifecycleCallbacks);
+ mGameSessionServiceConnector.setServiceLifecycleCallbacks(
+ mGameSessionServiceLifecycleCallbacks);
+
+ AndroidFuture<?> unusedConnectFuture = mGameServiceConnector.connect();
try {
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
@@ -237,19 +272,14 @@
mWindowManagerInternal.unregisterTaskSystemBarsListener(
mTaskSystemBarsVisibilityListener);
- for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
- destroyGameSessionFromRecordLocked(gameSessionRecord);
- }
- mGameSessions.clear();
+ destroyAndClearAllGameSessionsLocked();
- // TODO(b/204503192): It is possible that the game service is disconnected. In this
- // case we should avoid rebinding just to shut it down again.
- mGameServiceConnector.post(gameService -> {
- gameService.disconnected();
- }).whenComplete((result, t) -> {
- mGameServiceConnector.unbind();
- });
+ mGameServiceConnector.unbind();
mGameSessionServiceConnector.unbind();
+
+ mGameServiceConnector.setServiceLifecycleCallbacks(null);
+ mGameSessionServiceConnector.setServiceLifecycleCallbacks(null);
+
}
private void onTaskCreated(int taskId, @NonNull ComponentName componentName) {
@@ -488,6 +518,14 @@
createGameSessionResult.getSurfacePackage()));
}
+ @GuardedBy("mLock")
+ private void destroyAndClearAllGameSessionsLocked() {
+ for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
+ destroyGameSessionFromRecordLocked(gameSessionRecord);
+ }
+ mGameSessions.clear();
+ }
+
private void destroyGameSessionDuringAttach(
int taskId,
CreateGameSessionResult createGameSessionResult) {
@@ -544,9 +582,7 @@
Slog.d(TAG, "No active game sessions. Disconnecting GameSessionService");
}
- if (mGameSessionServiceConnector != null) {
- mGameSessionServiceConnector.unbind();
- }
+ mGameSessionServiceConnector.unbind();
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e2d00f7..ef7c93d 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -113,7 +113,6 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
import android.content.pm.UserInfo;
-import com.android.server.pm.pkg.component.ParsedAttribution;
import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
import android.net.Uri;
@@ -178,6 +177,7 @@
import com.android.server.SystemServiceManager;
import com.android.server.pm.PackageList;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedAttribution;
import dalvik.annotation.optimization.NeverCompile;
@@ -4551,15 +4551,16 @@
return new PackageVerificationResult(null,
/* isAttributionTagValid */ true);
}
- if (Process.isSupplemental(uid)) {
- // Supplemental processes run in their own UID range, but their associated
- // UID for checks should always be the UID of the supplemental package.
+ if (Process.isSdkSandboxUid(uid)) {
+ // SDK sandbox processes run in their own UID range, but their associated
+ // UID for checks should always be the UID of the package implementing SDK sandbox
+ // service.
// TODO: We will need to modify the callers of this function instead, so
// modifications and checks against the app ops state are done with the
// correct UID.
try {
final PackageManager pm = mContext.getPackageManager();
- final String supplementalPackageName = pm.getSupplementalProcessPackageName();
+ final String supplementalPackageName = pm.getSdkSandboxPackageName();
if (Objects.equals(packageName, supplementalPackageName)) {
int supplementalAppId = pm.getPackageUid(supplementalPackageName,
PackageManager.PackageInfoFlags.of(0));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ff7557a..270a61b 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1565,6 +1565,13 @@
private AtomicBoolean mMusicMuted = new AtomicBoolean(false);
+ private static <T> boolean hasIntersection(Set<T> a, Set<T> b) {
+ for (T e : a) {
+ if (b.contains(e)) return true;
+ }
+ return false;
+ }
+
boolean messageMutesMusic(int message) {
if (message == 0) {
return false;
@@ -1574,8 +1581,8 @@
|| message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT
|| message == MSG_L_A2DP_DEVICE_CONFIG_CHANGE)
&& AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
- && mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(
- mAudioService.getDevicesForStream(AudioSystem.STREAM_MUSIC))) {
+ && hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET,
+ mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) {
return false;
}
return true;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0ff8a93..82476d8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -195,6 +195,7 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
+import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -1822,6 +1823,7 @@
}
// Check if device to be updated is routed for the given audio stream
+ // This may include devices such as SPEAKER_SAFE.
List<AudioDeviceAttributes> devicesForAttributes = getDevicesForAttributesInt(
new AudioAttributes.Builder().setInternalLegacyStreamType(streamType).build(),
true /* forVolume */);
@@ -3693,8 +3695,7 @@
int streamType = getBluetoothContextualVolumeStream(newMode);
- final Set<Integer> deviceTypes = AudioSystem.generateAudioDeviceTypesSet(
- mAudioSystem.getDevicesForStream(streamType));
+ final Set<Integer> deviceTypes = getDeviceSetForStreamDirect(streamType);
final Set<Integer> absVolumeMultiModeCaseDevices = AudioSystem.intersectionAudioDeviceTypes(
mAbsVolumeMultiModeCaseDevices, deviceTypes);
if (absVolumeMultiModeCaseDevices.isEmpty()) {
@@ -6258,55 +6259,107 @@
}
}
- /** only public for mocking/spying, do not call outside of AudioService */
+ /**
+ * Returns device associated with the stream volume.
+ *
+ * Only public for mocking/spying, do not call outside of AudioService.
+ * Device volume aliasing means DEVICE_OUT_SPEAKER may be returned for
+ * DEVICE_OUT_SPEAKER_SAFE.
+ */
@VisibleForTesting
public int getDeviceForStream(int stream) {
- int device = getDevicesForStreamInt(stream);
- if ((device & (device - 1)) != 0) {
+ return selectOneAudioDevice(getDeviceSetForStream(stream));
+ }
+
+ /*
+ * Must match native apm_extract_one_audio_device() used in getDeviceForVolume()
+ * or the wrong device volume may be adjusted.
+ */
+ private int selectOneAudioDevice(Set<Integer> deviceSet) {
+ if (deviceSet.isEmpty()) {
+ return AudioSystem.DEVICE_NONE;
+ } else if (deviceSet.size() == 1) {
+ return deviceSet.iterator().next();
+ } else {
// Multiple device selection is either:
// - speaker + one other device: give priority to speaker in this case.
// - one A2DP device + another device: happens with duplicated output. In this case
// retain the device on the A2DP output as the other must not correspond to an active
// selection if not the speaker.
// - HDMI-CEC system audio mode only output: give priority to available item in order.
- // FIXME: Haven't applied audio device type refactor to this API
- // as it is going to be deprecated.
- if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {
- device = AudioSystem.DEVICE_OUT_SPEAKER;
- } else if ((device & AudioSystem.DEVICE_OUT_HDMI_ARC) != 0) {
- // FIXME(b/184944421): DEVICE_OUT_HDMI_EARC has two bits set,
- // so it must be handled correctly as it aliases
- // with DEVICE_OUT_HDMI_ARC | DEVICE_OUT_EARPIECE.
- device = AudioSystem.DEVICE_OUT_HDMI_ARC;
- } else if ((device & AudioSystem.DEVICE_OUT_SPDIF) != 0) {
- device = AudioSystem.DEVICE_OUT_SPDIF;
- } else if ((device & AudioSystem.DEVICE_OUT_AUX_LINE) != 0) {
- device = AudioSystem.DEVICE_OUT_AUX_LINE;
+
+ if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER)) {
+ return AudioSystem.DEVICE_OUT_SPEAKER;
+ } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER_SAFE)) {
+ // Note: DEVICE_OUT_SPEAKER_SAFE not present in getDeviceSetForStreamDirect
+ return AudioSystem.DEVICE_OUT_SPEAKER_SAFE;
+ } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_HDMI_ARC)) {
+ return AudioSystem.DEVICE_OUT_HDMI_ARC;
+ } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_HDMI_EARC)) {
+ return AudioSystem.DEVICE_OUT_HDMI_EARC;
+ } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_AUX_LINE)) {
+ return AudioSystem.DEVICE_OUT_AUX_LINE;
+ } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPDIF)) {
+ return AudioSystem.DEVICE_OUT_SPDIF;
} else {
- for (int deviceType : AudioSystem.DEVICE_OUT_ALL_A2DP_SET) {
- if ((deviceType & device) == deviceType) {
+ // At this point, deviceSet should contain exactly one A2DP device;
+ // regardless, return the first A2DP device in numeric order.
+ // If there is no A2DP device, this falls through to log an error.
+ for (int deviceType : deviceSet) {
+ if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(deviceType)) {
return deviceType;
}
}
}
}
- return device;
+ Log.w(TAG, "selectOneAudioDevice returning DEVICE_NONE from invalid device combination "
+ + AudioSystem.deviceSetToString(deviceSet));
+ return AudioSystem.DEVICE_NONE;
}
/**
* @see AudioManager#getDevicesForStream(int)
+ * @deprecated on {@link android.os.Build.VERSION_CODES#T} as new devices
+ * will have multi-bit device types since S.
+ * Use {@link #getDevicesForAttributes()} instead.
*/
- public int getDevicesForStream(int streamType) {
+ @Override
+ @Deprecated
+ public int getDeviceMaskForStream(int streamType) {
ensureValidStreamType(streamType);
+ // no permission required
final long token = Binder.clearCallingIdentity();
try {
- return mAudioSystem.getDevicesForStream(streamType);
+ return AudioSystem.getDeviceMaskFromSet(
+ getDeviceSetForStreamDirect(streamType));
} finally {
Binder.restoreCallingIdentity(token);
}
}
- private int getDevicesForStreamInt(int stream) {
+ /**
+ * Returns the devices associated with a stream type.
+ *
+ * SPEAKER_SAFE will alias to SPEAKER.
+ */
+ @NonNull
+ private Set<Integer> getDeviceSetForStreamDirect(int stream) {
+ final AudioAttributes attr =
+ AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(stream);
+ Set<Integer> deviceSet =
+ AudioSystem.generateAudioDeviceTypesSet(
+ getDevicesForAttributesInt(attr, true /* forVolume */));
+ return deviceSet;
+ }
+
+ /**
+ * Returns a reference to the list of devices for the stream, do not modify.
+ *
+ * The device returned may be aliased to the actual device whose volume curve
+ * will be used. For example DEVICE_OUT_SPEAKER_SAFE aliases to DEVICE_OUT_SPEAKER.
+ */
+ @NonNull
+ public Set<Integer> getDeviceSetForStream(int stream) {
ensureValidStreamType(stream);
synchronized (VolumeStreamState.class) {
return mStreamStates[stream].observeDevicesForStream_syncVSS(true);
@@ -6318,11 +6371,10 @@
synchronized (VolumeStreamState.class) {
for (int stream = 0; stream < mStreamStates.length; stream++) {
if (stream != skipStream) {
- int devices = mStreamStates[stream].observeDevicesForStream_syncVSS(
- false /*checkOthers*/);
-
- Set<Integer> devicesSet = AudioSystem.generateAudioDeviceTypesSet(devices);
- for (Integer device : devicesSet) {
+ Set<Integer> deviceSet =
+ mStreamStates[stream].observeDevicesForStream_syncVSS(
+ false /*checkOthers*/);
+ for (Integer device : deviceSet) {
// Update volume states for devices routed for the stream
updateVolumeStates(device, stream,
"AudioService#onObserveDevicesForAllStreams");
@@ -6643,7 +6695,7 @@
&& DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.contains(newDevice)
&& mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
&& mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
- && (newDevice & mAudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) {
+ && getDeviceSetForStreamDirect(AudioSystem.STREAM_MUSIC).contains(newDevice)) {
if (DEBUG_VOL) {
Log.i(TAG, String.format("onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
newDevice, AudioSystem.getOutputDeviceName(newDevice)));
@@ -7031,7 +7083,7 @@
private boolean mIsMuted;
private boolean mIsMutedInternally;
private String mVolumeIndexSettingName;
- private int mObservedDevices;
+ @NonNull private Set<Integer> mObservedDeviceSet = new TreeSet<>();
private final SparseIntArray mIndexMap = new SparseIntArray(8) {
@Override
@@ -7098,17 +7150,30 @@
}
}
+ /**
+ * Returns a list of devices associated with the stream type.
+ *
+ * This is a reference to the local list, do not modify.
+ */
@GuardedBy("VolumeStreamState.class")
- public int observeDevicesForStream_syncVSS(boolean checkOthers) {
+ @NonNull
+ public Set<Integer> observeDevicesForStream_syncVSS(
+ boolean checkOthers) {
if (!mSystemServer.isPrivileged()) {
- return AudioSystem.DEVICE_NONE;
+ return new TreeSet<Integer>();
}
- final int devices = mAudioSystem.getDevicesForStream(mStreamType);
- if (devices == mObservedDevices) {
- return devices;
+ final Set<Integer> deviceSet =
+ getDeviceSetForStreamDirect(mStreamType);
+ if (deviceSet.equals(mObservedDeviceSet)) {
+ return mObservedDeviceSet;
}
- final int prevDevices = mObservedDevices;
- mObservedDevices = devices;
+
+ // Use legacy bit masks for message signalling.
+ // TODO(b/185386781): message needs update since it uses devices bit-mask.
+ final int devices = AudioSystem.getDeviceMaskFromSet(deviceSet);
+ final int prevDevices = AudioSystem.getDeviceMaskFromSet(mObservedDeviceSet);
+
+ mObservedDeviceSet = deviceSet;
if (checkOthers) {
// one stream's devices have changed, check the others
postObserveDevicesForAllStreams(mStreamType);
@@ -7124,7 +7189,7 @@
SENDMSG_QUEUE, prevDevices /*arg1*/, devices /*arg2*/,
// ok to send reference to this object, it is final
mStreamDevicesChanged /*obj*/, 0 /*delay*/);
- return devices;
+ return mObservedDeviceSet;
}
public @Nullable String getSettingNameForDevice(int device) {
@@ -7555,19 +7620,7 @@
}
pw.println();
pw.print(" Devices: ");
- final int devices = getDevicesForStreamInt(mStreamType);
- int device, i = 0, n = 0;
- // iterate all devices from 1 to DEVICE_OUT_DEFAULT exclusive
- // (the default device is not returned by getDevicesForStreamInt)
- while ((device = 1 << i) != AudioSystem.DEVICE_OUT_DEFAULT) {
- if ((devices & device) != 0) {
- if (n++ > 0) {
- pw.print(", ");
- }
- pw.print(AudioSystem.getOutputDeviceName(device));
- }
- i++;
- }
+ pw.print(AudioSystem.deviceSetToString(getDeviceSetForStream(mStreamType)));
}
}
@@ -9324,7 +9377,9 @@
mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config,
"setHdmiSystemAudioSupported");
}
- device = getDevicesForStreamInt(AudioSystem.STREAM_MUSIC);
+ // TODO(b/185386781): Update AudioManager API to use device list.
+ // So far, this value appears to be advisory for debug log.
+ device = getDeviceMaskForStream(AudioSystem.STREAM_MUSIC);
}
}
return device;
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 6ef8e87..1429b3c 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -52,15 +52,13 @@
* in measured methods
*/
private static final boolean ENABLE_GETDEVICES_STATS = false;
- private static final int NB_MEASUREMENTS = 2;
- private static final int METHOD_GETDEVICESFORSTREAM = 0;
- private static final int METHOD_GETDEVICESFORATTRIBUTES = 1;
+ private static final int NB_MEASUREMENTS = 1;
+ private static final int METHOD_GETDEVICESFORATTRIBUTES = 0;
private long[] mMethodTimeNs;
private int[] mMethodCallCounter;
- private String[] mMethodNames = {"getDevicesForStream", "getDevicesForAttributes"};
+ private String[] mMethodNames = {"getDevicesForAttributes"};
private static final boolean USE_CACHE_FOR_GETDEVICES = true;
- private ConcurrentHashMap<Integer, Integer> mDevicesForStreamCache;
private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
mDevicesForAttrCache;
private int[] mMethodCacheHit;
@@ -113,8 +111,6 @@
sSingletonDefaultAdapter = new AudioSystemAdapter();
AudioSystem.setRoutingCallback(sSingletonDefaultAdapter);
if (USE_CACHE_FOR_GETDEVICES) {
- sSingletonDefaultAdapter.mDevicesForStreamCache =
- new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
sSingletonDefaultAdapter.mDevicesForAttrCache =
new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
@@ -131,11 +127,6 @@
if (DEBUG_CACHE) {
Log.d(TAG, "---- clearing cache ----------");
}
- if (mDevicesForStreamCache != null) {
- synchronized (mDevicesForStreamCache) {
- mDevicesForStreamCache.clear();
- }
- }
if (mDevicesForAttrCache != null) {
synchronized (mDevicesForAttrCache) {
mDevicesForAttrCache.clear();
@@ -144,60 +135,6 @@
}
/**
- * Same as {@link AudioSystem#getDevicesForStream(int)}
- * @param stream a valid stream type
- * @return a mask of device types
- */
- public int getDevicesForStream(int stream) {
- if (!ENABLE_GETDEVICES_STATS) {
- return getDevicesForStreamImpl(stream);
- }
- mMethodCallCounter[METHOD_GETDEVICESFORSTREAM]++;
- final long startTime = SystemClock.uptimeNanos();
- final int res = getDevicesForStreamImpl(stream);
- mMethodTimeNs[METHOD_GETDEVICESFORSTREAM] += SystemClock.uptimeNanos() - startTime;
- return res;
- }
-
- private int getDevicesForStreamImpl(int stream) {
- if (USE_CACHE_FOR_GETDEVICES) {
- Integer res;
- synchronized (mDevicesForStreamCache) {
- res = mDevicesForStreamCache.get(stream);
- if (res == null) {
- res = AudioSystem.getDevicesForStream(stream);
- mDevicesForStreamCache.put(stream, res);
- if (DEBUG_CACHE) {
- Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORSTREAM]
- + streamDeviceToDebugString(stream, res));
- }
- return res;
- }
- // cache hit
- mMethodCacheHit[METHOD_GETDEVICESFORSTREAM]++;
- if (DEBUG_CACHE) {
- final int real = AudioSystem.getDevicesForStream(stream);
- if (res == real) {
- Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORSTREAM]
- + streamDeviceToDebugString(stream, res) + " CACHE");
- } else {
- Log.e(TAG, mMethodNames[METHOD_GETDEVICESFORSTREAM]
- + streamDeviceToDebugString(stream, res)
- + " CACHE ERROR real dev=0x" + Integer.toHexString(real));
- }
- }
- }
- return res;
- }
- // not using cache
- return AudioSystem.getDevicesForStream(stream);
- }
-
- private static String streamDeviceToDebugString(int stream, int dev) {
- return " stream=" + stream + " dev=0x" + Integer.toHexString(dev);
- }
-
- /**
* Same as {@link AudioSystem#getDevicesForAttributes(AudioAttributes)}
* @param attributes the attributes for which the routing is queried
* @return the devices that the stream with the given attributes would be routed to
@@ -253,12 +190,9 @@
}
private static String attrDeviceToDebugString(@NonNull AudioAttributes attr,
- @NonNull ArrayList<AudioDeviceAttributes> devices) {
- String ds = " attrUsage=" + attr.getSystemUsage();
- for (AudioDeviceAttributes ada : devices) {
- ds = ds.concat(" dev=0x" + Integer.toHexString(ada.getInternalType()));
- }
- return ds;
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return " attrUsage=" + attr.getSystemUsage() + " "
+ + AudioSystem.deviceSetToString(AudioSystem.generateAudioDeviceTypesSet(devices));
}
/**
@@ -518,20 +452,23 @@
*/
public void dump(PrintWriter pw) {
pw.println("\nAudioSystemAdapter:");
- pw.println(" mDevicesForStreamCache:");
- if (mDevicesForStreamCache != null) {
- for (Integer stream : mDevicesForStreamCache.keySet()) {
- pw.println("\t stream:" + stream + " device:"
- + AudioSystem.getOutputDeviceName(mDevicesForStreamCache.get(stream)));
- }
- }
pw.println(" mDevicesForAttrCache:");
if (mDevicesForAttrCache != null) {
for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
entry : mDevicesForAttrCache.entrySet()) {
- pw.println("\t" + entry.getKey().first + " forVolume: " + entry.getKey().second);
- for (AudioDeviceAttributes devAttr : entry.getValue()) {
- pw.println("\t\t" + devAttr);
+ final AudioAttributes attributes = entry.getKey().first;
+ try {
+ final int stream = attributes.getVolumeControlStream();
+ pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
+ + " stream: "
+ + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
+ for (AudioDeviceAttributes devAttr : entry.getValue()) {
+ pw.println("\t\t" + devAttr);
+ }
+ } catch (IllegalArgumentException e) {
+ // dump could fail if attributes do not map to a stream.
+ pw.println("\t dump failed for attributes: " + attributes);
+ Log.e(TAG, "dump failed", e);
}
}
}
@@ -545,7 +482,8 @@
+ ": counter=" + mMethodCallCounter[i]
+ " time(ms)=" + (mMethodTimeNs[i] / 1E6)
+ (USE_CACHE_FOR_GETDEVICES
- ? (" FScacheHit=" + mMethodCacheHit[METHOD_GETDEVICESFORSTREAM]) : ""));
+ ? (" FScacheHit=" + mMethodCacheHit[METHOD_GETDEVICESFORATTRIBUTES])
+ : ""));
}
pw.println("\n");
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 79705a3..bf69284 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -20,12 +20,8 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FACE_SCANNING;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FP_SCANNING;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_SWITCHING;
-import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_UNKNOWN;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -100,14 +96,6 @@
@Retention(RetentionPolicy.SOURCE)
@interface SessionState {}
- /** Defined in biometrics.proto */
- @IntDef({
- MULTI_SENSOR_STATE_UNKNOWN,
- MULTI_SENSOR_STATE_FACE_SCANNING,
- MULTI_SENSOR_STATE_FP_SCANNING})
- @Retention(RetentionPolicy.SOURCE)
- @interface MultiSensorState {}
-
/**
* Notify the holder of the AuthSession that the caller/client's binder has died. The
* holder (BiometricService) should schedule {@link AuthSession#onClientDied()} to be run
@@ -119,7 +107,7 @@
private final Context mContext;
private final IStatusBarService mStatusBarService;
- private final IBiometricSysuiReceiver mSysuiReceiver;
+ @VisibleForTesting final IBiometricSysuiReceiver mSysuiReceiver;
private final KeyStore mKeyStore;
private final Random mRandom;
private final ClientDeathReceiver mClientDeathReceiver;
@@ -133,7 +121,7 @@
private final long mRequestId;
private final long mOperationId;
private final int mUserId;
- private final IBiometricSensorReceiver mSensorReceiver;
+ @VisibleForTesting final IBiometricSensorReceiver mSensorReceiver;
// Original receiver from BiometricPrompt.
private final IBiometricServiceReceiver mClientReceiver;
private final String mOpPackageName;
@@ -143,10 +131,10 @@
// The current state, which can be either idle, called, or started
private @SessionState int mState = STATE_AUTH_IDLE;
private @BiometricMultiSensorMode int mMultiSensorMode;
- private @MultiSensorState int mMultiSensorState;
private int[] mSensors;
// TODO(b/197265902): merge into state
private boolean mCancelled;
+ private int mAuthenticatedSensorId = -1;
// For explicit confirmation, do not send to keystore until the user has confirmed
// the authentication.
private byte[] mTokenEscrow;
@@ -232,8 +220,16 @@
}
}
- private void setSensorsToStateWaitingForCookie() throws RemoteException {
+ private void setSensorsToStateWaitingForCookie(boolean isTryAgain) throws RemoteException {
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ @BiometricSensor.SensorState final int state = sensor.getSensorState();
+ if (isTryAgain
+ && state != BiometricSensor.STATE_STOPPED
+ && state != BiometricSensor.STATE_CANCELING) {
+ Slog.d(TAG, "Skip retry because sensor: " + sensor.id + " is: " + state);
+ continue;
+ }
+
final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
final boolean requireConfirmation = isConfirmationRequired(sensor);
@@ -254,7 +250,6 @@
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mSensors = new int[0];
mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
- mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
mStatusBarService.showAuthenticationDialog(
mPromptInfo,
@@ -269,7 +264,7 @@
mMultiSensorMode);
} else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
// Some combination of biometric or biometric|credential is requested
- setSensorsToStateWaitingForCookie();
+ setSensorsToStateWaitingForCookie(false /* isTryAgain */);
mState = STATE_AUTH_CALLED;
} else {
// No authenticators requested. This should never happen - an exception should have
@@ -283,6 +278,10 @@
Slog.w(TAG, "Received cookie but already cancelled (ignoring): " + cookie);
return;
}
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onCookieReceived after successful auth");
+ return;
+ }
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
sensor.goToStateCookieReturnedIfCookieMatches(cookie);
@@ -307,7 +306,6 @@
}
mMultiSensorMode = getMultiSensorModeForNewSession(
mPreAuthInfo.eligibleSensors);
- mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
mStatusBarService.showAuthenticationDialog(mPromptInfo,
mSysuiReceiver,
@@ -381,9 +379,8 @@
// sending the final error callback to the application.
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
try {
- final boolean shouldCancel = filter.apply(sensor);
- Slog.d(TAG, "sensorId: " + sensor.id + ", shouldCancel: " + shouldCancel);
- if (shouldCancel) {
+ if (filter.apply(sensor)) {
+ Slog.d(TAG, "Cancelling sensorId: " + sensor.id);
sensor.goToStateCancelling(mToken, mOpPackageName, mRequestId);
}
} catch (RemoteException e) {
@@ -412,10 +409,16 @@
}
}
+ // do not propagate the error and let onAuthenticationSucceeded handle the new state
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onErrorReceived after successful auth (ignoring)");
+ return false;
+ }
+
mErrorEscrow = error;
mVendorCodeEscrow = vendorCode;
- final @BiometricAuthenticator.Modality int modality = sensorIdToModality(sensorId);
+ @Modality final int modality = sensorIdToModality(sensorId);
switch (mState) {
case STATE_AUTH_CALLED: {
@@ -430,7 +433,6 @@
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
- mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
mSensors = new int[0];
mStatusBarService.showAuthenticationDialog(
@@ -468,12 +470,6 @@
return true;
} else {
mState = STATE_ERROR_PENDING_SYSUI;
- if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT
- && mMultiSensorState == MULTI_SENSOR_STATE_FACE_SCANNING) {
- // wait for the UI to signal when modality should switch
- Slog.d(TAG, "onErrorReceived: waiting for modality switch callback");
- mMultiSensorState = MULTI_SENSOR_STATE_SWITCHING;
- }
mStatusBarService.onBiometricError(modality, error, vendorCode);
}
break;
@@ -505,6 +501,11 @@
}
void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onAcquired after successful auth");
+ return;
+ }
+
final String message = getAcquiredMessageForSensor(sensorId, acquiredInfo, vendorCode);
Slog.d(TAG, "sensorId: " + sensorId + " acquiredInfo: " + acquiredInfo
+ " message: " + message);
@@ -520,6 +521,10 @@
}
void onSystemEvent(int event) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onSystemEvent after successful auth");
+ return;
+ }
if (!mPromptInfo.isReceiveSystemEvents()) {
return;
}
@@ -538,53 +543,35 @@
mState = STATE_AUTH_STARTED_UI_SHOWING;
- if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
- mMultiSensorState = MULTI_SENSOR_STATE_FACE_SCANNING;
- } else {
- startFingerprintSensorsNow();
- }
- }
-
- // call anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
- // fingerprint sensor (i.e. face auth has failed or is not available)
- void onStartFingerprint() {
- if (mMultiSensorMode != BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
- Slog.e(TAG, "onStartFingerprint, unexpected mode: " + mMultiSensorMode);
- return;
- }
-
- if (mState != STATE_AUTH_STARTED
- && mState != STATE_AUTH_STARTED_UI_SHOWING
- && mState != STATE_AUTH_PAUSED
- && mState != STATE_ERROR_PENDING_SYSUI) {
- Slog.w(TAG, "onStartFingerprint, started from unexpected state: " + mState);
- }
-
- mMultiSensorState = MULTI_SENSOR_STATE_FP_SCANNING;
- startFingerprintSensorsNow();
- }
-
- // unguarded helper for the above methods only
- private void startFingerprintSensorsNow() {
startAllPreparedFingerprintSensors();
mState = STATE_AUTH_STARTED_UI_SHOWING;
}
void onTryAgainPressed() {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onTryAgainPressed after successful auth");
+ return;
+ }
+
if (mState != STATE_AUTH_PAUSED) {
Slog.w(TAG, "onTryAgainPressed, state: " + mState);
}
try {
- setSensorsToStateWaitingForCookie();
+ setSensorsToStateWaitingForCookie(true /* isTryAgain */);
mState = STATE_AUTH_PAUSED_RESUMING;
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException: " + e);
}
}
- void onAuthenticationSucceeded(int sensorId, boolean strong,
- byte[] token) {
+ void onAuthenticationSucceeded(int sensorId, boolean strong, byte[] token) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onAuthenticationSucceeded after successful auth");
+ return;
+ }
+
+ mAuthenticatedSensorId = sensorId;
if (strong) {
mTokenEscrow = token;
} else {
@@ -596,7 +583,7 @@
try {
// Notify SysUI that the biometric has been authenticated. SysUI already knows
// the implicit/explicit state and will react accordingly.
- mStatusBarService.onBiometricAuthenticated();
+ mStatusBarService.onBiometricAuthenticated(sensorIdToModality(sensorId));
final boolean requireConfirmation = isConfirmationRequiredByAnyEligibleSensor();
@@ -609,20 +596,22 @@
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
+
+ cancelAllSensors(sensor -> sensor.id != sensorId);
}
- void onAuthenticationRejected() {
- try {
- mStatusBarService.onBiometricError(TYPE_NONE,
- BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */);
+ void onAuthenticationRejected(int sensorId) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onAuthenticationRejected after successful auth");
+ return;
+ }
- // TODO: This logic will need to be updated if BP is multi-modal
- if (hasPausableBiometric()) {
- // Pause authentication. onBiometricAuthenticated(false) causes the
- // dialog to show a "try again" button for passive modalities.
+ try {
+ mStatusBarService.onBiometricError(sensorIdToModality(sensorId),
+ BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */);
+ if (pauseSensorIfSupported(sensorId)) {
mState = STATE_AUTH_PAUSED;
}
-
mClientReceiver.onAuthenticationFailed();
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
@@ -630,15 +619,34 @@
}
void onAuthenticationTimedOut(int sensorId, int cookie, int error, int vendorCode) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onAuthenticationTimedOut after successful auth");
+ return;
+ }
+
try {
mStatusBarService.onBiometricError(sensorIdToModality(sensorId), error, vendorCode);
+ pauseSensorIfSupported(sensorId);
mState = STATE_AUTH_PAUSED;
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
}
+ private boolean pauseSensorIfSupported(int sensorId) {
+ if (sensorIdToModality(sensorId) == TYPE_FACE) {
+ cancelAllSensors(sensor -> sensor.id == sensorId);
+ return true;
+ }
+ return false;
+ }
+
void onDeviceCredentialPressed() {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onDeviceCredentialPressed after successful auth");
+ return;
+ }
+
// Cancel authentication. Skip the token/package check since we are cancelling
// from system server. The interface is permission protected so this is fine.
cancelAllSensors();
@@ -666,6 +674,10 @@
}
}
+ private boolean hasAuthenticated() {
+ return mAuthenticatedSensorId != -1;
+ }
+
private void logOnDialogDismissed(@BiometricPrompt.DismissedReason int reason) {
if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) {
// Explicit auth, authentication confirmed.
@@ -794,6 +806,11 @@
* @return true if this AuthSession is finished, e.g. should be set to null
*/
boolean onCancelAuthSession(boolean force) {
+ if (hasAuthenticated()) {
+ Slog.d(TAG, "onCancelAuthSession after successful auth");
+ return true;
+ }
+
mCancelled = true;
final boolean authStarted = mState == STATE_AUTH_CALLED
@@ -848,15 +865,6 @@
return remainingCookies == 0;
}
- private boolean hasPausableBiometric() {
- for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
- if (sensor.modality == TYPE_FACE) {
- return true;
- }
- }
- return false;
- }
-
@SessionState int getState() {
return mState;
}
@@ -919,7 +927,7 @@
}
if (hasFace && hasFingerprint) {
- return BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
+ return BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
}
return BIOMETRIC_MULTI_SENSOR_DEFAULT;
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 0333c3e..7166783 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -131,8 +131,10 @@
void goToStateCancelling(IBinder token, String opPackageName, long requestId)
throws RemoteException {
- impl.cancelAuthenticationFromService(token, opPackageName, requestId);
- mSensorState = STATE_CANCELING;
+ if (mSensorState != STATE_CANCELING) {
+ impl.cancelAuthenticationFromService(token, opPackageName, requestId);
+ mSensorState = STATE_CANCELING;
+ }
}
void goToStoppedStateIfCookieMatches(int cookie, int error) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 758cf7a..0d9b754 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -55,7 +55,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -84,6 +83,7 @@
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
/**
* System service that arbitrates the modality for BiometricPrompt to use.
@@ -92,22 +92,6 @@
static final String TAG = "BiometricService";
- private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
- private static final int MSG_ON_AUTHENTICATION_REJECTED = 3;
- private static final int MSG_ON_ERROR = 4;
- private static final int MSG_ON_ACQUIRED = 5;
- private static final int MSG_ON_DISMISSED = 6;
- private static final int MSG_ON_TRY_AGAIN_PRESSED = 7;
- private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8;
- private static final int MSG_AUTHENTICATE = 9;
- private static final int MSG_CANCEL_AUTHENTICATION = 10;
- private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
- private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
- private static final int MSG_ON_SYSTEM_EVENT = 13;
- private static final int MSG_CLIENT_DIED = 14;
- private static final int MSG_ON_DIALOG_ANIMATED_IN = 15;
- private static final int MSG_ON_START_FINGERPRINT_NOW = 16;
-
private final Injector mInjector;
private final DevicePolicyManager mDevicePolicyManager;
@VisibleForTesting
@@ -116,7 +100,7 @@
final SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
private final Random mRandom = new Random();
- @NonNull private final AtomicLong mRequestCounter;
+ @NonNull private final Supplier<Long> mRequestCounter;
@VisibleForTesting
IStatusBarService mStatusBarService;
@@ -128,133 +112,13 @@
// Get and cache the available biometric authenticators and their associated info.
final ArrayList<BiometricSensor> mSensors = new ArrayList<>();
+ @VisibleForTesting
BiometricStrengthController mBiometricStrengthController;
// The current authentication session, null if idle/done.
@VisibleForTesting
- AuthSession mCurrentAuthSession;
-
- @VisibleForTesting
- final Handler mHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ON_AUTHENTICATION_SUCCEEDED: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleAuthenticationSucceeded(
- args.argi1 /* sensorId */,
- (byte[]) args.arg1 /* token */);
- args.recycle();
- break;
- }
-
- case MSG_ON_AUTHENTICATION_REJECTED: {
- handleAuthenticationRejected();
- break;
- }
-
- case MSG_ON_ERROR: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleOnError(
- args.argi1 /* sensorId */,
- args.argi2 /* cookie */,
- args.argi3 /* error */,
- args.argi4 /* vendorCode */);
- args.recycle();
- break;
- }
-
- case MSG_ON_ACQUIRED: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleOnAcquired(
- args.argi1 /* sensorId */,
- args.argi2 /* acquiredInfo */,
- args.argi3 /* vendorCode */);
- args.recycle();
- break;
- }
-
- case MSG_ON_DISMISSED: {
- handleOnDismissed(msg.arg1, (byte[]) msg.obj);
- break;
- }
-
- case MSG_ON_TRY_AGAIN_PRESSED: {
- handleOnTryAgainPressed();
- break;
- }
-
- case MSG_ON_READY_FOR_AUTHENTICATION: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleOnReadyForAuthentication(
- args.argi1 /* cookie */);
- args.recycle();
- break;
- }
-
- case MSG_AUTHENTICATE: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleAuthenticate(
- (IBinder) args.arg1 /* token */,
- (long) args.arg6 /* requestId */,
- (long) args.arg2 /* operationId */,
- args.argi1 /* userid */,
- (IBiometricServiceReceiver) args.arg3 /* receiver */,
- (String) args.arg4 /* opPackageName */,
- (PromptInfo) args.arg5 /* promptInfo */);
- args.recycle();
- break;
- }
-
- case MSG_CANCEL_AUTHENTICATION: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleCancelAuthentication((long) args.arg3 /* requestId */);
- args.recycle();
- break;
- }
-
- case MSG_ON_AUTHENTICATION_TIMED_OUT: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleAuthenticationTimedOut(
- args.argi1 /* sensorId */,
- args.argi2 /* cookie */,
- args.argi3 /* error */,
- args.argi4 /* vendorCode */);
- args.recycle();
- break;
- }
-
- case MSG_ON_DEVICE_CREDENTIAL_PRESSED: {
- handleOnDeviceCredentialPressed();
- break;
- }
-
- case MSG_ON_SYSTEM_EVENT: {
- handleOnSystemEvent((int) msg.obj);
- break;
- }
-
- case MSG_CLIENT_DIED: {
- handleClientDied();
- break;
- }
-
- case MSG_ON_DIALOG_ANIMATED_IN: {
- handleOnDialogAnimatedIn();
- break;
- }
-
- case MSG_ON_START_FINGERPRINT_NOW: {
- handleOnStartFingerprintNow();
- break;
- }
-
- default:
- Slog.e(TAG, "Unknown message: " + msg);
- break;
- }
- }
- };
+ AuthSession mAuthSession;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
/**
* Tracks authenticatorId invalidation. For more details, see
@@ -552,93 +416,74 @@
}
// Receives events from individual biometric sensors.
- @VisibleForTesting
- final IBiometricSensorReceiver mBiometricSensorReceiver = new IBiometricSensorReceiver.Stub() {
- @Override
- public void onAuthenticationSucceeded(int sensorId, byte[] token) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = sensorId;
- args.arg1 = token;
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_SUCCEEDED, args).sendToTarget();
- }
-
- @Override
- public void onAuthenticationFailed(int sensorId) {
- Slog.v(TAG, "onAuthenticationFailed");
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget();
- }
-
- @Override
- public void onError(int sensorId, int cookie, @BiometricConstants.Errors int error,
- int vendorCode) {
- // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
- // soft errors and we should allow the user to try authenticating again instead of
- // dismissing BiometricPrompt.
- if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = sensorId;
- args.argi2 = cookie;
- args.argi3 = error;
- args.argi4 = vendorCode;
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, args).sendToTarget();
- } else {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = sensorId;
- args.argi2 = cookie;
- args.argi3 = error;
- args.argi4 = vendorCode;
- mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
+ private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) {
+ return new IBiometricSensorReceiver.Stub() {
+ @Override
+ public void onAuthenticationSucceeded(int sensorId, byte[] token) {
+ mHandler.post(() -> handleAuthenticationSucceeded(requestId, sensorId, token));
}
- }
- @Override
- public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = sensorId;
- args.argi2 = acquiredInfo;
- args.argi3 = vendorCode;
- mHandler.obtainMessage(MSG_ON_ACQUIRED, args).sendToTarget();
- }
- };
+ @Override
+ public void onAuthenticationFailed(int sensorId) {
+ Slog.v(TAG, "onAuthenticationFailed");
+ mHandler.post(() -> handleAuthenticationRejected(requestId, sensorId));
+ }
- final IBiometricSysuiReceiver mSysuiReceiver = new IBiometricSysuiReceiver.Stub() {
- @Override
- public void onDialogDismissed(@BiometricPrompt.DismissedReason int reason,
- @Nullable byte[] credentialAttestation) {
- mHandler.obtainMessage(MSG_ON_DISMISSED,
- reason,
- 0 /* arg2 */,
- credentialAttestation /* obj */).sendToTarget();
- }
+ @Override
+ public void onError(int sensorId, int cookie, @BiometricConstants.Errors int error,
+ int vendorCode) {
+ // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
+ // soft errors and we should allow the user to try authenticating again instead of
+ // dismissing BiometricPrompt.
+ if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
+ mHandler.post(() -> handleAuthenticationTimedOut(
+ requestId, sensorId, cookie, error, vendorCode));
+ } else {
+ mHandler.post(() -> handleOnError(
+ requestId, sensorId, cookie, error, vendorCode));
+ }
+ }
- @Override
- public void onTryAgainPressed() {
- mHandler.sendEmptyMessage(MSG_ON_TRY_AGAIN_PRESSED);
- }
+ @Override
+ public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) {
+ mHandler.post(() -> handleOnAcquired(
+ requestId, sensorId, acquiredInfo, vendorCode));
+ }
+ };
+ }
- @Override
- public void onDeviceCredentialPressed() {
- mHandler.sendEmptyMessage(MSG_ON_DEVICE_CREDENTIAL_PRESSED);
- }
+ private IBiometricSysuiReceiver createSysuiReceiver(final long requestId) {
+ return new IBiometricSysuiReceiver.Stub() {
+ @Override
+ public void onDialogDismissed(@BiometricPrompt.DismissedReason int reason,
+ @Nullable byte[] credentialAttestation) {
+ mHandler.post(() -> handleOnDismissed(requestId, reason, credentialAttestation));
+ }
- @Override
- public void onSystemEvent(int event) {
- mHandler.obtainMessage(MSG_ON_SYSTEM_EVENT, event).sendToTarget();
- }
+ @Override
+ public void onTryAgainPressed() {
+ mHandler.post(() -> handleOnTryAgainPressed(requestId));
+ }
- @Override
- public void onDialogAnimatedIn() {
- mHandler.obtainMessage(MSG_ON_DIALOG_ANIMATED_IN).sendToTarget();
- }
+ @Override
+ public void onDeviceCredentialPressed() {
+ mHandler.post(() -> handleOnDeviceCredentialPressed(requestId));
+ }
- @Override
- public void onStartFingerprintNow() {
- mHandler.obtainMessage(MSG_ON_START_FINGERPRINT_NOW).sendToTarget();
- }
- };
+ @Override
+ public void onSystemEvent(int event) {
+ mHandler.post(() -> handleOnSystemEvent(requestId, event));
+ }
- private final AuthSession.ClientDeathReceiver mClientDeathReceiver = () -> {
- mHandler.sendEmptyMessage(MSG_CLIENT_DIED);
+ @Override
+ public void onDialogAnimatedIn() {
+ mHandler.post(() -> handleOnDialogAnimatedIn(requestId));
+ }
+ };
+ }
+
+ private AuthSession.ClientDeathReceiver createClientDeathReceiver(final long requestId) {
+ return () -> mHandler.post(() -> handleClientDied(requestId));
};
/**
@@ -679,12 +524,10 @@
}
@Override // Binder call
- public void onReadyForAuthentication(int cookie) {
+ public void onReadyForAuthentication(long requestId, int cookie) {
checkInternalPermission();
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = cookie;
- mHandler.obtainMessage(MSG_ON_READY_FOR_AUTHENTICATION, args).sendToTarget();
+ mHandler.post(() -> handleOnReadyForAuthentication(requestId, cookie));
}
@Override // Binder call
@@ -711,18 +554,9 @@
}
}
- final long requestId = mRequestCounter.incrementAndGet();
-
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = token;
- args.arg2 = operationId;
- args.argi1 = userId;
- args.arg3 = receiver;
- args.arg4 = opPackageName;
- args.arg5 = promptInfo;
- args.arg6 = requestId;
-
- mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
+ final long requestId = mRequestCounter.get();
+ mHandler.post(() -> handleAuthenticate(
+ token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
return requestId;
}
@@ -736,7 +570,7 @@
args.arg2 = opPackageName;
args.arg3 = requestId;
- mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
+ mHandler.post(() -> handleCancelAuthentication(requestId));
}
@Override // Binder call
@@ -1002,8 +836,7 @@
Slog.d(TAG, "ClearSchedulerBuffer: " + clearSchedulerBuffer);
final ProtoOutputStream proto = new ProtoOutputStream(fd);
proto.write(BiometricServiceStateProto.AUTH_SESSION_STATE,
- mCurrentAuthSession != null ? mCurrentAuthSession.getState()
- : STATE_AUTH_IDLE);
+ mAuthSession != null ? mAuthSession.getState() : STATE_AUTH_IDLE);
for (BiometricSensor sensor : mSensors) {
byte[] serviceState = sensor.impl
.dumpSensorServiceStateProto(clearSchedulerBuffer);
@@ -1128,8 +961,9 @@
CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0;
}
- public AtomicLong getRequestGenerator() {
- return new AtomicLong(0);
+ public Supplier<Long> getRequestGenerator() {
+ final AtomicLong generator = new AtomicLong(0);
+ return () -> generator.incrementAndGet();
}
}
@@ -1202,172 +1036,184 @@
return false;
}
- private void handleAuthenticationSucceeded(int sensorId, byte[] token) {
+ @Nullable
+ private AuthSession getAuthSessionIfCurrent(long requestId) {
+ final AuthSession session = mAuthSession;
+ if (session != null && session.getRequestId() == requestId) {
+ return session;
+ }
+ return null;
+ }
+
+ private void handleAuthenticationSucceeded(long requestId, int sensorId, byte[] token) {
Slog.v(TAG, "handleAuthenticationSucceeded(), sensorId: " + sensorId);
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
- if (mCurrentAuthSession == null) {
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
Slog.e(TAG, "handleAuthenticationSucceeded: AuthSession is null");
return;
}
- mCurrentAuthSession.onAuthenticationSucceeded(sensorId, isStrongBiometric(sensorId), token);
+ session.onAuthenticationSucceeded(sensorId, isStrongBiometric(sensorId), token);
}
- private void handleAuthenticationRejected() {
+ private void handleAuthenticationRejected(long requestId, int sensorId) {
Slog.v(TAG, "handleAuthenticationRejected()");
// Should never happen, log this to catch bad HAL behavior (e.g. auth rejected
// after user dismissed/canceled dialog).
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleAuthenticationRejected: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleAuthenticationRejected: AuthSession is not current");
return;
}
- mCurrentAuthSession.onAuthenticationRejected();
+ session.onAuthenticationRejected(sensorId);
}
- private void handleAuthenticationTimedOut(int sensorId, int cookie, int error, int vendorCode) {
+ private void handleAuthenticationTimedOut(long requestId, int sensorId, int cookie, int error,
+ int vendorCode) {
Slog.v(TAG, "handleAuthenticationTimedOut(), sensorId: " + sensorId
+ ", cookie: " + cookie
+ ", error: " + error
+ ", vendorCode: " + vendorCode);
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleAuthenticationTimedOut: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleAuthenticationTimedOut: AuthSession is not current");
return;
}
- mCurrentAuthSession.onAuthenticationTimedOut(sensorId, cookie, error, vendorCode);
+ session.onAuthenticationTimedOut(sensorId, cookie, error, vendorCode);
}
- private void handleOnError(int sensorId, int cookie, @BiometricConstants.Errors int error,
- int vendorCode) {
+ private void handleOnError(long requestId, int sensorId, int cookie,
+ @BiometricConstants.Errors int error, int vendorCode) {
Slog.d(TAG, "handleOnError() sensorId: " + sensorId
+ ", cookie: " + cookie
+ ", error: " + error
+ ", vendorCode: " + vendorCode);
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnError: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnError: AuthSession is not current");
return;
}
try {
- final boolean finished = mCurrentAuthSession
- .onErrorReceived(sensorId, cookie, error, vendorCode);
+ final boolean finished = session.onErrorReceived(sensorId, cookie, error, vendorCode);
if (finished) {
Slog.d(TAG, "handleOnError: AuthSession finished");
- mCurrentAuthSession = null;
+ mAuthSession = null;
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
}
- private void handleOnAcquired(int sensorId, int acquiredInfo, int vendorCode) {
+ private void handleOnAcquired(long requestId, int sensorId, int acquiredInfo, int vendorCode) {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "onAcquired: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "onAcquired: AuthSession is not current");
return;
}
- mCurrentAuthSession.onAcquired(sensorId, acquiredInfo, vendorCode);
+ session.onAcquired(sensorId, acquiredInfo, vendorCode);
}
- private void handleOnDismissed(@BiometricPrompt.DismissedReason int reason,
+ private void handleOnDismissed(long requestId, @BiometricPrompt.DismissedReason int reason,
@Nullable byte[] credentialAttestation) {
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "onDismissed: " + reason + ", AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.e(TAG, "onDismissed: " + reason + ", AuthSession is not current");
return;
}
- mCurrentAuthSession.onDialogDismissed(reason, credentialAttestation);
- mCurrentAuthSession = null;
+ session.onDialogDismissed(reason, credentialAttestation);
+ mAuthSession = null;
}
- private void handleOnTryAgainPressed() {
+ private void handleOnTryAgainPressed(long requestId) {
Slog.d(TAG, "onTryAgainPressed");
// No need to check permission, since it can only be invoked by SystemUI
// (or system server itself).
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnTryAgainPressed: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnTryAgainPressed: AuthSession is not current");
return;
}
- mCurrentAuthSession.onTryAgainPressed();
+ session.onTryAgainPressed();
}
- private void handleOnDeviceCredentialPressed() {
+ private void handleOnDeviceCredentialPressed(long requestId) {
Slog.d(TAG, "onDeviceCredentialPressed");
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnDeviceCredentialPressed: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnDeviceCredentialPressed: AuthSession is not current");
return;
}
- mCurrentAuthSession.onDeviceCredentialPressed();
+ session.onDeviceCredentialPressed();
}
- private void handleOnSystemEvent(int event) {
+ private void handleOnSystemEvent(long requestId, int event) {
Slog.d(TAG, "onSystemEvent: " + event);
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnSystemEvent: AuthSession is null");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnSystemEvent: AuthSession is not current");
return;
}
- mCurrentAuthSession.onSystemEvent(event);
+ session.onSystemEvent(event);
}
- private void handleClientDied() {
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleClientDied: AuthSession is null");
+ private void handleClientDied(long requestId) {
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleClientDied: AuthSession is not current");
return;
}
- Slog.e(TAG, "Session: " + mCurrentAuthSession);
- final boolean finished = mCurrentAuthSession.onClientDied();
+ Slog.e(TAG, "Session: " + session);
+ final boolean finished = session.onClientDied();
if (finished) {
- mCurrentAuthSession = null;
+ mAuthSession = null;
}
}
- private void handleOnDialogAnimatedIn() {
+ private void handleOnDialogAnimatedIn(long requestId) {
Slog.d(TAG, "handleOnDialogAnimatedIn");
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnDialogAnimatedIn: AuthSession is null");
+
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnDialogAnimatedIn: AuthSession is not current");
return;
}
- mCurrentAuthSession.onDialogAnimatedIn();
- }
-
- private void handleOnStartFingerprintNow() {
- Slog.d(TAG, "handleOnStartFingerprintNow");
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleOnStartFingerprintNow: AuthSession is null");
- return;
- }
-
- mCurrentAuthSession.onStartFingerprint();
+ session.onDialogAnimatedIn();
}
/**
* Invoked when each service has notified that its client is ready to be started. When
* all biometrics are ready, this invokes the SystemUI dialog through StatusBar.
*/
- private void handleOnReadyForAuthentication(int cookie) {
- if (mCurrentAuthSession == null) {
+ private void handleOnReadyForAuthentication(long requestId, int cookie) {
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
// Only should happen if a biometric was locked out when authenticate() was invoked.
// In that case, if device credentials are allowed, the UI is already showing. If not
// allowed, the error has already been returned to the caller.
- Slog.w(TAG, "handleOnReadyForAuthentication: AuthSession is null");
+ Slog.w(TAG, "handleOnReadyForAuthentication: AuthSession is not current");
return;
}
- mCurrentAuthSession.onCookieReceived(cookie);
+ session.onCookieReceived(cookie);
}
private void handleAuthenticate(IBinder token, long requestId, long operationId, int userId,
@@ -1428,47 +1274,41 @@
// No need to dismiss dialog / send error yet if we're continuing authentication, e.g.
// "Try again" is showing due to something like ERROR_TIMEOUT.
- if (mCurrentAuthSession != null) {
+ if (mAuthSession != null) {
// Forcefully cancel authentication. Dismiss the UI, and immediately send
// ERROR_CANCELED to the client. Note that we should/will ignore HAL ERROR_CANCELED.
// Expect to see some harmless "unknown cookie" errors.
- Slog.w(TAG, "Existing AuthSession: " + mCurrentAuthSession);
- mCurrentAuthSession.onCancelAuthSession(true /* force */);
- mCurrentAuthSession = null;
+ Slog.w(TAG, "Existing AuthSession: " + mAuthSession);
+ mAuthSession.onCancelAuthSession(true /* force */);
+ mAuthSession = null;
}
final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
- mCurrentAuthSession = new AuthSession(getContext(), mStatusBarService, mSysuiReceiver,
- mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, requestId,
- operationId, userId, mBiometricSensorReceiver, receiver, opPackageName, promptInfo,
- debugEnabled, mInjector.getFingerprintSensorProperties(getContext()));
+ mAuthSession = new AuthSession(getContext(), mStatusBarService,
+ createSysuiReceiver(requestId), mKeyStore, mRandom,
+ createClientDeathReceiver(requestId), preAuthInfo, token, requestId,
+ operationId, userId, createBiometricSensorReceiver(requestId), receiver,
+ opPackageName, promptInfo, debugEnabled,
+ mInjector.getFingerprintSensorProperties(getContext()));
try {
- mCurrentAuthSession.goToInitialState();
+ mAuthSession.goToInitialState();
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
}
private void handleCancelAuthentication(long requestId) {
- if (mCurrentAuthSession == null) {
- Slog.e(TAG, "handleCancelAuthentication: AuthSession is null");
- return;
- }
- if (mCurrentAuthSession.getRequestId() != requestId) {
- // TODO: actually cancel the operation
- // This can happen if the operation has been queued, but is cancelled before
- // it reaches the head of the scheduler. Consider it a programming error for now
- // and ignore it.
- Slog.e(TAG, "handleCancelAuthentication: AuthSession mismatch current requestId: "
- + mCurrentAuthSession.getRequestId() + " cancel for: " + requestId
- + " (ignoring cancellation)");
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleCancelAuthentication: AuthSession is not current");
+ // TODO: actually cancel the operation?
return;
}
- final boolean finished = mCurrentAuthSession.onCancelAuthSession(false /* force */);
+ final boolean finished = session.onCancelAuthSession(false /* force */);
if (finished) {
Slog.d(TAG, "handleCancelAuthentication: AuthSession finished");
- mCurrentAuthSession = null;
+ mAuthSession = null;
}
}
@@ -1491,7 +1331,7 @@
pw.println(" " + sensor);
}
pw.println();
- pw.println("CurrentSession: " + mCurrentAuthSession);
+ pw.println("CurrentSession: " + mAuthSession);
pw.println();
pw.println("CoexCoordinator: " + CoexCoordinator.getInstance().toString());
pw.println();
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 54b79e1..6d68772 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -86,6 +86,7 @@
private long mStartTimeMs;
private boolean mAuthAttempted;
+ private boolean mAuthSuccess = false;
// 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.
@@ -237,6 +238,7 @@
"Successful background authentication!");
}
+ mAuthSuccess = true;
markAlreadyDone();
if (mTaskStackListener != null) {
@@ -502,6 +504,11 @@
return mAuthAttempted;
}
+ /** If an auth attempt completed successfully. */
+ public boolean wasAuthSuccessful() {
+ return mAuthSuccess;
+ }
+
protected int getShowOverlayReason() {
if (isKeyguard()) {
return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
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 1a6da94..d0ec447 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -316,7 +316,8 @@
}
} else {
try {
- mBiometricService.onReadyForAuthentication(cookie);
+ mBiometricService.onReadyForAuthentication(
+ mCurrentOperation.getClientMonitor().getRequestId(), cookie);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index f1c786b49..46d863d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -123,7 +123,8 @@
}
}
- void onRemoved(BiometricAuthenticator.Identifier identifier, int remaining)
+ /** Called when a user has been removed. */
+ public void onRemoved(BiometricAuthenticator.Identifier identifier, int remaining)
throws RemoteException {
if (mFaceServiceReceiver != null) {
mFaceServiceReceiver.onRemoved((Face) identifier, remaining);
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 25d4a38..5aa9b79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
@@ -173,18 +173,13 @@
}
// SensorType to AuthenticationClient map
- private final Map<Integer, AuthenticationClient<?>> mClientMap;
- @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths;
+ private final Map<Integer, AuthenticationClient<?>> mClientMap = new HashMap<>();
+ @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths = new LinkedList<>();
private boolean mAdvancedLogicEnabled;
private boolean mFaceHapticDisabledWhenNonBypass;
- private final Handler mHandler;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
- private CoexCoordinator() {
- // Singleton
- mClientMap = new HashMap<>();
- mSuccessfulAuths = new LinkedList<>();
- mHandler = new Handler(Looper.getMainLooper());
- }
+ private CoexCoordinator() {}
public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType,
@NonNull AuthenticationClient<?> client) {
@@ -221,8 +216,14 @@
public void onAuthenticationSucceeded(long currentTimeMillis,
@NonNull AuthenticationClient<?> client,
@NonNull Callback callback) {
+ final boolean isUsingSingleModality = isSingleAuthOnly(client);
+
if (client.isBiometricPrompt()) {
- callback.sendHapticFeedback();
+ if (!isUsingSingleModality && hasMultipleSuccessfulAuthentications()) {
+ // only send feedback on the first one
+ } else {
+ callback.sendHapticFeedback();
+ }
// For BP, BiometricService will add the authToken to Keystore.
callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */);
callback.handleLifecycleAfterAuth();
@@ -234,7 +235,7 @@
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
callback.handleLifecycleAfterAuth();
} else if (mAdvancedLogicEnabled && client.isKeyguard()) {
- if (isSingleAuthOnly(client)) {
+ if (isUsingSingleModality) {
// Single sensor authentication
callback.sendHapticFeedback();
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
@@ -295,10 +296,10 @@
@NonNull AuthenticationClient<?> client,
@LockoutTracker.LockoutMode int lockoutMode,
@NonNull Callback callback) {
- final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
+ final boolean isUsingSingleModality = isSingleAuthOnly(client);
- if (keyguardAdvancedLogic) {
- if (isSingleAuthOnly(client)) {
+ if (mAdvancedLogicEnabled && client.isKeyguard()) {
+ if (isUsingSingleModality) {
callback.sendHapticFeedback();
callback.handleLifecycleAfterAuth();
} else {
@@ -319,8 +320,7 @@
// also done now.
callback.sendHapticFeedback();
callback.handleLifecycleAfterAuth();
- }
- else {
+ } else {
// UDFPS auth has never been attempted.
if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
Slog.w(TAG, "Skipping face reject haptic");
@@ -360,6 +360,11 @@
callback.handleLifecycleAfterAuth();
}
}
+ } else if (client.isBiometricPrompt() && !isUsingSingleModality) {
+ if (!isCurrentFaceAuth(client)) {
+ callback.sendHapticFeedback();
+ }
+ callback.handleLifecycleAfterAuth();
} else {
callback.sendHapticFeedback();
callback.handleLifecycleAfterAuth();
@@ -380,6 +385,8 @@
*/
public void onAuthenticationError(@NonNull AuthenticationClient<?> client,
@BiometricConstants.Errors int error, @NonNull ErrorCallback callback) {
+ final boolean isUsingSingleModality = isSingleAuthOnly(client);
+
// Figure out non-coex state
final boolean shouldUsuallyVibrate;
if (isCurrentFaceAuth(client)) {
@@ -401,25 +408,26 @@
}
// Figure out coex state
- final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
final boolean hapticSuppressedByCoex;
-
- if (keyguardAdvancedLogic) {
- if (isSingleAuthOnly(client)) {
+ if (mAdvancedLogicEnabled && client.isKeyguard()) {
+ if (isUsingSingleModality) {
hapticSuppressedByCoex = false;
} else {
hapticSuppressedByCoex = isCurrentFaceAuth(client)
&& !client.isKeyguardBypassEnabled();
}
+ } else if (client.isBiometricPrompt() && !isUsingSingleModality) {
+ hapticSuppressedByCoex = isCurrentFaceAuth(client);
} else {
hapticSuppressedByCoex = false;
}
// Combine and send feedback if appropriate
- Slog.d(TAG, "shouldUsuallyVibrate: " + shouldUsuallyVibrate
- + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
if (shouldUsuallyVibrate && !hapticSuppressedByCoex) {
callback.sendHapticFeedback();
+ } else {
+ Slog.v(TAG, "no haptic shouldUsuallyVibrate: " + shouldUsuallyVibrate
+ + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
}
}
@@ -504,6 +512,19 @@
return true;
}
+ private boolean hasMultipleSuccessfulAuthentications() {
+ int count = 0;
+ for (AuthenticationClient<?> c : mClientMap.values()) {
+ if (c.wasAuthSuccessful()) {
+ count++;
+ }
+ if (count > 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 07ce841..e0d5194 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -71,6 +72,24 @@
@Override
public void onRemoved(@NonNull BiometricAuthenticator.Identifier identifier, int remaining) {
+ // This happens when we have failed to remove a biometric.
+ if (identifier == null) {
+ Slog.e(TAG, "identifier was null, skipping onRemove()");
+ try {
+ if (getListener() != null) {
+ getListener().onError(getSensorId(), getCookie(),
+ BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE,
+ 0 /* vendorCode */);
+ } else {
+ Slog.e(TAG, "Error, listener was null, not sending onError callback");
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to send error to client for onRemoved", e);
+ }
+ mCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
Slog.d(TAG, "onRemoved: " + identifier.getBiometricId() + " remaining: " + remaining);
mBiometricUtils.removeBiometricForUser(getContext(), getTargetUserId(),
identifier.getBiometricId());
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9bfdd68..66e9da0 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2579,7 +2579,7 @@
boolean getAllowNonNativeRefreshRateOverride() {
return DisplayProperties
- .debug_allow_non_native_refresh_rate_override().orElse(false);
+ .debug_allow_non_native_refresh_rate_override().orElse(true);
}
@NonNull
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c15242a..140a28f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -144,6 +144,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.OptionalInt;
/*
* Wraps the C++ InputManager and provides its callbacks.
@@ -2915,48 +2916,17 @@
// Native callback
@SuppressWarnings("unused")
- private void notifyWindowUnresponsive(IBinder token, String reason) {
- int gestureMonitorPid = -1;
- synchronized (mInputMonitors) {
- final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
- if (gestureMonitor != null) {
- gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
- }
- }
- if (gestureMonitorPid != -1) {
- mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(gestureMonitorPid, reason);
- return;
- }
- mWindowManagerCallbacks.notifyWindowUnresponsive(token, reason);
+ private void notifyWindowUnresponsive(IBinder token, int pid, boolean isPidValid,
+ String reason) {
+ mWindowManagerCallbacks.notifyWindowUnresponsive(token,
+ isPidValid ? OptionalInt.of(pid) : OptionalInt.empty(), reason);
}
// Native callback
@SuppressWarnings("unused")
- private void notifyMonitorUnresponsive(int pid, String reason) {
- mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(pid, reason);
- }
-
- // Native callback
- @SuppressWarnings("unused")
- private void notifyWindowResponsive(IBinder token) {
- int gestureMonitorPid = -1;
- synchronized (mInputMonitors) {
- final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
- if (gestureMonitor != null) {
- gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
- }
- }
- if (gestureMonitorPid != -1) {
- mWindowManagerCallbacks.notifyGestureMonitorResponsive(gestureMonitorPid);
- return;
- }
- mWindowManagerCallbacks.notifyWindowResponsive(token);
- }
-
- // Native callback
- @SuppressWarnings("unused")
- private void notifyMonitorResponsive(int pid) {
- mWindowManagerCallbacks.notifyGestureMonitorResponsive(pid);
+ private void notifyWindowResponsive(IBinder token, int pid, boolean isPidValid) {
+ mWindowManagerCallbacks.notifyWindowResponsive(token,
+ isPidValid ? OptionalInt.of(pid) : OptionalInt.empty());
}
// Native callback.
@@ -3329,34 +3299,22 @@
void notifyNoFocusedWindowAnr(InputApplicationHandle applicationHandle);
/**
- * Notify the window manager about a gesture monitor that is unresponsive.
- *
- * @param pid the pid of the gesture monitor process
- * @param reason the reason why this connection is unresponsive
- */
- void notifyGestureMonitorUnresponsive(int pid, @NonNull String reason);
-
- /**
* Notify the window manager about a window that is unresponsive.
*
* @param token the token that can be used to look up the window
+ * @param pid the pid of the window owner, if known
* @param reason the reason why this connection is unresponsive
*/
- void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull String reason);
-
- /**
- * Notify the window manager about a gesture monitor that has become responsive.
- *
- * @param pid the pid of the gesture monitor process
- */
- void notifyGestureMonitorResponsive(int pid);
+ void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
+ @NonNull String reason);
/**
* Notify the window manager about a window that has become responsive.
*
* @param token the token that can be used to look up the window
+ * @param pid the pid of the window owner, if known
*/
- void notifyWindowResponsive(@NonNull IBinder token);
+ void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid);
/**
* This callback is invoked when an event first arrives to InputDispatcher and before it is
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index a102406..1203769 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -262,7 +262,7 @@
long nextAlarmTime = strongAuthTime + dpm.getRequiredStrongAuthTimeout(null, userId);
// schedule a new alarm listener for the user
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextAlarmTime,
STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm, mHandler);
}
@@ -303,7 +303,7 @@
alarm = new NonStrongBiometricTimeoutAlarmListener(userId);
mNonStrongBiometricTimeoutAlarmListener.put(userId, alarm);
// schedule a new alarm listener for the user
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextAlarmTime,
NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm, mHandler);
}
@@ -394,7 +394,7 @@
}
// schedule a new alarm listener for the user
if (DEBUG) Slog.d(TAG, "Schedule a new alarm for non-strong biometric idle timeout");
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextAlarmTime,
NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm, mHandler);
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 30ac1b8..0c9855b 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2161,8 +2161,8 @@
private String[] getPackagesForUidInternal(int uid, int callingUid) {
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final int userId = UserHandle.getUserId(uid);
- if (Process.isSupplemental(uid)) {
- uid = getSupplementalProcessUid();
+ if (Process.isSdkSandboxUid(uid)) {
+ uid = getBaseSdkSandboxUid();
}
final int appId = UserHandle.getAppId(uid);
return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp);
@@ -2401,9 +2401,9 @@
}
public final boolean isCallerSameApp(String packageName, int uid) {
- if (Process.isSupplemental(uid)) {
+ if (Process.isSdkSandboxUid(uid)) {
return (packageName != null
- && packageName.equals(mService.getSupplementalProcessPackageName()));
+ && packageName.equals(mService.getSdkSandboxPackageName()));
}
AndroidPackage pkg = mPackages.get(packageName);
return pkg != null
@@ -2812,8 +2812,8 @@
"MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission");
} else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0
&& isCallerSystemUser
- && mUserManager.hasManagedProfile(UserHandle.USER_SYSTEM)) {
- // If the caller wants all packages and has a restricted profile associated with it,
+ && mUserManager.hasProfile(UserHandle.USER_SYSTEM)) {
+ // If the caller wants all packages and has a profile associated with it,
// then match all users. This is to make sure that launchers that need to access
//work
// profile apps don't start breaking. TODO: Remove this hack when launchers stop
@@ -4326,8 +4326,8 @@
if (getInstantAppPackageName(callingUid) != null) {
return null;
}
- if (Process.isSupplemental(uid)) {
- uid = getSupplementalProcessUid();
+ if (Process.isSdkSandboxUid(uid)) {
+ uid = getBaseSdkSandboxUid();
}
final int callingUserId = UserHandle.getUserId(callingUid);
final int appId = UserHandle.getAppId(uid);
@@ -4362,8 +4362,8 @@
final String[] names = new String[uids.length];
for (int i = uids.length - 1; i >= 0; i--) {
int uid = uids[i];
- if (Process.isSupplemental(uid)) {
- uid = getSupplementalProcessUid();
+ if (Process.isSdkSandboxUid(uid)) {
+ uid = getBaseSdkSandboxUid();
}
final int appId = UserHandle.getAppId(uid);
final Object obj = mSettings.getSettingBase(appId);
@@ -4411,8 +4411,8 @@
if (getInstantAppPackageName(callingUid) != null) {
return 0;
}
- if (Process.isSupplemental(uid)) {
- uid = getSupplementalProcessUid();
+ if (Process.isSdkSandboxUid(uid)) {
+ uid = getBaseSdkSandboxUid();
}
final int callingUserId = UserHandle.getUserId(callingUid);
final int appId = UserHandle.getAppId(uid);
@@ -4439,8 +4439,8 @@
if (getInstantAppPackageName(callingUid) != null) {
return 0;
}
- if (Process.isSupplemental(uid)) {
- uid = getSupplementalProcessUid();
+ if (Process.isSdkSandboxUid(uid)) {
+ uid = getBaseSdkSandboxUid();
}
final int callingUserId = UserHandle.getUserId(callingUid);
final int appId = UserHandle.getAppId(uid);
@@ -4466,8 +4466,8 @@
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return false;
}
- if (Process.isSupplemental(uid)) {
- uid = getSupplementalProcessUid();
+ if (Process.isSdkSandboxUid(uid)) {
+ uid = getBaseSdkSandboxUid();
}
final int appId = UserHandle.getAppId(uid);
final Object obj = mSettings.getSettingBase(appId);
@@ -5597,8 +5597,8 @@
@Override
public int getUidTargetSdkVersion(int uid) {
- if (Process.isSupplemental(uid)) {
- uid = getSupplementalProcessUid();
+ if (Process.isSdkSandboxUid(uid)) {
+ uid = getBaseSdkSandboxUid();
}
final int appId = UserHandle.getAppId(uid);
final SettingBase settingBase = mSettings.getSettingBase(appId);
@@ -5628,8 +5628,8 @@
@Nullable
@Override
public ArrayMap<String, ProcessInfo> getProcessesForUid(int uid) {
- if (Process.isSupplemental(uid)) {
- uid = getSupplementalProcessUid();
+ if (Process.isSdkSandboxUid(uid)) {
+ uid = getBaseSdkSandboxUid();
}
final int appId = UserHandle.getAppId(uid);
final SettingBase settingBase = mSettings.getSettingBase(appId);
@@ -5661,8 +5661,8 @@
}
}
- private int getSupplementalProcessUid() {
- return getPackage(mService.getSupplementalProcessPackageName()).getUid();
+ private int getBaseSdkSandboxUid() {
+ return getPackage(mService.getSdkSandboxPackageName()).getUid();
}
@Nullable
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 53eb9cf..c832693 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -460,7 +460,8 @@
| DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
| DexoptOptions.DEXOPT_BOOT_COMPLETE
| (force ? DexoptOptions.DEXOPT_FORCE : 0);
- return performDexOpt(new DexoptOptions(packageName, compilerFilter, flags));
+ return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
+ compilerFilter, null /* splitName */, flags));
}
// Sort apps by importance for dexopt ordering. Important apps are given
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 54c2019..ceab925 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2893,9 +2893,13 @@
}
}
- final boolean deferInstallObserver = succeeded && update && !killApp;
+ final boolean deferInstallObserver = succeeded && update;
if (deferInstallObserver) {
- mPm.scheduleDeferredNoKillInstallObserver(res, installObserver);
+ if (killApp) {
+ mPm.scheduleDeferredPendingKillInstallObserver(res, installObserver);
+ } else {
+ mPm.scheduleDeferredNoKillInstallObserver(res, installObserver);
+ }
} else {
mPm.notifyInstallObserver(res, installObserver);
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index b028a2c..e8faca9 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -24,6 +24,7 @@
import static com.android.server.pm.PackageManagerService.DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD;
import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_INSTALL_OBSERVER;
import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_POST_DELETE;
+import static com.android.server.pm.PackageManagerService.DEFERRED_PENDING_KILL_INSTALL_OBSERVER;
import static com.android.server.pm.PackageManagerService.DOMAIN_VERIFICATION;
import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_STATUS;
import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_TIMEOUT;
@@ -126,10 +127,12 @@
}
}
} break;
- case DEFERRED_NO_KILL_INSTALL_OBSERVER: {
- String packageName = (String) msg.obj;
+ case DEFERRED_NO_KILL_INSTALL_OBSERVER:
+ case DEFERRED_PENDING_KILL_INSTALL_OBSERVER: {
+ final String packageName = (String) msg.obj;
if (packageName != null) {
- mPm.notifyInstallObserver(packageName);
+ final boolean killApp = msg.what == DEFERRED_PENDING_KILL_INSTALL_OBSERVER;
+ mPm.notifyInstallObserver(packageName, killApp);
}
} break;
case WRITE_SETTINGS: {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c05faf1..6fbad24 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -247,8 +247,8 @@
import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
import com.android.server.storage.DeviceStorageMonitorInternal;
-import com.android.server.supplementalprocess.SupplementalProcessManagerLocal;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.utils.Watchable;
@@ -840,6 +840,9 @@
private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
+ private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
+ mPendingKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
+
// Internal interface for permission manager
final PermissionManagerServiceInternal mPermissionManager;
@@ -887,9 +890,11 @@
static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
static final int DOMAIN_VERIFICATION = 27;
static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28;
+ static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER = 29;
static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
private static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
+ private static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS = 1000;
static final int WRITE_SETTINGS_DELAY = 10*1000; // 10 seconds
@@ -934,7 +939,7 @@
final @Nullable String mOverlayConfigSignaturePackage;
final @Nullable String mRecentsPackage;
final @Nullable String mAmbientContextDetectionPackage;
- private final @NonNull String mRequiredSupplementalProcessPackage;
+ private final @NonNull String mRequiredSdkSandboxPackage;
@GuardedBy("mLock")
private final PackageUsage mPackageUsage = new PackageUsage();
@@ -1166,13 +1171,14 @@
Computer computer = snapshotComputer();
ArraySet<String> packagesToNotify = computer.getNotifyPackagesForReplacedReceived(packages);
for (int index = 0; index < packagesToNotify.size(); index++) {
- notifyInstallObserver(packagesToNotify.valueAt(index));
+ notifyInstallObserver(packagesToNotify.valueAt(index), false /* killApp */);
}
}
- void notifyInstallObserver(String packageName) {
- Pair<PackageInstalledInfo, IPackageInstallObserver2> pair =
- mNoKillInstallObservers.remove(packageName);
+ void notifyInstallObserver(String packageName, boolean killApp) {
+ final Pair<PackageInstalledInfo, IPackageInstallObserver2> pair =
+ killApp ? mPendingKillInstallObservers.remove(packageName)
+ : mNoKillInstallObservers.remove(packageName);
if (pair != null) {
notifyInstallObserver(pair.first, pair.second);
@@ -1211,6 +1217,15 @@
delay ? getPruneUnusedSharedLibrariesDelay() : 0);
}
+ void scheduleDeferredPendingKillInstallObserver(PackageInstalledInfo info,
+ IPackageInstallObserver2 observer) {
+ final String packageName = info.mPkg.getPackageName();
+ mPendingKillInstallObservers.put(packageName, Pair.create(info, observer));
+ final Message message = mHandler.obtainMessage(DEFERRED_PENDING_KILL_INSTALL_OBSERVER,
+ packageName);
+ mHandler.sendMessageDelayed(message, DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS);
+ }
+
private static long getPruneUnusedSharedLibrariesDelay() {
return SystemProperties.getLong("debug.pm.prune_unused_shared_libraries_delay",
PRUNE_UNUSED_SHARED_LIBRARIES_DELAY);
@@ -1667,7 +1682,7 @@
mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName;
mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
mResolveComponentName = testParams.resolveComponentName;
- mRequiredSupplementalProcessPackage = testParams.requiredSupplementalProcessPackage;
+ mRequiredSdkSandboxPackage = testParams.requiredSdkSandboxPackage;
mLiveComputer = createLiveComputer();
mSnapshotComputer = null;
@@ -2055,8 +2070,6 @@
}
}
- mPrepareAppDataFuture = mAppDataHelper.fixAppsDataOnBoot();
-
// If this is first boot after an OTA, and a normal boot, then
// we need to clear code cache directories.
// Note that we do *not* clear the application profiles. These remain valid
@@ -2077,6 +2090,9 @@
ver.fingerprint = PackagePartitions.FINGERPRINT;
}
+ // Defer the app data fixup until we are done with app data clearing above.
+ mPrepareAppDataFuture = mAppDataHelper.fixAppsDataOnBoot();
+
// Legacy existing (installed before Q) non-system apps to hide
// their icons in launcher.
if (!mOnlyCore && mIsPreQUpgrade) {
@@ -2141,8 +2157,8 @@
getPackageInfo(mRequiredPermissionControllerPackage, 0,
UserHandle.USER_SYSTEM).getLongVersionCode());
- // Resolve the supplemental process
- mRequiredSupplementalProcessPackage = getRequiredSupplementalProcessPackageName();
+ // Resolve the sdk sandbox package
+ mRequiredSdkSandboxPackage = getRequiredSdkSandboxPackageName();
// Initialize InstantAppRegistry's Instant App list for all users.
for (AndroidPackage pkg : mPackages.values()) {
@@ -2557,8 +2573,9 @@
try {
return super.onTransact(code, data, reply, flags);
} catch (RuntimeException e) {
- if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException)) {
- Slog.wtf(TAG, "Package Manager Crash", e);
+ if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException)
+ && !(e instanceof ParcelableException)) {
+ Slog.wtf(TAG, "Package Manager Unexpected Exception", e);
}
throw e;
}
@@ -3143,8 +3160,8 @@
}
@Override
- public String getSupplementalProcessPackageName() {
- return mRequiredSupplementalProcessPackage;
+ public String getSdkSandboxPackageName() {
+ return mRequiredSdkSandboxPackage;
}
String getPackageInstallerPackageName() {
@@ -5458,8 +5475,8 @@
}
}
- private @NonNull String getRequiredSupplementalProcessPackageName() {
- final Intent intent = new Intent(SupplementalProcessManagerLocal.SERVICE_INTERFACE);
+ private @NonNull String getRequiredSdkSandboxPackageName() {
+ final Intent intent = new Intent(SdkSandboxManagerLocal.SERVICE_INTERFACE);
final List<ResolveInfo> matches = queryIntentServicesInternal(
intent,
@@ -5471,7 +5488,7 @@
if (matches.size() == 1) {
return matches.get(0).getComponentInfo().packageName;
} else {
- throw new RuntimeException("There should exactly one supplemental process; found "
+ throw new RuntimeException("There should exactly one sdk sandbox package; found "
+ matches.size() + ": matches=" + matches);
}
}
@@ -6675,6 +6692,11 @@
@Override
public IPackageInstaller getPackageInstaller() {
+ // Return installer service for internal calls.
+ if (PackageManagerServiceUtils.isSystemOrRoot()) {
+ return mInstallerService;
+ }
+ // Return null for InstantApps.
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return null;
}
@@ -7393,6 +7415,12 @@
}
@Override
+ public void onPackageProcessKilledForUninstall(String packageName) {
+ mHandler.post(() -> PackageManagerService.this.notifyInstallObserver(packageName,
+ true /* killApp */));
+ }
+
+ @Override
public SparseArray<String> getAppsWithSharedUserIds() {
return mComputer.getAppsWithSharedUserIds();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index d12c826..5bdda0b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -89,7 +89,7 @@
public @Nullable String defaultTextClassifierPackage;
public @Nullable String systemTextClassifierPackage;
public @Nullable String overlayConfigSignaturePackage;
- public @NonNull String requiredSupplementalProcessPackage;
+ public @NonNull String requiredSdkSandboxPackage;
public ViewCompiler viewCompiler;
public @Nullable String retailDemoPackage;
public @Nullable String recentsPackage;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index a9471cf..8d3fbf7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1250,6 +1250,14 @@
}
/**
+ * Check if the Binder caller is system UID or root's UID.
+ */
+ public static boolean isSystemOrRoot() {
+ final int uid = Binder.getCallingUid();
+ return uid == Process.SYSTEM_UID || uid == Process.ROOT_UID;
+ }
+
+ /**
* Enforces that only the system UID or root's UID can call a method exposed
* via Binder.
*
@@ -1257,8 +1265,7 @@
* @throws SecurityException if the caller is not system or root
*/
public static void enforceSystemOrRoot(String message) {
- final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID && uid != Process.ROOT_UID) {
+ if (!isSystemOrRoot()) {
throw new SecurityException(message);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b7c55c5..fed214f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6263,11 +6263,11 @@
}
/**
- * Checks if the given user has a managed profile associated with it.
+ * Checks if the given user has a profile associated with it.
* @param userId The parent user
* @return
*/
- boolean hasManagedProfile(@UserIdInt int userId) {
+ boolean hasProfile(@UserIdInt int userId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
final int userSize = mUsers.size();
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 49553f4..36633cc 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1021,7 +1021,8 @@
}
for (String packageName : packageNames) {
grantPermissionsToSystemPackage(NO_PM_CACHE, packageName, userId,
- PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS);
+ PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS,
+ NOTIFICATION_PERMISSIONS);
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
index d3c8c7b..a0bb8dc 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
@@ -268,6 +268,9 @@
List<FeatureGroupInfo> getFeatureGroups();
@NonNull
+ List<String> getImplicitPermissions();
+
+ @NonNull
List<ParsedInstrumentation> getInstrumentations();
long getLongVersionCode();
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index c637c67..f2ce0d4 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -34,6 +34,7 @@
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
+import android.app.KeyguardManager;
import android.app.TaskInfo;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -150,6 +151,7 @@
private Context mContext;
private PackageManagerInternal mPackageManagerInternal;
private NotificationManagerInternal mNotificationManager;
+ private final KeyguardManager mKeyguardManager;
private final PackageManager mPackageManager;
public PermissionPolicyService(@NonNull Context context) {
@@ -157,6 +159,7 @@
mContext = context;
mPackageManager = context.getPackageManager();
+ mKeyguardManager = context.getSystemService(KeyguardManager.class);
LocalServices.addService(PermissionPolicyInternal.class, new Internal());
}
@@ -1046,12 +1049,21 @@
}
@Override
- public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
- super.onActivityLaunched(taskInfo, activityInfo);
- clearNotificationReviewFlagsIfNeeded(activityInfo.packageName,
- UserHandle.of(taskInfo.userId));
- showNotificationPromptIfNeeded(activityInfo.packageName,
- taskInfo.userId, taskInfo.taskId);
+ public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
+ ActivityInterceptorInfo info) {
+ super.onActivityLaunched(taskInfo, activityInfo, info);
+ if (!shouldShowNotificationDialogOrClearFlags(info.intent,
+ info.checkedOptions)) {
+ return;
+ }
+ UserHandle user = UserHandle.of(taskInfo.userId);
+ if (CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID,
+ activityInfo.packageName, user)) {
+ clearNotificationReviewFlagsIfNeeded(activityInfo.packageName, user);
+ } else {
+ showNotificationPromptIfNeeded(activityInfo.packageName,
+ taskInfo.userId, taskInfo.taskId);
+ }
}
};
@@ -1092,10 +1104,28 @@
launchNotificationPermissionRequestDialog(packageName, user, taskId);
}
+ /**
+ * Determine if we should show a notification dialog, or clear the REVIEW_REQUIRED flag,
+ * from a particular package for a particular intent. Returns true if:
+ * 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or
+ * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER)
+ */
+ private boolean shouldShowNotificationDialogOrClearFlags(Intent intent,
+ ActivityOptions options) {
+ if ((options != null && options.isEligibleForLegacyPermissionPrompt())) {
+ return true;
+ }
+
+ return Intent.ACTION_MAIN.equals(intent.getAction())
+ && intent.getCategories() != null
+ && (intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)
+ || intent.getCategories().contains(Intent.CATEGORY_LEANBACK_LAUNCHER)
+ || intent.getCategories().contains(Intent.CATEGORY_CAR_LAUNCHER));
+ }
+
private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle user) {
- if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, user)
- || ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user)
- & FLAG_PERMISSION_REVIEW_REQUIRED) == 0)) {
+ if ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user)
+ & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
return;
}
try {
@@ -1210,8 +1240,8 @@
}
if (!pkg.getRequestedPermissions().contains(POST_NOTIFICATIONS)
- || CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID,
- pkg.getPackageName(), user)) {
+ || CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, pkgName, user)
+ || mKeyguardManager.isKeyguardLocked()) {
return false;
}
@@ -1220,7 +1250,7 @@
mNotificationManager = LocalServices.getService(NotificationManagerInternal.class);
}
boolean hasCreatedNotificationChannels = mNotificationManager
- .getNumNotificationChannelsForPackage(pkg.getPackageName(), uid, true) > 0;
+ .getNumNotificationChannelsForPackage(pkgName, uid, true) > 0;
int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user);
boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0;
boolean needsReview = (flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index bd58472..914e5ec 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1176,14 +1176,14 @@
@Override
public void onBootPhase(int phase) {
- synchronized (mLock) {
- if (phase == PHASE_SYSTEM_SERVICES_READY) {
- systemReady();
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ systemReady();
- } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- incrementBootCount();
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ incrementBootCount();
- } else if (phase == PHASE_BOOT_COMPLETED) {
+ } else if (phase == PHASE_BOOT_COMPLETED) {
+ synchronized (mLock) {
final long now = mClock.uptimeMillis();
mBootCompleted = true;
mDirty |= DIRTY_BOOT_COMPLETED;
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 2491565d..8755662 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -27,6 +27,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -51,8 +52,13 @@
private static final boolean DEBUG = false;
@VisibleForTesting final long mHintSessionPreferredRate;
+ // Multi-levle map storing all active AppHintSessions.
+ // First level is keyed by the UID of the client process creating the session.
+ // Second level is keyed by an IBinder passed from client process. This is used to observe
+ // when the process exits. The client generally uses the same IBinder object across multiple
+ // sessions, so the value is a set of AppHintSessions.
@GuardedBy("mLock")
- private final ArrayMap<Integer, ArrayMap<IBinder, AppHintSession>> mActiveSessions;
+ private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions;
/** Lock to protect HAL handles and listen list. */
private final Object mLock = new Object();
@@ -201,13 +207,16 @@
public void onUidGone(int uid, boolean disabled) {
FgThread.getHandler().post(() -> {
synchronized (mLock) {
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
if (tokenMap == null) {
return;
}
for (int i = tokenMap.size() - 1; i >= 0; i--) {
// Will remove the session from tokenMap
- tokenMap.valueAt(i).close();
+ ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
+ for (int j = sessionSet.size() - 1; j >= 0; j--) {
+ sessionSet.valueAt(j).close();
+ }
}
mProcStatesCache.delete(uid);
}
@@ -231,12 +240,14 @@
FgThread.getHandler().post(() -> {
synchronized (mLock) {
mProcStatesCache.put(uid, procState);
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
if (tokenMap == null) {
return;
}
- for (AppHintSession s : tokenMap.values()) {
- s.onProcStateChanged();
+ for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) {
+ for (AppHintSession s : sessionSet) {
+ s.onProcStateChanged();
+ }
}
}
});
@@ -305,17 +316,25 @@
long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid,
tids, durationNanos);
- if (halSessionPtr == 0) return null;
+ if (halSessionPtr == 0) {
+ return null;
+ }
AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
halSessionPtr, durationNanos);
synchronized (mLock) {
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(callingUid);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+ mActiveSessions.get(callingUid);
if (tokenMap == null) {
tokenMap = new ArrayMap<>(1);
mActiveSessions.put(callingUid, tokenMap);
}
- tokenMap.put(token, hs);
+ ArraySet<AppHintSession> sessionSet = tokenMap.get(token);
+ if (sessionSet == null) {
+ sessionSet = new ArraySet<>(1);
+ tokenMap.put(token, sessionSet);
+ }
+ sessionSet.add(hs);
return hs;
}
} finally {
@@ -339,10 +358,14 @@
pw.println("Active Sessions:");
for (int i = 0; i < mActiveSessions.size(); i++) {
pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.valueAt(i);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+ mActiveSessions.valueAt(i);
for (int j = 0; j < tokenMap.size(); j++) {
- pw.println(" Session " + j + ":");
- tokenMap.valueAt(j).dump(pw, " ");
+ ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
+ for (int k = 0; k < sessionSet.size(); ++k) {
+ pw.println(" Session:");
+ sessionSet.valueAt(k).dump(pw, " ");
+ }
}
}
}
@@ -432,11 +455,18 @@
mNativeWrapper.halCloseHintSession(mHalSessionPtr);
mHalSessionPtr = 0;
mToken.unlinkToDeath(this, 0);
- ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(mUid);
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
if (tokenMap == null) {
- Slogf.w(TAG, "UID %d is note present in active session map", mUid);
+ Slogf.w(TAG, "UID %d is not present in active session map", mUid);
+ return;
}
- tokenMap.remove(mToken);
+ ArraySet<AppHintSession> sessionSet = tokenMap.get(mToken);
+ if (sessionSet == null) {
+ Slogf.w(TAG, "Token %s is not present in token map", mToken.toString());
+ return;
+ }
+ sessionSet.remove(this);
+ if (sessionSet.isEmpty()) tokenMap.remove(mToken);
if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 411f3dc..11fd99c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -157,7 +157,7 @@
* @see com.android.internal.statusbar.IStatusBar#requestWindowMagnificationConnection(boolean
* request)
*/
- void requestWindowMagnificationConnection(boolean request);
+ boolean requestWindowMagnificationConnection(boolean request);
/**
* Handles a logging command from the WM shell command.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e4a969b..59b9daf 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -637,12 +637,14 @@
}
@Override
- public void requestWindowMagnificationConnection(boolean request) {
+ public boolean requestWindowMagnificationConnection(boolean request) {
if (mBar != null) {
try {
mBar.requestWindowMagnificationConnection(request);
+ return true;
} catch (RemoteException ex) { }
}
+ return false;
}
@Override
@@ -856,11 +858,11 @@
}
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(@Modality int modality) {
enforceBiometricDialog();
if (mBar != null) {
try {
- mBar.onBiometricAuthenticated();
+ mBar.onBiometricAuthenticated(modality);
} catch (RemoteException ex) {
}
}
@@ -1952,8 +1954,7 @@
public void setNavBarMode(@NavBarMode int navBarMode) {
enforceStatusBar();
if (navBarMode != NAV_BAR_MODE_DEFAULT && navBarMode != NAV_BAR_MODE_KIDS) {
- throw new UnsupportedOperationException(
- "Supplied navBarMode not supported: " + navBarMode);
+ throw new IllegalArgumentException("Supplied navBarMode not supported: " + navBarMode);
}
final int userId = mCurrentUserId;
@@ -1961,6 +1962,8 @@
try {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.NAV_BAR_KIDS_MODE, navBarMode, userId);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NAV_BAR_FORCE_VISIBLE, navBarMode, userId);
IOverlayManager overlayManager = getOverlayManager();
if (overlayManager != null && navBarMode == NAV_BAR_MODE_KIDS
diff --git a/services/core/java/com/android/server/timezonedetector/OWNERS b/services/core/java/com/android/server/timezonedetector/OWNERS
index 0293242..485a0dd 100644
--- a/services/core/java/com/android/server/timezonedetector/OWNERS
+++ b/services/core/java/com/android/server/timezonedetector/OWNERS
@@ -3,5 +3,6 @@
# ultimately referenced by other OWNERS files for components maintained by the same team.
nfuller@google.com
jmorace@google.com
+kanyinsola@google.com
mingaleev@google.com
narayan@google.com
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 8f703c5..d3f3abe 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -79,6 +79,7 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -138,7 +139,7 @@
new SparseArray<>();
private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
private int mFocusedDisplay = -1;
- private boolean mIsImeVisible = false;
+ private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
// Set to true if initializing window population complete.
private boolean mAllObserversInitialized = true;
private final AccessibilityWindowsPopulator mAccessibilityWindowsPopulator;
@@ -167,8 +168,11 @@
if (dc != null) {
final Display display = dc.getDisplay();
if (display != null && display.getType() != Display.TYPE_OVERLAY) {
- mDisplayMagnifiers.put(displayId, new DisplayMagnifier(
- mService, dc, display, callbacks));
+ final DisplayMagnifier magnifier = new DisplayMagnifier(
+ mService, dc, display, callbacks);
+ magnifier.notifyImeWindowVisibilityChanged(
+ mIsImeVisibleArray.get(displayId, false));
+ mDisplayMagnifiers.put(displayId, magnifier);
result = true;
}
}
@@ -494,11 +498,13 @@
mAccessibilityTracing.logTrace(TAG + ".updateImeVisibilityIfNeeded",
FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + ";shown=" + shown);
}
- if (mIsImeVisible == shown) {
+
+ final boolean isDisplayImeVisible = mIsImeVisibleArray.get(displayId, false);
+ if (isDisplayImeVisible == shown) {
return;
}
- mIsImeVisible = shown;
+ mIsImeVisibleArray.put(displayId, shown);
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.notifyImeWindowVisibilityChanged(shown);
@@ -534,6 +540,7 @@
}
public void onDisplayRemoved(int displayId) {
+ mIsImeVisibleArray.delete(displayId);
mFocusedWindow.remove(displayId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 4df2e17..06c58ba 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -43,9 +43,13 @@
public abstract @Nullable ActivityInterceptResult intercept(ActivityInterceptorInfo info);
/**
- * Called when an activity is successfully launched.
+ * Called when an activity is successfully launched. The intent included in the
+ * ActivityInterceptorInfo may have changed from the one sent in
+ * {@link #intercept(ActivityInterceptorInfo)}, due to the return from
+ * {@link #intercept(ActivityInterceptorInfo)}.
*/
- public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
+ public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
+ ActivityInterceptorInfo info) {
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9c910eb..4e751f5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,7 +46,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -7515,8 +7514,7 @@
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
final boolean isFixedOrientationLetterboxAllowed =
- isSplitScreenWindowingMode(parentWindowingMode)
- || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+ parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
|| parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
// TODO(b/181207944): Consider removing the if condition and always run
// resolveFixedOrientationConfiguration() since this should be applied for all cases.
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 658a17b..c5fcd12 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -188,10 +188,7 @@
final SparseArray<ActivityInterceptorCallback> callbacks =
mService.getActivityInterceptorCallbacks();
final ActivityInterceptorCallback.ActivityInterceptorInfo interceptorInfo =
- new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid,
- mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent,
- mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid,
- mActivityOptions);
+ getInterceptorInfo();
for (int i = 0; i < callbacks.size(); i++) {
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
@@ -412,9 +409,17 @@
void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
final SparseArray<ActivityInterceptorCallback> callbacks =
mService.getActivityInterceptorCallbacks();
+ ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo();
for (int i = 0; i < callbacks.size(); i++) {
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
- callback.onActivityLaunched(taskInfo, activityInfo);
+ callback.onActivityLaunched(taskInfo, activityInfo, info);
}
}
+
+ private ActivityInterceptorCallback.ActivityInterceptorInfo getInterceptorInfo() {
+ return new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid,
+ mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent,
+ mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid,
+ mActivityOptions);
+ }
}
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 3d54b27..6befefd 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -21,6 +21,7 @@
import static com.android.server.wm.ActivityRecord.INVALID_PID;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.annotation.NonNull;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
@@ -35,6 +36,7 @@
import java.io.File;
import java.util.ArrayList;
+import java.util.OptionalInt;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -75,7 +77,33 @@
activity.inputDispatchingTimedOut(reason, INVALID_PID);
}
- void notifyWindowUnresponsive(IBinder inputToken, String reason) {
+
+ /**
+ * Notify a window was unresponsive.
+ *
+ * @param token - the input token of the window
+ * @param pid - the pid of the window, if known
+ * @param reason - the reason for the window being unresponsive
+ */
+ void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
+ @NonNull String reason) {
+ if (notifyWindowUnresponsive(token, reason)) {
+ return;
+ }
+ if (!pid.isPresent()) {
+ Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was unresponsive.");
+ return;
+ }
+ notifyWindowUnresponsive(pid.getAsInt(), reason);
+ }
+
+ /**
+ * Notify a window identified by its input token was unresponsive.
+ *
+ * @return true if the window was identified by the given input token and the request was
+ * handled, false otherwise.
+ */
+ private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken, String reason) {
preDumpIfLockTooSlow();
final int pid;
final boolean aboveSystem;
@@ -83,10 +111,8 @@
synchronized (mService.mGlobalLock) {
InputTarget target = mService.getInputTargetFromToken(inputToken);
if (target == null) {
- Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request");
- return;
+ return false;
}
-
WindowState windowState = target.getWindowState();
pid = target.getPid();
// Blame the activity if the input token belongs to the window. If the target is
@@ -102,34 +128,63 @@
} else {
mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, reason);
}
+ return true;
}
- void notifyWindowResponsive(IBinder inputToken) {
+ /**
+ * Notify a window owned by the provided pid was unresponsive.
+ */
+ private void notifyWindowUnresponsive(int pid, String reason) {
+ Slog.i(TAG_WM, "ANR in input window owned by pid=" + pid + ". Reason: " + reason);
+ dumpAnrStateLocked(null /* activity */, null /* windowState */, reason);
+
+ // We cannot determine the z-order of the window, so place the anr dialog as high
+ // as possible.
+ mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, reason);
+ }
+
+ /**
+ * Notify a window was responsive after previously being unresponsive.
+ *
+ * @param token - the input token of the window
+ * @param pid - the pid of the window, if known
+ */
+ void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) {
+ if (notifyWindowResponsive(token)) {
+ return;
+ }
+ if (!pid.isPresent()) {
+ Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was responsive.");
+ return;
+ }
+ notifyWindowResponsive(pid.getAsInt());
+ }
+
+ /**
+ * Notify a window identified by its input token was responsive after previously being
+ * unresponsive.
+ *
+ * @return true if the window was identified by the given input token and the request was
+ * handled, false otherwise.
+ */
+ private boolean notifyWindowResponsive(@NonNull IBinder inputToken) {
final int pid;
synchronized (mService.mGlobalLock) {
InputTarget target = mService.getInputTargetFromToken(inputToken);
if (target == null) {
- Slog.e(TAG_WM, "Unknown token, dropping notifyWindowConnectionResponsive request");
- return;
+ return false;
}
pid = target.getPid();
}
mService.mAmInternal.inputDispatchingResumed(pid);
+ return true;
}
- void notifyGestureMonitorUnresponsive(int gestureMonitorPid, String reason) {
- preDumpIfLockTooSlow();
- synchronized (mService.mGlobalLock) {
- Slog.i(TAG_WM, "ANR in gesture monitor owned by pid:" + gestureMonitorPid
- + ". Reason: " + reason);
- dumpAnrStateLocked(null /* activity */, null /* windowState */, reason);
- }
- mService.mAmInternal.inputDispatchingTimedOut(gestureMonitorPid, /* aboveSystem */ true,
- reason);
- }
-
- void notifyGestureMonitorResponsive(int gestureMonitorPid) {
- mService.mAmInternal.inputDispatchingResumed(gestureMonitorPid);
+ /**
+ * Notify a window owned by the provided pid was responsive after previously being unresponsive.
+ */
+ private void notifyWindowResponsive(int pid) {
+ mService.mAmInternal.inputDispatchingResumed(pid);
}
/**
@@ -228,12 +283,7 @@
mService.mAtmService.saveANRState(reason);
}
- private boolean isWindowAboveSystem(WindowState windowState) {
- if (windowState == null) {
- // If the window state is not available we cannot easily determine its z order. Try to
- // place the anr dialog as high as possible.
- return true;
- }
+ private boolean isWindowAboveSystem(@NonNull WindowState windowState) {
int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw(
TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow);
return windowState.mBaseLayer > systemAlertLayer;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 487aff6..23f14a7 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -53,7 +53,7 @@
* Returns true if the back predictability feature is enabled
*/
static boolean isEnabled() {
- return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 1) > 0;
+ return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
}
static boolean isScreenshotEnabled() {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index afa4f19..08a9da4 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -24,8 +24,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
@@ -475,33 +473,9 @@
return WindowConfiguration.inMultiWindowMode(windowingMode);
}
- /** Returns true if this container is currently in split-screen windowing mode. */
- public boolean inSplitScreenWindowingMode() {
- /*@WindowConfiguration.WindowingMode*/ int windowingMode =
- mFullConfiguration.windowConfiguration.getWindowingMode();
-
- return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
-
- /** Returns true if this container is currently in split-screen secondary windowing mode. */
- public boolean inSplitScreenSecondaryWindowingMode() {
- /*@WindowConfiguration.WindowingMode*/ int windowingMode =
- mFullConfiguration.windowConfiguration.getWindowingMode();
-
- return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
-
- public boolean inSplitScreenPrimaryWindowingMode() {
- return mFullConfiguration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
- }
-
/**
- * Returns true if this container can be put in either
- * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
- * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
- * its current state.
+ * Returns true if this container supports split-screen multi-window and can be put in
+ * split-screen based on its current state.
*/
public boolean supportsSplitScreenWindowingMode() {
return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1c0687a..82ad861 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -26,7 +26,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -5521,11 +5520,7 @@
final float[] tmpFloat9 = new float[9];
forAllWindows(w -> {
if (w.isVisible() && !w.inPinnedWindowingMode()) {
- if (w.mSession.mSetsUnrestrictedKeepClearAreas) {
- outUnrestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
- } else {
- outRestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
- }
+ w.getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9);
}
// We stop traversing when we reach the base of a fullscreen app.
@@ -5583,13 +5578,12 @@
}
static boolean alwaysCreateRootTask(int windowingMode, int activityType) {
- // Always create a root task for fullscreen, freeform, and split-screen-secondary windowing
+ // Always create a root task for fullscreen, freeform, and multi windowing
// modes so that we can manage visual ordering and return types correctly.
return activityType == ACTIVITY_TYPE_STANDARD
&& (windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_FREEFORM
|| windowingMode == WINDOWING_MODE_PINNED
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW);
}
@@ -6368,10 +6362,10 @@
}
/**
- * Returns the fixed orientation requested by a transient launch (e.g. recents animation).
- * If it doesn't return SCREEN_ORIENTATION_UNSET, the rotation change should be deferred.
+ * Returns {@code true} if the transient launch (e.g. recents animation) requested a fixed
+ * orientation, then the rotation change should be deferred.
*/
- @ActivityInfo.ScreenOrientation int getTransientFixedOrientation() {
+ boolean shouldDeferRotation() {
ActivityRecord source = null;
if (mTransitionController.isShellTransitionsEnabled()) {
final ActivityRecord r = mFixedRotationLaunchingApp;
@@ -6383,13 +6377,10 @@
}
if (source == null || source.getRequestedConfigurationOrientation(
true /* forDisplay */) == ORIENTATION_UNDEFINED) {
- return SCREEN_ORIENTATION_UNSET;
+ return false;
}
- if (!mWmService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) {
- // If screen is off or the device is going to sleep, then still allow to update.
- return SCREEN_ORIENTATION_UNSET;
- }
- return source.mOrientation;
+ // If screen is off or the device is going to sleep, then still allow to update.
+ return mWmService.mPolicy.okToAnimate(false /* ignoreScreenOn */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f116fff..262ddae 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -446,12 +446,10 @@
return false;
}
- final int transientFixedOrientation =
- mDisplayContent.mFixedRotationTransitionListener.getTransientFixedOrientation();
- if (transientFixedOrientation != SCREEN_ORIENTATION_UNSET) {
+ if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) {
// Makes sure that after the transition is finished, updateOrientation() can see
// the difference from the latest orientation source.
- mLastOrientation = transientFixedOrientation;
+ mLastOrientation = SCREEN_ORIENTATION_UNSET;
// During the recents animation, the closing app might still be considered on top.
// In order to ignore its requested orientation to avoid a sensor led rotation (e.g
// user rotating the device while the recents animation is running), we ignore
diff --git a/services/core/java/com/android/server/wm/DragResizeMode.java b/services/core/java/com/android/server/wm/DragResizeMode.java
index d754fd8..684cf06 100644
--- a/services/core/java/com/android/server/wm/DragResizeMode.java
+++ b/services/core/java/com/android/server/wm/DragResizeMode.java
@@ -40,8 +40,6 @@
switch (mode) {
case DRAG_RESIZE_MODE_FREEFORM:
return rootTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
- case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
- return rootTask.inSplitScreenWindowingMode();
default:
return false;
}
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1f0fdcf..8d1425d 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -39,6 +39,7 @@
import com.android.server.input.InputManagerService;
import java.io.PrintWriter;
+import java.util.OptionalInt;
final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
@@ -98,23 +99,14 @@
}
@Override
- public void notifyGestureMonitorUnresponsive(int pid, @NonNull String reason) {
- mService.mAnrController.notifyGestureMonitorUnresponsive(pid, reason);
+ public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
+ @NonNull String reason) {
+ mService.mAnrController.notifyWindowUnresponsive(token, pid, reason);
}
@Override
- public void notifyWindowUnresponsive(@NonNull IBinder token, String reason) {
- mService.mAnrController.notifyWindowUnresponsive(token, reason);
- }
-
- @Override
- public void notifyGestureMonitorResponsive(int pid) {
- mService.mAnrController.notifyGestureMonitorResponsive(pid);
- }
-
- @Override
- public void notifyWindowResponsive(@NonNull IBinder token) {
- mService.mAnrController.notifyWindowResponsive(token);
+ public void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) {
+ mService.mAnrController.notifyWindowResponsive(token, pid);
}
/** Notifies that the input device configuration has changed. */
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index bb6d83c..4fdb1f7 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -449,11 +449,8 @@
boolean copyState) {
final WindowState roundedCornerWindow = mPolicy.getRoundedCornerWindow();
final Task task = w.getTask();
- final boolean isInSplitScreenMode = task != null && task.inMultiWindowMode()
- && task.getRootTask() != null
- && task.getRootTask().getAdjacentTaskFragment() != null;
if (task != null && !task.getWindowConfiguration().tasksAreFloating()
- && (roundedCornerWindow != null || isInSplitScreenMode)) {
+ && (roundedCornerWindow != null || task.inSplitScreen())) {
// Instead of using display frame to calculating rounded corner, for the fake rounded
// corners drawn by divider bar or task bar, we need to re-calculate rounded corners
// based on task bounds and if the task bounds is intersected with task bar, we should
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 93714e8..a407021 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -18,7 +18,6 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
@@ -610,8 +609,7 @@
final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
final Task task = adapter.mTask;
final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment();
- final boolean inSplitScreen = task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
- && adjacentTask != null;
+ final boolean inSplitScreen = task.inSplitScreen();
if (task.isActivityTypeHomeOrRecents()
// Skip if the task is in split screen and in landscape.
|| (inSplitScreen && isDisplayLandscape)
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index cd8ddf4..bafdd92 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -84,6 +84,7 @@
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
+import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
@@ -521,10 +522,15 @@
}
@Override
- public void reportKeepClearAreasChanged(IWindow window, List<Rect> keepClearAreas) {
+ public void reportKeepClearAreasChanged(IWindow window, List<Rect> restricted,
+ List<Rect> unrestricted) {
+ if (!mSetsUnrestrictedKeepClearAreas && !unrestricted.isEmpty()) {
+ unrestricted = Collections.emptyList();
+ }
+
final long ident = Binder.clearCallingIdentity();
try {
- mService.reportKeepClearAreasChanged(this, window, keepClearAreas);
+ mService.reportKeepClearAreasChanged(this, window, restricted, unrestricted);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f3933c5..7fe34f4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -29,6 +29,7 @@
import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
@@ -1722,6 +1723,13 @@
&& (topTask == null || topTask.supportsSplitScreenWindowingModeInner(tda));
}
+ /** Returns {@code true} if this task is currently in split-screen. */
+ boolean inSplitScreen() {
+ return getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+ && getRootTask() != null
+ && getRootTask().getAdjacentTaskFragment() != null;
+ }
+
private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) {
return super.supportsSplitScreenWindowingMode()
&& mAtmService.mSupportsSplitScreenMultiWindow
@@ -6020,9 +6028,6 @@
}
boolean shouldIgnoreInput() {
- if (inSplitScreenPrimaryWindowingMode() && !isFocusable()) {
- return true;
- }
if (mAtmService.mHasLeanbackFeature && inPinnedWindowingMode()
&& !isFocusedRootTaskOnDisplay()) {
// Preventing Picture-in-Picture root task from receiving input on TVs.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 7fab94c..afc3087 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1767,8 +1767,7 @@
// Resolve override windowing mode to fullscreen for home task (even on freeform
// display), or split-screen if in split-screen mode.
if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
- windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
- ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
+ windowingMode = WINDOWING_MODE_FULLSCREEN;
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 037d582..331f124 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -729,6 +729,16 @@
// Skip if task still not appeared.
return;
}
+ if (force && mPendingTaskEvents.isEmpty()) {
+ // There are task-info changed events do not result in
+ // - RootWindowContainer#performSurfacePlacementNoTrace OR
+ // - WindowAnimator#animate
+ // For instance, when an app requesting aspect ratio change when in PiP mode.
+ // To solve this, we directly dispatch the pending event if there are no events queued (
+ // otherwise, all pending events should be dispatched on next drawn).
+ dispatchTaskInfoChanged(task, true /* force */);
+ return;
+ }
// Defer task info reporting while layout is deferred. This is because layout defer
// blocks tend to do lots of re-ordering which can mess up animations in receivers.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 04f135e..03e2140 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3035,7 +3035,7 @@
@Override
public void onKeyguardShowingAndNotOccludedChanged() {
mH.sendEmptyMessage(H.RECOMPUTE_FOCUS);
- dispatchKeyguardLockedStateState();
+ dispatchKeyguardLockedState();
}
@Override
@@ -3249,7 +3249,7 @@
+ " permission required to read keyguard visibility");
}
- private void dispatchKeyguardLockedStateState() {
+ private void dispatchKeyguardLockedState() {
mH.post(() -> {
final boolean isKeyguardLocked = mPolicy.isKeyguardShowing();
if (mDispatchedKeyguardLockedState == isKeyguardLocked) {
@@ -4412,10 +4412,11 @@
}
}
- void reportKeepClearAreasChanged(Session session, IWindow window, List<Rect> keepClearAreas) {
+ void reportKeepClearAreasChanged(Session session, IWindow window,
+ List<Rect> restricted, List<Rect> unrestricted) {
synchronized (mGlobalLock) {
final WindowState win = windowForClientLocked(session, window, true);
- if (win.setKeepClearAreas(keepClearAreas)) {
+ if (win.setKeepClearAreas(restricted, unrestricted)) {
win.getDisplayContent().updateKeepClearAreas();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c70a40c..ce27d73 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -21,11 +21,13 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION;
import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION_RECT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
@@ -895,6 +897,17 @@
taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
break;
}
+ case HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER:
+ final Rect insetsProviderWindowContainer = hop.getInsetsProviderFrame();
+ final WindowContainer receiverWindowContainer =
+ WindowContainer.fromBinder(hop.getContainer());
+ receiverWindowContainer.addLocalRectInsetsSourceProvider(
+ insetsProviderWindowContainer, hop.getInsetsTypes());
+ break;
+ case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER:
+ WindowContainer.fromBinder(hop.getContainer())
+ .removeLocalInsetsSourceProvider(hop.getInsetsTypes());
+ break;
}
return effects;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 517837c..d547275 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -22,7 +22,6 @@
import static android.app.AppOpsManager.OP_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.graphics.GraphicsProtos.dumpPointProto;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
@@ -181,6 +180,7 @@
import static com.android.server.wm.WindowStateProto.STACK_ID;
import static com.android.server.wm.WindowStateProto.SURFACE_INSETS;
import static com.android.server.wm.WindowStateProto.SURFACE_POSITION;
+import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_AREAS;
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
@@ -482,6 +482,12 @@
*/
private final List<Rect> mKeepClearAreas = new ArrayList<>();
+ /**
+ * Like mKeepClearAreas, but the unrestricted ones can be trusted to behave nicely.
+ * Floating windows (like Pip) will be moved away from them without applying restrictions.
+ */
+ private final List<Rect> mUnrestrictedKeepClearAreas = new ArrayList<>();
+
// 0 = left, 1 = right
private final int[] mLastRequestedExclusionHeight = {0, 0};
private final int[] mLastGrantedExclusionHeight = {0, 0};
@@ -1023,51 +1029,86 @@
}
/**
- * @return a list of rects that should ideally not be covered by floating windows like pip.
- * The returned rect coordinates are relative to the display origin.
+ * Collects all restricted and unrestricted keep-clear areas for this window.
+ * Keep-clear areas are rects that should ideally not be covered by floating windows like Pip.
+ * The system is more careful about restricted ones and may apply restrictions to them, while
+ * the unrestricted ones are considered safe.
+ *
+ * @param outRestricted list to add restricted keep-clear areas to
+ * @param outUnrestricted list to add unrestricted keep-clear areas to
*/
- List<Rect> getKeepClearAreas() {
+ void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted) {
final Matrix tmpMatrix = new Matrix();
final float[] tmpFloat9 = new float[9];
- return getKeepClearAreas(tmpMatrix, tmpFloat9);
+ getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9);
}
/**
+ * Collects all restricted and unrestricted keep-clear areas for this window.
+ * Keep-clear areas are rects that should ideally not be covered by floating windows like Pip.
+ * The system is more careful about restricted ones and may apply restrictions to them, while
+ * the unrestricted ones are considered safe.
+ *
+ * @param outRestricted list to add restricted keep-clear areas to
+ * @param outUnrestricted list to add unrestricted keep-clear areas to
* @param tmpMatrix a temporary matrix to be used for transformations
* @param float9 a temporary array of 9 floats
- *
- * @return a list of rects that should ideally not be covered by floating windows like pip.
- * The returned rect coordinates are relative to the display origin.
*/
- List<Rect> getKeepClearAreas(Matrix tmpMatrix, float[] float9) {
+ void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted, Matrix tmpMatrix,
+ float[] float9) {
+ outRestricted.addAll(getRectsInScreenSpace(mKeepClearAreas, tmpMatrix, float9));
+ outUnrestricted.addAll(
+ getRectsInScreenSpace(mUnrestrictedKeepClearAreas, tmpMatrix, float9));
+ }
+
+ /**
+ * Transforms the given rects from window coordinate space to screen space.
+ */
+ List<Rect> getRectsInScreenSpace(List<Rect> rects, Matrix tmpMatrix, float[] float9) {
getTransformationMatrix(float9, tmpMatrix);
- // Translate all keep-clear rects to screen coordinates.
- final List<Rect> transformedKeepClearAreas = new ArrayList<Rect>();
+ final List<Rect> transformedRects = new ArrayList<Rect>();
final RectF tmpRect = new RectF();
Rect curr;
- for (Rect r : mKeepClearAreas) {
+ for (Rect r : rects) {
tmpRect.set(r);
tmpMatrix.mapRect(tmpRect);
curr = new Rect();
tmpRect.roundOut(curr);
- transformedKeepClearAreas.add(curr);
+ transformedRects.add(curr);
}
- return transformedKeepClearAreas;
+ return transformedRects;
}
/**
- * @param keepClearAreas the new keep-clear areas for this window. The rects should be defined
- * in window coordinate space
+ * Sets the new keep-clear areas for this window. The rects should be defined in window
+ * coordinate space.
+ * Keep-clear areas can be restricted or unrestricted, depending on whether the app holds the
+ * {@link android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS} system permission.
+ * Restricted ones will be handled more carefully by the system. Restrictions may be applied.
+ * Unrestricted ones are considered safe. The system should move floating windows away from them
+ * without applying restrictions.
+ *
+ * @param restricted the new restricted keep-clear areas for this window
+ * @param unrestricted the new unrestricted keep-clear areas for this window
*
* @return true if there is a change in the list of keep-clear areas; false otherwise
*/
- boolean setKeepClearAreas(List<Rect> keepClearAreas) {
- if (mKeepClearAreas.equals(keepClearAreas)) {
+ boolean setKeepClearAreas(List<Rect> restricted, List<Rect> unrestricted) {
+ final boolean newRestrictedAreas = !mKeepClearAreas.equals(restricted);
+ final boolean newUnrestrictedAreas = !mUnrestrictedKeepClearAreas.equals(unrestricted);
+ if (!newRestrictedAreas && !newUnrestrictedAreas) {
return false;
}
- mKeepClearAreas.clear();
- mKeepClearAreas.addAll(keepClearAreas);
+ if (newRestrictedAreas) {
+ mKeepClearAreas.clear();
+ mKeepClearAreas.addAll(restricted);
+ }
+
+ if (newUnrestrictedAreas) {
+ mUnrestrictedKeepClearAreas.clear();
+ mUnrestrictedKeepClearAreas.addAll(unrestricted);
+ }
return true;
}
@@ -3582,11 +3623,13 @@
final int requested = mLastRequestedExclusionHeight[side];
final int granted = mLastGrantedExclusionHeight[side];
+ final boolean inSplitScreen = getTask() != null && getTask().inSplitScreen();
+
FrameworkStatsLog.write(FrameworkStatsLog.EXCLUSION_RECT_STATE_CHANGED,
mAttrs.packageName, requested, requested - granted /* rejected */,
side + 1 /* Sides are 1-indexed in atoms.proto */,
(getConfiguration().orientation == ORIENTATION_LANDSCAPE),
- isSplitScreenWindowingMode(getWindowingMode()), (int) duration);
+ inSplitScreen, (int) duration);
}
private void initExclusionRestrictions() {
@@ -4121,8 +4164,7 @@
if (task == null) {
return false;
}
- if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()
- && !task.getRootTask().mCreatedByOrganizer) {
+ if (!inFreeformWindowingMode() && !task.getRootTask().mCreatedByOrganizer) {
return false;
}
// TODO(157912944): formalize drag-resizing so that exceptions aren't hardcoded like this
@@ -4202,9 +4244,12 @@
proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
proto.write(HAS_COMPAT_SCALE, hasCompatScale());
proto.write(GLOBAL_SCALE, mGlobalScale);
- for (Rect r : getKeepClearAreas()) {
+ for (Rect r : mKeepClearAreas) {
r.dumpDebug(proto, KEEP_CLEAR_AREAS);
}
+ for (Rect r : mUnrestrictedKeepClearAreas) {
+ r.dumpDebug(proto, UNRESTRICTED_KEEP_CLEAR_AREAS);
+ }
proto.end(token);
}
@@ -4373,7 +4418,8 @@
}
pw.println(prefix + "isOnScreen=" + isOnScreen());
pw.println(prefix + "isVisible=" + isVisible());
- pw.println(prefix + "keepClearAreas=" + getKeepClearAreas());
+ pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas
+ + ", unrestricted=" + mUnrestrictedKeepClearAreas);
if (dumpAll) {
final String visibilityString = mRequestedVisibilities.toString();
if (!visibilityString.isEmpty()) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3c122b0..31b5579 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -96,8 +96,6 @@
jmethodID notifyNoFocusedWindowAnr;
jmethodID notifyWindowUnresponsive;
jmethodID notifyWindowResponsive;
- jmethodID notifyMonitorUnresponsive;
- jmethodID notifyMonitorResponsive;
jmethodID notifyFocusChanged;
jmethodID notifySensorEvent;
jmethodID notifySensorAccuracy;
@@ -308,10 +306,9 @@
void notifyConfigurationChanged(nsecs_t when) override;
// ANR-related callbacks -- start
void notifyNoFocusedWindowAnr(const std::shared_ptr<InputApplicationHandle>& handle) override;
- void notifyWindowUnresponsive(const sp<IBinder>& token, const std::string& reason) override;
- void notifyWindowResponsive(const sp<IBinder>& token) override;
- void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) override;
- void notifyMonitorResponsive(int32_t pid) override;
+ void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<int32_t> pid,
+ const std::string& reason) override;
+ void notifyWindowResponsive(const sp<IBinder>& token, std::optional<int32_t> pid) override;
// ANR-related callbacks -- end
void notifyInputChannelBroken(const sp<IBinder>& token) override;
void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) override;
@@ -838,6 +835,7 @@
}
void NativeInputManager::notifyWindowUnresponsive(const sp<IBinder>& token,
+ std::optional<int32_t> pid,
const std::string& reason) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifyWindowUnresponsive");
@@ -851,11 +849,12 @@
ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str()));
env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowUnresponsive, tokenObj,
- reasonObj.get());
+ pid.value_or(0), pid.has_value(), reasonObj.get());
checkAndClearExceptionFromCallback(env, "notifyWindowUnresponsive");
}
-void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token) {
+void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token,
+ std::optional<int32_t> pid) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifyWindowResponsive");
#endif
@@ -866,39 +865,11 @@
jobject tokenObj = javaObjectForIBinder(env, token);
- env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj);
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj,
+ pid.value_or(0), pid.has_value());
checkAndClearExceptionFromCallback(env, "notifyWindowResponsive");
}
-void NativeInputManager::notifyMonitorUnresponsive(int32_t pid, const std::string& reason) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
- ALOGD("notifyMonitorUnresponsive");
-#endif
- ATRACE_CALL();
-
- JNIEnv* env = jniEnv();
- ScopedLocalFrame localFrame(env);
-
- ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str()));
-
- env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyMonitorUnresponsive, pid,
- reasonObj.get());
- checkAndClearExceptionFromCallback(env, "notifyMonitorUnresponsive");
-}
-
-void NativeInputManager::notifyMonitorResponsive(int32_t pid) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
- ALOGD("notifyMonitorResponsive");
-#endif
- ATRACE_CALL();
-
- JNIEnv* env = jniEnv();
- ScopedLocalFrame localFrame(env);
-
- env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyMonitorResponsive, pid);
- checkAndClearExceptionFromCallback(env, "notifyMonitorResponsive");
-}
-
void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifyInputChannelBroken");
@@ -2506,16 +2477,10 @@
"(Landroid/view/InputApplicationHandle;)V");
GET_METHOD_ID(gServiceClassInfo.notifyWindowUnresponsive, clazz, "notifyWindowUnresponsive",
- "(Landroid/os/IBinder;Ljava/lang/String;)V");
-
- GET_METHOD_ID(gServiceClassInfo.notifyMonitorUnresponsive, clazz, "notifyMonitorUnresponsive",
- "(ILjava/lang/String;)V");
+ "(Landroid/os/IBinder;IZLjava/lang/String;)V");
GET_METHOD_ID(gServiceClassInfo.notifyWindowResponsive, clazz, "notifyWindowResponsive",
- "(Landroid/os/IBinder;)V");
-
- GET_METHOD_ID(gServiceClassInfo.notifyMonitorResponsive, clazz, "notifyMonitorResponsive",
- "(I)V");
+ "(Landroid/os/IBinder;IZ)V");
GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz,
"filterInputEvent", "(Landroid/view/InputEvent;I)Z");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1ac0c26..5cda9ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8610,20 +8610,23 @@
admin.getPackageName(), userId, "set-device-owner");
Slogf.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
-
- if (setProfileOwnerOnCurrentUserIfNecessary
- && mInjector.userManagerIsHeadlessSystemUserMode()) {
- int currentForegroundUser = getCurrentForegroundUserId();
- Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin
- + " as profile owner on user " + currentForegroundUser);
- // Sets profile owner on current foreground user since
- // the human user will complete the DO setup workflow from there.
- manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
- /* managedUser= */ currentForegroundUser, /* adminExtras= */ null,
- /* showDisclaimer= */ false);
- }
- return true;
}
+
+ if (setProfileOwnerOnCurrentUserIfNecessary
+ && mInjector.userManagerIsHeadlessSystemUserMode()) {
+ int currentForegroundUser;
+ synchronized (getLockObject()) {
+ currentForegroundUser = getCurrentForegroundUserId();
+ }
+ Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin
+ + " as profile owner on user " + currentForegroundUser);
+ // Sets profile owner on current foreground user since
+ // the human user will complete the DO setup workflow from there.
+ manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
+ /* managedUser= */ currentForegroundUser, /* adminExtras= */ null,
+ /* showDisclaimer= */ false);
+ }
+ return true;
}
@Override
@@ -10853,7 +10856,7 @@
final int userHandle = user.getIdentifier();
final long id = mInjector.binderClearCallingIdentity();
try {
- maybeInstallDeviceManagerRoleHolderInUser(userHandle);
+ maybeInstallDevicePolicyManagementRoleHolderInUser(userHandle);
manageUserUnchecked(admin, profileOwner, userHandle, adminExtras,
/* showDisclaimer= */ true);
@@ -17732,7 +17735,7 @@
startTime,
callerPackage);
- maybeInstallDeviceManagerRoleHolderInUser(userInfo.id);
+ maybeInstallDevicePolicyManagementRoleHolderInUser(userInfo.id);
installExistingAdminPackage(userInfo.id, admin.getPackageName());
if (!enableAdminAndSetProfileOwner(
@@ -17800,24 +17803,25 @@
private void onCreateAndProvisionManagedProfileCompleted(
ManagedProfileProvisioningParams provisioningParams) {}
- private void maybeInstallDeviceManagerRoleHolderInUser(int targetUserId) {
- String deviceManagerRoleHolderPackageName = getDeviceManagerRoleHolderPackageName(mContext);
- if (deviceManagerRoleHolderPackageName == null) {
- Slogf.d(LOG_TAG, "No device manager role holder specified.");
+ private void maybeInstallDevicePolicyManagementRoleHolderInUser(int targetUserId) {
+ String devicePolicyManagerRoleHolderPackageName =
+ getDevicePolicyManagementRoleHolderPackageName(mContext);
+ if (devicePolicyManagerRoleHolderPackageName == null) {
+ Slogf.d(LOG_TAG, "No device policy management role holder specified.");
return;
}
try {
if (mIPackageManager.isPackageAvailable(
- deviceManagerRoleHolderPackageName, targetUserId)) {
- Slogf.d(LOG_TAG, "The device manager role holder "
- + deviceManagerRoleHolderPackageName + " is already installed in "
+ devicePolicyManagerRoleHolderPackageName, targetUserId)) {
+ Slogf.d(LOG_TAG, "The device policy management role holder "
+ + devicePolicyManagerRoleHolderPackageName + " is already installed in "
+ "user " + targetUserId);
return;
}
- Slogf.d(LOG_TAG, "Installing the device manager role holder "
- + deviceManagerRoleHolderPackageName + " in user " + targetUserId);
+ Slogf.d(LOG_TAG, "Installing the device policy management role holder "
+ + devicePolicyManagerRoleHolderPackageName + " in user " + targetUserId);
mIPackageManager.installExistingPackageAsUser(
- deviceManagerRoleHolderPackageName,
+ devicePolicyManagerRoleHolderPackageName,
targetUserId,
PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
PackageManager.INSTALL_REASON_POLICY,
@@ -17827,10 +17831,10 @@
}
}
- private String getDeviceManagerRoleHolderPackageName(Context context) {
+ private String getDevicePolicyManagementRoleHolderPackageName(Context context) {
RoleManager roleManager = context.getSystemService(RoleManager.class);
List<String> roleHolders =
- roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_MANAGER);
+ roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
if (roleHolders.isEmpty()) {
return null;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 598f9e8..f3b164c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -94,7 +94,7 @@
String getActiveApexPackageNameContainingPackage(String packageName);
- String getDeviceManagerRoleHolderPackageName(Context context);
+ String getDevicePolicyManagementRoleHolderPackageName(Context context);
}
private static final class DefaultInjector implements Injector {
@@ -110,11 +110,11 @@
}
@Override
- public String getDeviceManagerRoleHolderPackageName(Context context) {
+ public String getDevicePolicyManagementRoleHolderPackageName(Context context) {
return Binder.withCleanCallingIdentity(() -> {
RoleManager roleManager = context.getSystemService(RoleManager.class);
List<String> roleHolders =
- roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_MANAGER);
+ roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
if (roleHolders.isEmpty()) {
return null;
}
@@ -166,7 +166,7 @@
private Set<String> getDeviceManagerRoleHolders() {
HashSet<String> result = new HashSet<>();
String deviceManagerRoleHolderPackageName =
- mInjector.getDeviceManagerRoleHolderPackageName(mContext);
+ mInjector.getDevicePolicyManagementRoleHolderPackageName(mContext);
if (deviceManagerRoleHolderPackageName != null) {
result.add(deviceManagerRoleHolderPackageName);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a14e14b..4ff53ea 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -422,8 +422,8 @@
private static final String SAFETY_CENTER_SERVICE_CLASS =
"com.android.safetycenter.SafetyCenterService";
- private static final String SUPPLEMENTALPROCESS_SERVICE_CLASS =
- "com.android.server.supplementalprocess.SupplementalProcessManagerService$Lifecycle";
+ private static final String SDK_SANDBOX_MANAGER_SERVICE_CLASS =
+ "com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle";
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
@@ -2605,9 +2605,9 @@
mSystemServiceManager.startService(IncidentCompanionService.class);
t.traceEnd();
- // Supplemental Process
- t.traceBegin("StartSupplementalProcessManagerService");
- mSystemServiceManager.startService(SUPPLEMENTALPROCESS_SERVICE_CLASS);
+ // SdkSandboxManagerService
+ t.traceBegin("StarSdkSandboxManagerService");
+ mSystemServiceManager.startService(SDK_SANDBOX_MANAGER_SERVICE_CLASS);
t.traceEnd();
if (safeMode) {
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 3e60af3..670c159 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -52,7 +52,7 @@
"service-blobstore",
"service-jobscheduler",
"service-permission.impl",
- "service-supplementalprocess.impl",
+ "service-sdksandbox.impl",
"services.companion",
"services.core",
"services.devicepolicy",
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 26b5218..4a40b5f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -1000,7 +1000,7 @@
final String dummyPackageName = "com.android.test";
final String dummyClassName = ".Foo";
app.setHostingRecord(HostingRecord.byAppZygote(new ComponentName(
- dummyPackageName, dummyClassName), "", definingUid));
+ dummyPackageName, dummyClassName), "", definingUid, ""));
}
app.mServices.setConnectionGroup(connectionGroup);
app.mState.setReportedProcState(procState);
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java b/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java
index 0ae509e..182430d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java
@@ -17,6 +17,7 @@
package com.android.server.app;
+import android.annotation.Nullable;
import android.os.IInterface;
import com.android.internal.infra.AndroidFuture;
@@ -30,9 +31,10 @@
* Tests provide a service instance via {@link #FakeServiceConnector(IInterface)} that will be
* connected to and used to fulfill service jobs.
*/
-final class FakeServiceConnector<T extends IInterface> implements
- ServiceConnector<T> {
+final class FakeServiceConnector<T extends IInterface> implements ServiceConnector<T> {
private final T mService;
+ @Nullable
+ private ServiceLifecycleCallbacks mServiceLifecycleCallbacks;
private boolean mIsConnected;
private int mConnectCount = 0;
@@ -96,9 +98,17 @@
@Override
public void unbind() {
+ if (mServiceLifecycleCallbacks != null) {
+ mServiceLifecycleCallbacks.onDisconnected(mService);
+ }
mIsConnected = false;
}
+ @Override
+ public void setServiceLifecycleCallbacks(@Nullable ServiceLifecycleCallbacks<T> callbacks) {
+ mServiceLifecycleCallbacks = callbacks;
+ }
+
private void markPossibleConnection() {
if (mIsConnected) {
return;
@@ -106,6 +116,21 @@
mConnectCount += 1;
mIsConnected = true;
+
+ if (mServiceLifecycleCallbacks != null) {
+ mServiceLifecycleCallbacks.onConnected(mService);
+ }
+ }
+
+ public void killServiceProcess() {
+ if (!mIsConnected) {
+ return;
+ }
+ mIsConnected = false;
+
+ if (mServiceLifecycleCallbacks != null) {
+ mServiceLifecycleCallbacks.onBinderDied();
+ }
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index ed232e5..32a31d0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -713,6 +713,35 @@
}
@Test
+ public void gameSessionServiceDies_severalActiveGameSessions_destroysGameSessions() {
+ mGameServiceProviderInstance.start();
+
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+ startTask(11, GAME_A_MAIN_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(11);
+
+ FakeGameSession gameSession11 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(11)
+ .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
+
+ mFakeGameSessionServiceConnector.killServiceProcess();
+
+ assertThat(gameSession10.mIsDestroyed).isTrue();
+ assertThat(gameSession11.mIsDestroyed).isTrue();
+ assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
+ assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
+ }
+
+ @Test
public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception {
mGameServiceProviderInstance.start();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 6510cd1..cb9f003 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -70,7 +70,7 @@
import com.android.server.pm.pkg.parsing.ParsingPackageUtils
import com.android.server.pm.resolution.ComponentResolver
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
-import com.android.server.supplementalprocess.SupplementalProcessManagerLocal
+import com.android.server.sdksandbox.SdkSandboxManagerLocal
import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
import com.android.server.testutils.nullable
@@ -577,7 +577,7 @@
1L, systemPartitions[0].privAppFolder,
withPackage = { pkg: PackageImpl ->
val applicationInfo: ApplicationInfo = createBasicApplicationInfo(pkg)
- mockQueryServices(SupplementalProcessManagerLocal.SERVICE_INTERFACE,
+ mockQueryServices(SdkSandboxManagerLocal.SERVICE_INTERFACE,
createBasicServiceInfo(
pkg, applicationInfo, "SupplementalProcessService"))
pkg
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 953b536..1f016fb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -154,6 +154,7 @@
mMockWindowMagnificationMgr);
when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
mMockFullScreenMagnificationController);
+ when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true);
when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
mMockA11yController);
when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 3ce2ed8..f3a0b7f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -1158,7 +1158,7 @@
MagnificationCallbacks callbacks = getMagnificationCallbacks(DISPLAY_0);
callbacks.onImeWindowVisibilityChanged(true);
mMessageCapturingHandler.sendAllMessages();
- verify(mRequestObserver).onImeWindowVisibilityChanged(eq(true));
+ verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true));
}
private void setScaleToMagnifying() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index ec59090..cc6d761 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -41,6 +41,7 @@
import android.accessibilityservice.MagnificationConfig;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
@@ -100,6 +101,8 @@
@Mock
private Context mContext;
@Mock
+ PackageManager mPackageManager;
+ @Mock
private FullScreenMagnificationController mScreenMagnificationController;
private MagnificationScaleProvider mScaleProvider;
@Captor
@@ -136,6 +139,7 @@
mMockResolver = new MockContentResolver();
mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContext.getContentResolver()).thenReturn(mMockResolver);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
Settings.Secure.putFloatForUser(mMockResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE,
CURRENT_USER_ID);
@@ -748,7 +752,7 @@
MagnificationController spyController = spy(mMagnificationController);
spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
- spyController.onImeWindowVisibilityChanged(true);
+ spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
verify(spyController).logMagnificationModeWithIme(
eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
@@ -759,7 +763,7 @@
MagnificationController spyController = spy(mMagnificationController);
spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
- spyController.onImeWindowVisibilityChanged(true);
+ spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
verify(spyController).logMagnificationModeWithIme(
eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
@@ -768,7 +772,7 @@
@Test
public void imeWindowStateShown_noMagnifying_noLogAnyMode() {
MagnificationController spyController = spy(mMagnificationController);
- spyController.onImeWindowVisibilityChanged(true);
+ spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
verify(spyController, never()).logMagnificationModeWithIme(anyInt());
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
index 3822dc3..4b77764 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -94,6 +94,14 @@
}
@Test
+ public void moveWindowMagnifierToPosition() throws RemoteException {
+ mConnectionWrapper.moveWindowMagnifierToPosition(TEST_DISPLAY, 100, 150,
+ mAnimationCallback);
+ verify(mConnection).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+ eq(100f), eq(150f), any(IRemoteMagnificationAnimationCallback.class));
+ }
+
+ @Test
public void showMagnificationButton() throws RemoteException {
mConnectionWrapper.showMagnificationButton(TEST_DISPLAY,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
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 0742c09..978000a 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
@@ -21,6 +21,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
@@ -54,6 +56,8 @@
import android.view.accessibility.IWindowMagnificationConnectionCallback;
import android.view.accessibility.MagnificationAnimationCallback;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
@@ -99,12 +103,7 @@
mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext));
when(mContext.getContentResolver()).thenReturn(mResolver);
- doAnswer((InvocationOnMock invocation) -> {
- final boolean connect = (Boolean) invocation.getArguments()[0];
- mWindowMagnificationManager.setConnection(
- connect ? mMockConnection.getConnection() : null);
- return null;
- }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean());
+ stubSetConnection(false);
mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
Settings.Secure.putFloatForUser(mResolver,
@@ -112,6 +111,25 @@
CURRENT_USER_ID);
}
+ private void stubSetConnection(boolean needDelay) {
+ doAnswer((InvocationOnMock invocation) -> {
+ final boolean connect = (Boolean) invocation.getArguments()[0];
+ // Simulates setConnection() called by another process.
+ if (needDelay) {
+ final Context context = ApplicationProvider.getApplicationContext();
+ context.getMainThreadHandler().postDelayed(
+ () -> {
+ mWindowMagnificationManager.setConnection(
+ connect ? mMockConnection.getConnection() : null);
+ }, 10);
+ } else {
+ mWindowMagnificationManager.setConnection(
+ connect ? mMockConnection.getConnection() : null);
+ }
+ return true;
+ }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean());
+ }
+
@Test
public void setConnection_connectionIsNull_wrapperIsNullAndLinkToDeath() {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
@@ -275,32 +293,33 @@
}
@Test
- public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnification()
+ public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnifier()
throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region outRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
final Rect requestedRect = outRegion.getBounds();
requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
- mMockConnection.getConnectionCallback().onDrag(TEST_DISPLAY);
+ mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY),
- eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
- eq(0f), eq(0f), notNull());
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
}
@Test
- public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnification()
+ public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnifier()
throws RemoteException {
final float distanceX = 10f;
final float distanceY = 10f;
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region outRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
final Rect requestedRect = outRegion.getBounds();
@@ -310,16 +329,16 @@
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY),
- eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
- eq(0f), eq(0f), notNull());
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
}
@Test
- public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnification()
+ public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnifier()
throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region outRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
final Rect requestedRect = outRegion.getBounds();
@@ -328,12 +347,11 @@
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY),
- eq(3f), eq(500f), eq(500f), eq(0f), eq(0f), notNull());
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
}
-
@Test
- public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnification()
+ public void onRectangleOnScreenRequested_imeVisibilityDefaultInvisible_withoutMovingMagnifier()
throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
@@ -345,20 +363,56 @@
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f),
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+ }
+
+ @Test
+ public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnifier()
+ throws RemoteException {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+ final Region outRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+ final Rect requestedRect = outRegion.getBounds();
+ requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+ mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+ requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+ verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
- eq(0f), eq(0f), notNull());
+ any(IRemoteMagnificationAnimationCallback.class));
}
@Test
- public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnification()
+ public void onRectangleOnScreenRequested_imeInvisible_withoutMovingMagnifier()
throws RemoteException {
- final PointF initialPoint = new PointF(50f, 50f);
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
- mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f,
- initialPoint.x, initialPoint.y);
- mMockConnection.getConnectionCallback().onDrag(TEST_DISPLAY);
- mWindowMagnificationManager.onImeWindowVisibilityChanged(true);
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+ final Region outRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+ final Rect requestedRect = outRegion.getBounds();
+ requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, false);
+
+ mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+ requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+ verify(mMockConnection.getConnection(), never())
+ .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+ }
+
+ @Test
+ public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnifier()
+ throws RemoteException {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+ mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region outRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
final Rect requestedRect = outRegion.getBounds();
@@ -367,16 +421,16 @@
mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
- verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY),
- eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
- eq(0f), eq(0f), notNull());
+ verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+ eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
+ any(IRemoteMagnificationAnimationCallback.class));
}
@Test
- public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnification()
- throws RemoteException {
+ public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnifier() {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
final Region beforeRegion = new Region();
mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
final Rect requestedRect = beforeRegion.getBounds();
@@ -392,6 +446,48 @@
}
@Test
+ public void onRectangleOnScreenRequested_trackingDisabled_withoutMovingMagnifier() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+ mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
+ final Region beforeRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
+ final Rect requestedRect = beforeRegion.getBounds();
+ requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+ mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+ requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+ final Region afterRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion);
+ assertEquals(afterRegion, beforeRegion);
+ }
+
+ @Test
+ public void onRectangleOnScreenRequested_trackingDisabledAndEnabledMagnifier_movingMagnifier()
+ throws RemoteException {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+ mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+ mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
+ final Region beforeRegion = new Region();
+ mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
+ final Rect requestedRect = beforeRegion.getBounds();
+ requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+ mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
+ // Enabling a window magnifier again will turn on the tracking typing focus functionality.
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, NaN, NaN, NaN);
+
+ mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+ requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+ verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+ eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
+ any(IRemoteMagnificationAnimationCallback.class));
+ }
+
+ @Test
public void moveWindowMagnifier_enabled_invokeConnectionMethod() throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN);
@@ -464,7 +560,7 @@
public void
requestConnectionToNull_disableAllMagnifiersAndRequestWindowMagnificationConnection()
throws RemoteException {
- mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ assertTrue(mWindowMagnificationManager.requestConnection(true));
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
assertTrue(mWindowMagnificationManager.requestConnection(false));
@@ -499,7 +595,7 @@
@Test
public void requestConnectionToNull_expectedGetterResults() {
- mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.requestConnection(true);
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1);
mWindowMagnificationManager.requestConnection(false);
@@ -513,6 +609,20 @@
}
@Test
+ public void enableWindowMagnification_connecting_invokeConnectionMethodAfterConnected()
+ throws RemoteException {
+ stubSetConnection(true);
+ mWindowMagnificationManager.requestConnection(true);
+
+ assertTrue(mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1));
+
+ // Invoke enableWindowMagnification if the connection is connected.
+ verify(mMockConnection.getConnection()).enableWindowMagnification(
+ eq(TEST_DISPLAY), eq(3f),
+ eq(1f), eq(1f), eq(0f), eq(0f), notNull());
+ }
+
+ @Test
public void resetAllMagnification_enabledBySameId_windowMagnifiersDisabled() {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index b255a35..25cf8a8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -219,7 +219,7 @@
public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes()
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
- testMultiAuth_fingerprintSensorStartsAfter(false /* fingerprintStartsAfterDelay */);
+ testMultiAuth_fingerprintSensorStartsAfterUINotifies();
}
@Test
@@ -227,10 +227,10 @@
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class));
- testMultiAuth_fingerprintSensorStartsAfter(true /* fingerprintStartsAfterDelay */);
+ testMultiAuth_fingerprintSensorStartsAfterUINotifies();
}
- public void testMultiAuth_fingerprintSensorStartsAfter(boolean fingerprintStartsAfterDelay)
+ public void testMultiAuth_fingerprintSensorStartsAfterUINotifies()
throws Exception {
final long operationId = 123;
final int userId = 10;
@@ -274,12 +274,6 @@
// Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started.
session.onDialogAnimatedIn();
- if (fingerprintStartsAfterDelay) {
- assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
- assertEquals(BiometricSensor.STATE_COOKIE_RETURNED,
- session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
- session.onStartFingerprint();
- }
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
assertEquals(BiometricSensor.STATE_AUTHENTICATING,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index b94b690..2ad5eae 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
@@ -85,14 +86,11 @@
import org.mockito.MockitoAnnotations;
import java.util.Random;
-import java.util.concurrent.atomic.AtomicLong;
@Presubmit
@SmallTest
public class BiometricServiceTest {
- private static final String TAG = "BiometricServiceTest";
-
private static final String TEST_PACKAGE_NAME = "test_package";
private static final long TEST_REQUEST_ID = 44;
@@ -153,7 +151,7 @@
.thenReturn(mock(BiometricStrengthController.class));
when(mInjector.getTrustManager()).thenReturn(mTrustManager);
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
- when(mInjector.getRequestGenerator()).thenReturn(new AtomicLong(TEST_REQUEST_ID - 1));
+ when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -178,22 +176,22 @@
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
waitForIdle();
- verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+ verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mAuthSession),
anyInt());
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
waitForIdle();
- assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
- mBiometricService.mCurrentAuthSession.binderDied();
+ mBiometricService.mAuthSession.binderDied();
waitForIdle();
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
}
@@ -205,31 +203,31 @@
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
waitForIdle();
- verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+ verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mAuthSession),
anyInt());
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
- mBiometricService.mCurrentAuthSession.binderDied();
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
+ mBiometricService.mAuthSession.binderDied();
waitForIdle();
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
assertEquals(STATE_CLIENT_DIED_CANCELLING,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
- verify(mBiometricService.mCurrentAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
+ verify(mBiometricService.mAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
.cancelAuthenticationFromService(any(), any(), anyLong());
// Simulate ERROR_CANCELED received from HAL
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
waitForIdle();
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -265,12 +263,12 @@
Authenticators.DEVICE_CREDENTIAL);
waitForIdle();
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
// StatusBar showBiometricDialog invoked
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -304,21 +302,21 @@
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(0 /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS),
eq(0 /* vendorCode */));
}
@Test
public void testAuthenticate_notStrongEnough_returnsHardwareNotPresent() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
Authenticators.BIOMETRIC_STRONG);
@@ -335,7 +333,7 @@
// is able to proceed.
final int[] modalities = new int[] {
- BiometricAuthenticator.TYPE_FINGERPRINT,
+ TYPE_FINGERPRINT,
BiometricAuthenticator.TYPE_FACE,
};
@@ -356,7 +354,7 @@
// StatusBar showBiometricDialog invoked with face, which was set up to be STRONG
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[] {SENSOR_ID_FACE}),
eq(false) /* credentialAllowed */,
@@ -377,14 +375,14 @@
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(0 /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
eq(0 /* vendorCode */));
}
@@ -415,13 +413,13 @@
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
final byte[] HAT = generateRandomHAT();
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FACE,
HAT);
waitForIdle();
// Confirmation is required
assertEquals(STATE_AUTH_PENDING_CONFIRM,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
// Enrolled, not disabled in settings, user doesn't require confirmation in settings
resetReceivers();
@@ -431,25 +429,25 @@
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
null /* authenticators */);
waitForIdle();
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FACE,
HAT);
waitForIdle();
// Confirmation not required, waiting for dialog to dismiss
assertEquals(STATE_AUTHENTICATED_PENDING_SYSUI,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
}
@Test
public void testAuthenticate_happyPathWithoutConfirmation_strongBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
testAuthenticate_happyPathWithoutConfirmation(true /* isStrongBiometric */);
}
@Test
public void testAuthenticate_happyPathWithoutConfirmation_weakBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
testAuthenticate_happyPathWithoutConfirmation(false /* isStrongBiometric */);
}
@@ -461,7 +459,7 @@
waitForIdle();
// Creates a pending auth session with the correct initial states
- assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
// Invokes <Modality>Service#prepareForAuthentication
ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -477,19 +475,19 @@
cookieCaptor.capture() /* cookie */,
anyBoolean() /* allowBackgroundAuthentication */);
- // onReadyForAuthentication, mCurrentAuthSession state OK
- mBiometricService.mImpl.onReadyForAuthentication(cookieCaptor.getValue());
+ // onReadyForAuthentication, mAuthSession state OK
+ mBiometricService.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookieCaptor.getValue());
waitForIdle();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
// startPreparedClient invoked
- mBiometricService.mCurrentAuthSession.onDialogAnimatedIn();
+ mBiometricService.mAuthSession.onDialogAnimatedIn();
verify(mBiometricService.mSensors.get(0).impl)
.startPreparedClient(cookieCaptor.getValue());
// StatusBar showBiometricDialog invoked
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
any(),
eq(false) /* credentialAllowed */,
@@ -502,18 +500,18 @@
// Hardware authenticated
final byte[] HAT = generateRandomHAT();
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FINGERPRINT,
HAT);
waitForIdle();
// Waiting for SystemUI to send dismissed callback
assertEquals(STATE_AUTHENTICATED_PENDING_SYSUI,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
// Notify SystemUI hardware authenticated
- verify(mBiometricService.mStatusBarService).onBiometricAuthenticated();
+ verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(TYPE_FINGERPRINT);
// SystemUI sends callback with dismissed reason
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
null /* credentialAttestation */);
waitForIdle();
@@ -527,7 +525,7 @@
verify(mReceiver1).onAuthenticationSucceeded(
BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
// Current session becomes null
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -542,11 +540,11 @@
waitForIdle();
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
assertEquals(Authenticators.DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators());
+ mBiometricService.mAuthSession.mPromptInfo.getAuthenticators());
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -578,16 +576,16 @@
// Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not
// sent to KeyStore yet
final byte[] HAT = generateRandomHAT();
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FACE,
HAT);
waitForIdle();
// Waiting for SystemUI to send confirmation callback
- assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
// SystemUI sends confirm, HAT is sent to keystore and client is notified.
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED,
null /* credentialAttestation */);
waitForIdle();
@@ -624,33 +622,34 @@
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onAuthenticationFailed(SENSOR_ID_FACE);
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationFailed(SENSOR_ID_FACE);
waitForIdle();
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
- assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
}
@Test
public void testRejectFingerprint_whenAuthenticating_notifiesAndKeepsAuthenticating()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onAuthenticationFailed(SENSOR_ID_FINGERPRINT);
+ mBiometricService.mAuthSession.mSensorReceiver
+ .onAuthenticationFailed(SENSOR_ID_FINGERPRINT);
waitForIdle();
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_NONE),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
@@ -678,14 +677,14 @@
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
waitForIdle();
- assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
@@ -694,15 +693,15 @@
verify(mReceiver1, never()).onAuthenticationFailed();
// No auth session. Pressing try again will create one.
- assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
// Pressing "Try again" on SystemUI
- mBiometricService.mSysuiReceiver.onTryAgainPressed();
+ mBiometricService.mAuthSession.mSysuiReceiver.onTryAgainPressed();
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
// AuthSession is now resuming
- assertEquals(STATE_AUTH_PAUSED_RESUMING, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_PAUSED_RESUMING, mBiometricService.mAuthSession.getState());
// Test resuming when hardware becomes ready. SystemUI should not be requested to
// show another dialog since it's already showing.
@@ -728,14 +727,14 @@
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
waitForIdle();
@@ -748,7 +747,7 @@
// Dialog is hidden immediately
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
// Auth session is over
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -757,61 +756,61 @@
// For errors that show in SystemUI, BiometricService stays in STATE_ERROR_PENDING_SYSUI
// until SystemUI notifies us that the dialog is dismissed at which point the current
// session is done.
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
0 /* vendorCode */);
waitForIdle();
// Sends error to SystemUI and does not notify client yet
- assertEquals(STATE_ERROR_PENDING_SYSUI, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_ERROR_PENDING_SYSUI, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(0 /* vendorCode */));
verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
// SystemUI animation completed, client is notified, auth session is over
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_ERROR, null /* credentialAttestation */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(0 /* vendorCode */));
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
public void testErrorFromHal_whilePreparingAuthentication_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
waitForIdle();
- assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
- mBiometricService.mBiometricSensorReceiver.onError(
+ assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForPendingSession(mBiometricService.mCurrentAuthSession),
+ getCookieForPendingSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
0 /* vendorCode */);
waitForIdle();
// We should be showing device credential now
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
assertEquals(Authenticators.DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators());
+ mBiometricService.mAuthSession.mPromptInfo.getAuthenticators());
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -826,23 +825,23 @@
@Test
public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
waitForIdle();
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForPendingSession(mBiometricService.mCurrentAuthSession),
+ getCookieForPendingSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
0 /* vendorCode */);
waitForIdle();
// Error is sent to client
- verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
eq(0) /* vendorCode */);
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -861,7 +860,7 @@
private void testBiometricAuth_whenLockout(@LockoutTracker.LockoutMode int lockoutMode,
int biometricPromptError) throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(lockoutMode);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
@@ -869,16 +868,15 @@
waitForIdle();
// Modality and error are sent
- verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
eq(biometricPromptError), eq(0) /* vendorCode */);
}
@Test
public void testBiometricOrCredentialAuth_whenBiometricLockout_showsCredential()
throws Exception {
- when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
- .thenReturn(true);
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LockoutTracker.LOCKOUT_PERMANENT);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
@@ -887,13 +885,13 @@
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
assertEquals(Authenticators.DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators());
+ mBiometricService.mAuthSession.mPromptInfo.getAuthenticators());
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -959,73 +957,73 @@
@Test
public void testErrorFromHal_whileShowingDeviceCredential_doesntNotifySystemUI()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
- mBiometricService.mSysuiReceiver.onDeviceCredentialPressed();
+ mBiometricService.mAuthSession.mSysuiReceiver.onDeviceCredentialPressed();
waitForIdle();
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
waitForIdle();
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
}
@Test
public void testLockout_whileAuthenticating_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
0 /* vendorCode */);
waitForIdle();
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
eq(0 /* vendorCode */));
}
@Test
public void testLockout_whenAuthenticating_credentialNotAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
0 /* vendorCode */);
waitForIdle();
assertEquals(STATE_ERROR_PENDING_SYSUI,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(0 /* vendorCode */));
}
@@ -1033,20 +1031,20 @@
@Test
public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mSensors.get(0).impl).cancelAuthenticationFromService(
any(), any(), anyLong());
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
@@ -1055,12 +1053,12 @@
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_NEGATIVE, null /* credentialAttestation */);
waitForIdle();
@@ -1069,18 +1067,17 @@
}
@Test
- public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws
- Exception {
+ public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FACE,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
0 /* vendorCode */);
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
waitForIdle();
@@ -1094,10 +1091,10 @@
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded(
+ mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FACE,
new byte[69] /* HAT */);
- mBiometricService.mSysuiReceiver.onDialogDismissed(
+ mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
waitForIdle();
@@ -1108,19 +1105,19 @@
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
- assertNull(mBiometricService.mCurrentAuthSession);
+ assertNull(mBiometricService.mAuthSession);
}
@Test
public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception {
when(mContext.getResources().getString(anyInt())).thenReturn("test string");
- final int modality = BiometricAuthenticator.TYPE_FINGERPRINT;
+ final int modality = TYPE_FINGERPRINT;
setupAuthForOnly(modality, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mBiometricSensorReceiver.onAcquired(
+ mBiometricService.mAuthSession.mSensorReceiver.onAcquired(
SENSOR_ID_FINGERPRINT,
FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
0 /* vendorCode */);
@@ -1130,29 +1127,29 @@
// string is retrieved for now, but it's also very unlikely to break anyway.
verify(mBiometricService.mStatusBarService)
.onBiometricHelp(eq(modality), anyString());
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
public void testCancel_whenAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
- mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
+ mBiometricService.mImpl.cancelAuthentication(mBiometricService.mAuthSession.mToken,
TEST_PACKAGE_NAME, TEST_REQUEST_ID);
waitForIdle();
// Pretend that the HAL has responded to cancel with ERROR_CANCELED
- mBiometricService.mBiometricSensorReceiver.onError(
+ mBiometricService.mAuthSession.mSensorReceiver.onError(
SENSOR_ID_FINGERPRINT,
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ getCookieForCurrentSession(mBiometricService.mAuthSession),
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
waitForIdle();
// Hides system dialog and invokes the onError callback
- verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
@@ -1161,7 +1158,7 @@
@Test
public void testCanAuthenticate_whenDeviceHasRequestedBiometricStrength() throws Exception {
// When only biometric is requested, and sensor is strong enough
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
@@ -1170,7 +1167,7 @@
@Test
public void testCanAuthenticate_whenDeviceDoesNotHaveRequestedBiometricStrength()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
// When only biometric is requested, and sensor is not strong enough
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
@@ -1208,9 +1205,8 @@
@Test
public void testCanAuthenticate_whenNoBiometricsEnrolled() throws Exception {
// With credential set up, test the following.
- when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
- .thenReturn(true);
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
false /* enrolled */);
// When only biometric is requested
@@ -1277,7 +1273,7 @@
private void testCanAuthenticate_whenLockedOut(@LockoutTracker.LockoutMode int lockoutMode)
throws Exception {
// When only biometric is requested, and sensor is strong enough
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(lockoutMode);
@@ -1311,7 +1307,7 @@
for (int i = 0; i < testCases.length; i++) {
final BiometricSensor sensor =
new BiometricSensor(mContext, 0 /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT,
+ TYPE_FINGERPRINT,
testCases[i][0],
mock(IBiometricAuthenticator.class)) {
@Override
@@ -1341,7 +1337,7 @@
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService.mImpl.registerAuthenticator(0 /* testId */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
verify(mBiometricService.mBiometricStrengthController).updateStrengths();
@@ -1360,7 +1356,7 @@
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService.mImpl.registerAuthenticator(testId /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
// Downgrade the authenticator
@@ -1378,7 +1374,7 @@
false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(TYPE_FINGERPRINT),
eq(BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED),
eq(0) /* vendorCode */);
@@ -1392,7 +1388,7 @@
authenticators);
waitForIdle();
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[] {testId}),
eq(false) /* credentialAllowed */,
@@ -1414,9 +1410,9 @@
false /* requireConfirmation */,
authenticators);
waitForIdle();
- assertTrue(Utils.isCredentialRequested(mBiometricService.mCurrentAuthSession.mPromptInfo));
+ assertTrue(Utils.isCredentialRequested(mBiometricService.mAuthSession.mPromptInfo));
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[0]) /* sensorIds */,
eq(true) /* credentialAllowed */,
@@ -1442,7 +1438,7 @@
false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
- eq(mBiometricService.mCurrentAuthSession.mPromptInfo),
+ eq(mBiometricService.mAuthSession.mPromptInfo),
any(IBiometricSysuiReceiver.class),
AdditionalMatchers.aryEq(new int[] {testId}) /* sensorIds */,
eq(false) /* credentialAllowed */,
@@ -1495,29 +1491,29 @@
@Test
public void testWorkAuthentication_fingerprintWorksIfNotDisabledByDevicePolicyManager()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
.thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1,
Authenticators.BIOMETRIC_STRONG);
waitForIdle();
- assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
startPendingAuthSession(mBiometricService);
waitForIdle();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
public void testAuthentication_normalAppIgnoresDevicePolicy() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG);
waitForIdle();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
@@ -1530,18 +1526,17 @@
invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1,
Authenticators.BIOMETRIC_STRONG);
waitForIdle();
- assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
startPendingAuthSession(mBiometricService);
waitForIdle();
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
}
@Test
public void testWorkAuthentication_fingerprintFailsIfDisabledByDevicePolicyManager()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
- when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
- .thenReturn(true);
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
@@ -1555,9 +1550,9 @@
invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver2,
Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
waitForIdle();
- assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertNotNull(mBiometricService.mAuthSession);
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
- mBiometricService.mCurrentAuthSession.getState());
+ mBiometricService.mAuthSession.getState());
verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
}
@@ -1580,7 +1575,7 @@
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
- if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+ if ((modality & TYPE_FINGERPRINT) != 0) {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(enrolled);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
@@ -1614,7 +1609,7 @@
final int modality = modalities[i];
final int strength = strengths[i];
- if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+ if ((modality & TYPE_FINGERPRINT) != 0) {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
@@ -1654,8 +1649,9 @@
startPendingAuthSession(mBiometricService);
waitForIdle();
- assertNotNull(mBiometricService.mCurrentAuthSession);
- assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+ assertNotNull(mBiometricService.mAuthSession);
+ assertEquals(TEST_REQUEST_ID, mBiometricService.mAuthSession.getRequestId());
+ assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
return requestId;
}
@@ -1663,14 +1659,14 @@
private static void startPendingAuthSession(BiometricService service) throws Exception {
// Get the cookie so we can pretend the hardware is ready to authenticate
// Currently we only support single modality per auth
- final PreAuthInfo preAuthInfo = service.mCurrentAuthSession.mPreAuthInfo;
+ final PreAuthInfo preAuthInfo = service.mAuthSession.mPreAuthInfo;
assertEquals(preAuthInfo.eligibleSensors.size(), 1);
assertEquals(preAuthInfo.numSensorsWaitingForCookie(), 1);
final int cookie = preAuthInfo.eligibleSensors.get(0).getCookie();
assertNotEquals(cookie, 0);
- service.mImpl.onReadyForAuthentication(cookie);
+ service.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookie);
}
private static long invokeAuthenticate(IBiometricService.Stub service,
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 bfb0be7..f40b31a 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
@@ -29,12 +29,8 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-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;
import androidx.test.InstrumentationRegistry;
@@ -43,9 +39,11 @@
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.LinkedList;
@@ -53,39 +51,38 @@
@SmallTest
public class CoexCoordinatorTest {
- private static final String TAG = "CoexCoordinatorTest";
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
- private CoexCoordinator mCoexCoordinator;
- private Handler mHandler;
-
- @Mock
- private Context mContext;
@Mock
private CoexCoordinator.Callback mCallback;
@Mock
private CoexCoordinator.ErrorCallback mErrorCallback;
+ @Mock
+ private AuthenticationClient mFaceClient;
+ @Mock
+ private AuthenticationClient mFingerprintClient;
+ @Mock(extraInterfaces = {Udfps.class})
+ private AuthenticationClient mUdfpsClient;
+
+ private CoexCoordinator mCoexCoordinator;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mHandler = new Handler(Looper.getMainLooper());
-
mCoexCoordinator = CoexCoordinator.getInstance();
mCoexCoordinator.setAdvancedLogicEnabled(true);
mCoexCoordinator.setFaceHapticDisabledWhenNonBypass(true);
+ mCoexCoordinator.reset();
}
@Test
public void testBiometricPrompt_authSuccess() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isBiometricPrompt()).thenReturn(true);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
-
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFaceClient, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -93,15 +90,12 @@
@Test
public void testBiometricPrompt_authReject_whenNotLockedOut() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isBiometricPrompt()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
- client, LockoutTracker.LOCKOUT_NONE, mCallback);
+ mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -109,30 +103,97 @@
@Test
public void testBiometricPrompt_authReject_whenLockedOut() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isBiometricPrompt()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
- client, LockoutTracker.LOCKOUT_TIMED, mCallback);
+ mFaceClient, LockoutTracker.LOCKOUT_TIMED, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
verify(mCallback).handleLifecycleAfterAuth();
}
@Test
+ public void testBiometricPrompt_coex_success() {
+ testBiometricPrompt_coex_success(false /* twice */);
+ }
+
+ @Test
+ public void testBiometricPrompt_coex_successWithoutDouble() {
+ testBiometricPrompt_coex_success(true /* twice */);
+ }
+
+ private void testBiometricPrompt_coex_success(boolean twice) {
+ initFaceAndFingerprintForBiometricPrompt();
+ when(mFaceClient.wasAuthSuccessful()).thenReturn(true);
+ when(mUdfpsClient.wasAuthSuccessful()).thenReturn(twice, true);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
+
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFaceClient, mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mUdfpsClient, mCallback);
+
+ if (twice) {
+ verify(mCallback, never()).sendHapticFeedback();
+ } else {
+ verify(mCallback).sendHapticFeedback();
+ }
+ }
+
+ @Test
+ public void testBiometricPrompt_coex_reject() {
+ initFaceAndFingerprintForBiometricPrompt();
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
+
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+ mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback);
+
+ verify(mCallback, never()).sendHapticFeedback();
+
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+ mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback);
+
+ verify(mCallback).sendHapticFeedback();
+ }
+
+ @Test
+ public void testBiometricPrompt_coex_errorNoHaptics() {
+ initFaceAndFingerprintForBiometricPrompt();
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
+
+ mCoexCoordinator.onAuthenticationError(mFaceClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+ mCoexCoordinator.onAuthenticationError(mUdfpsClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+
+ verify(mErrorCallback, never()).sendHapticFeedback();
+ }
+
+ private void initFaceAndFingerprintForBiometricPrompt() {
+ when(mFaceClient.isKeyguard()).thenReturn(false);
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
+ when(mFaceClient.wasAuthAttempted()).thenReturn(true);
+ when(mUdfpsClient.isKeyguard()).thenReturn(false);
+ when(mUdfpsClient.isBiometricPrompt()).thenReturn(true);
+ when(mUdfpsClient.wasAuthAttempted()).thenReturn(true);
+ }
+
+ @Test
public void testKeyguard_faceAuthOnly_success() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isKeyguard()).thenReturn(true);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
-
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFaceClient, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -140,21 +201,16 @@
@Test
public void testKeyguard_faceAuth_udfpsNotTouching_faceSuccess() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
- 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, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
- mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFaceClient, mCallback);
// Haptics tested in #testKeyguard_bypass_haptics. Let's leave this commented out (instead
// of removed) to keep this context.
// verify(mCallback).sendHapticFeedback();
@@ -192,25 +248,19 @@
private void testKeyguard_bypass_haptics(boolean bypassEnabled, boolean faceAccepted,
boolean shouldReceiveHaptics) {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
-
- 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.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
if (faceAccepted) {
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient,
mCallback);
} else {
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_NONE, mCallback);
}
@@ -244,24 +294,18 @@
private void testKeyguard_faceAuth_udfpsTouching_faceSuccess(boolean thenUdfpsAccepted,
long udfpsRejectedAfterMs) {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
+ when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
-
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
- when (udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
// For easier reading
final CoexCoordinator.Callback faceCallback = mCallback;
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient,
faceCallback);
verify(faceCallback, never()).sendHapticFeedback();
verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
@@ -272,9 +316,9 @@
// Reset the mock
CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
assertEquals(1, mCoexCoordinator.mSuccessfulAuths.size());
- assertEquals(faceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient);
+ assertEquals(mFaceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient);
if (thenUdfpsAccepted) {
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient,
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient,
udfpsCallback);
verify(udfpsCallback).sendHapticFeedback();
verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */);
@@ -284,7 +328,7 @@
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
} else {
- mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient,
+ mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, mUdfpsClient,
LockoutTracker.LOCKOUT_NONE, udfpsCallback);
if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) {
verify(udfpsCallback, never()).sendHapticFeedback();
@@ -310,56 +354,44 @@
@Test
public void testKeyguard_udfpsAuthSuccess_whileFaceScanning() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient,
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient,
mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(true));
- verify(faceClient).cancel();
+ verify(mFaceClient).cancel();
verify(mCallback).handleLifecycleAfterAuth();
}
@Test
public void testKeyguard_faceRejectedWhenUdfpsTouching_thenUdfpsRejected() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_NONE, mCallback);
verify(mCallback, never()).sendHapticFeedback();
verify(mCallback).handleLifecycleAfterAuth();
// BiometricScheduler removes the face authentication client after rejection
- mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+ mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
// Then UDFPS rejected
CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
- mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, udfpsClient,
+ mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mUdfpsClient,
LockoutTracker.LOCKOUT_NONE, udfpsCallback);
verify(udfpsCallback).sendHapticFeedback();
verify(udfpsCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
@@ -368,26 +400,20 @@
@Test
public void testKeyguard_udfpsRejected_thenFaceRejected_noKeyguardBypass() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
+ when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(faceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
- withSettings().extraInterfaces(Udfps.class));
- when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(udfpsClient.isKeyguard()).thenReturn(true);
- when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
-
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient,
- LockoutTracker.LOCKOUT_NONE, mCallback);
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+ mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback);
// Auth was attempted
- when(udfpsClient.getState())
+ when(mUdfpsClient.getState())
.thenReturn(AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED);
verify(mCallback, never()).sendHapticFeedback();
verify(mCallback).handleLifecycleAfterAuth();
@@ -395,7 +421,7 @@
// Then face rejected. Note that scheduler leaves UDFPS in the CoexCoordinator since
// unlike face, its lifecycle becomes "paused" instead of "finished".
CoexCoordinator.Callback faceCallback = mock(CoexCoordinator.Callback.class);
- mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_NONE, faceCallback);
verify(faceCallback).sendHapticFeedback();
verify(faceCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
@@ -404,20 +430,16 @@
@Test
public void testKeyguard_capacitiveAccepted_whenFaceScanning() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFingerprintClient.isKeyguard()).thenReturn(true);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient);
- AuthenticationClient<?> fpClient = mock(AuthenticationClient.class);
- when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(fpClient.isKeyguard()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient);
-
- mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, fpClient, mCallback);
+ mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */,
+ mFingerprintClient, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -425,21 +447,16 @@
@Test
public void testKeyguard_capacitiveRejected_whenFaceScanning() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ when(mFingerprintClient.isKeyguard()).thenReturn(true);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(true);
- when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient);
- AuthenticationClient<?> fpClient = mock(AuthenticationClient.class);
- when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
- when(fpClient.isKeyguard()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient);
-
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, fpClient,
- LockoutTracker.LOCKOUT_NONE, mCallback);
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
+ mFingerprintClient, LockoutTracker.LOCKOUT_NONE, mCallback);
verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
@@ -447,14 +464,11 @@
@Test
public void testNonKeyguard_rejectAndNotLockedOut() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(false);
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(false);
- when(faceClient.isBiometricPrompt()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_NONE, mCallback);
verify(mCallback).sendHapticFeedback();
@@ -464,14 +478,11 @@
@Test
public void testNonKeyguard_rejectLockedOut() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(false);
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
- AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
- when(faceClient.isKeyguard()).thenReturn(false);
- when(faceClient.isBiometricPrompt()).thenReturn(true);
-
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
- mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient,
LockoutTracker.LOCKOUT_TIMED, mCallback);
verify(mCallback).sendHapticFeedback();
@@ -496,16 +507,13 @@
@Test
public void testBiometricPrompt_FaceError() {
- mCoexCoordinator.reset();
+ when(mFaceClient.isBiometricPrompt()).thenReturn(true);
+ when(mFaceClient.wasAuthAttempted()).thenReturn(true);
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
- when(client.isBiometricPrompt()).thenReturn(true);
- when(client.wasAuthAttempted()).thenReturn(true);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
-
- mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
- mErrorCallback);
+ mCoexCoordinator.onAuthenticationError(mFaceClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
verify(mErrorCallback).sendHapticFeedback();
}
@@ -520,18 +528,15 @@
}
private void testKeyguard_faceAuthOnly(boolean bypassEnabled) {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(mFaceClient.wasAuthAttempted()).thenReturn(true);
+ when(mFaceClient.wasUserDetected()).thenReturn(true);
- 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, mFaceClient);
- mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
-
- mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
- mErrorCallback);
+ mCoexCoordinator.onAuthenticationError(mFaceClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
verify(mErrorCallback).sendHapticFeedback();
}
@@ -546,23 +551,17 @@
}
private void testKeyguard_coex_faceError(boolean bypassEnabled) {
- mCoexCoordinator.reset();
+ when(mFaceClient.isKeyguard()).thenReturn(true);
+ when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(mFaceClient.wasAuthAttempted()).thenReturn(true);
+ when(mFaceClient.wasUserDetected()).thenReturn(true);
+ when(mUdfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false);
- 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);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient);
- 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,
+ mCoexCoordinator.onAuthenticationError(mFaceClient,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
if (bypassEnabled) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
new file mode 100644
index 0000000..76a5acc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 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.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Presubmit
+@SmallTest
+public class FaceRemovalClientTest {
+
+ private static final int USER_ID = 12;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Mock
+ private BiometricUtils<Face> mUtils;
+ @Mock
+ private BiometricAuthenticator.Identifier mIdentifier;
+ private Map<Integer, Long> mAuthenticatorIds = new HashMap<Integer, Long>();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void testFaceRemovalClient() throws RemoteException {
+ final int authenticatorId = 1;
+ int[] authenticatorIds = new int[]{authenticatorId};
+ final FaceRemovalClient client = createClient(1, authenticatorIds);
+ when(mIdentifier.getBiometricId()).thenReturn(authenticatorId);
+ client.start(mCallback);
+ verify(mHal).removeEnrollments(authenticatorIds);
+ client.onRemoved(mIdentifier, 0 /* remaining */);
+ verify(mClientMonitorCallbackConverter).onRemoved(
+ eq(mIdentifier) /* identifier */, eq(0) /* remaining */);
+ verify(mCallback).onClientFinished(client, true);
+ }
+
+ @Test
+ public void clientSendsErrorWhenHALFailsToRemoveEnrollment() throws RemoteException {
+ final FaceRemovalClient client = createClient(1, new int[0]);
+ client.start(mCallback);
+ client.onRemoved(null, 0 /* remaining */);
+ verify(mClientMonitorCallbackConverter).onError(eq(5) /* sensorId */, anyInt(),
+ eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_REMOVE), eq(0) /* vendorCode*/);
+ verify(mCallback).onClientFinished(client, false);
+ }
+
+ private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FaceRemovalClient(mContext, () -> aidl, mToken,
+ mClientMonitorCallbackConverter, biometricIds, USER_ID,
+ "own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+ mAuthenticatorIds);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 533fb2d..4f6fc3d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -309,7 +309,7 @@
@Test
public void testGetNonRequiredApps_managedProfile_roleHolder_works() {
- when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+ when(mInjector.getDevicePolicyManagementRoleHolderPackageName(any()))
.thenReturn(ROLE_HOLDER_PACKAGE_NAME);
setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
@@ -319,7 +319,7 @@
@Test
public void testGetNonRequiredApps_managedDevice_roleHolder_works() {
- when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+ when(mInjector.getDevicePolicyManagementRoleHolderPackageName(any()))
.thenReturn(ROLE_HOLDER_PACKAGE_NAME);
setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
index acb20ed..6de7fdd 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
@@ -229,8 +229,8 @@
}
private void verifyAlarm(long when, String tag, AlarmManager.OnAlarmListener alarm) {
- verify(mAlarmManager).set(
- eq(AlarmManager.ELAPSED_REALTIME),
+ verify(mAlarmManager).setExact(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
eq(when),
eq(tag),
eq(alarm),
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index eeaf781..bfdffc0 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -39,9 +39,11 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedWriter;
import java.io.File;
+import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
@@ -137,13 +139,14 @@
new ArraySet<>(Arrays.asList("GUEST", "PROFILE")));
final File folder1 = createTempSubfolder("folder1");
- createTempFile(folder1, "permFile1.xml", contents1);
+ createTempFile(folder1, "permissionFile1.xml", contents1);
final File folder2 = createTempSubfolder("folder2");
- createTempFile(folder2, "permFile2.xml", contents2);
+ createTempFile(folder2, "permissionFile2.xml", contents2);
- // Also, make a third file, but with the name folder1/permFile2.xml, to prove no conflicts.
- createTempFile(folder1, "permFile2.xml", contents3);
+ // Also, make a third file, but with the name folder1/permissionFile2.xml, to prove no
+ // conflicts.
+ createTempFile(folder1, "permissionFile2.xml", contents3);
readPermissions(folder1, /* No permission needed anyway */ 0);
readPermissions(folder2, /* No permission needed anyway */ 0);
@@ -333,6 +336,91 @@
assertThat(mSysConfig.getAllowedVendorApexes()).isEmpty();
}
+ @Test
+ public void readApexPrivAppPermissions_addAllPermissions()
+ throws Exception {
+ final String contents =
+ "<privapp-permissions package=\"com.android.apk_in_apex\">"
+ + "<permission name=\"android.permission.FOO\"/>"
+ + "<deny-permission name=\"android.permission.BAR\"/>"
+ + "</privapp-permissions>";
+ File apexDir = createTempSubfolder("apex");
+ File permissionFile = createTempFile(
+ createTempSubfolder("apex/com.android.my_module/etc/permissions"),
+ "permissions.xml", contents);
+ XmlPullParser parser = readXmlUntilStartTag(permissionFile);
+
+ mSysConfig.readApexPrivAppPermissions(parser, permissionFile, apexDir.toPath());
+
+ assertThat(mSysConfig.getApexPrivAppPermissions("com.android.my_module",
+ "com.android.apk_in_apex"))
+ .containsExactly("android.permission.FOO");
+ assertThat(mSysConfig.getApexPrivAppDenyPermissions("com.android.my_module",
+ "com.android.apk_in_apex"))
+ .containsExactly("android.permission.BAR");
+ }
+
+ @Test
+ public void pruneVendorApexPrivappAllowlists_removeVendor()
+ throws Exception {
+ File apexDir = createTempSubfolder("apex");
+
+ // Read non-vendor apex permission allowlists
+ final String allowlistNonVendorContents =
+ "<privapp-permissions package=\"com.android.apk_in_non_vendor_apex\">"
+ + "<permission name=\"android.permission.FOO\"/>"
+ + "<deny-permission name=\"android.permission.BAR\"/>"
+ + "</privapp-permissions>";
+ File nonVendorPermDir =
+ createTempSubfolder("apex/com.android.non_vendor/etc/permissions");
+ File nonVendorPermissionFile =
+ createTempFile(nonVendorPermDir, "permissions.xml", allowlistNonVendorContents);
+ XmlPullParser nonVendorParser = readXmlUntilStartTag(nonVendorPermissionFile);
+ mSysConfig.readApexPrivAppPermissions(nonVendorParser, nonVendorPermissionFile,
+ apexDir.toPath());
+
+ // Read vendor apex permission allowlists
+ final String allowlistVendorContents =
+ "<privapp-permissions package=\"com.android.apk_in_vendor_apex\">"
+ + "<permission name=\"android.permission.BAZ\"/>"
+ + "<deny-permission name=\"android.permission.BAT\"/>"
+ + "</privapp-permissions>";
+ File vendorPermissionFile =
+ createTempFile(createTempSubfolder("apex/com.android.vendor/etc/permissions"),
+ "permissions.xml", allowlistNonVendorContents);
+ XmlPullParser vendorParser = readXmlUntilStartTag(vendorPermissionFile);
+ mSysConfig.readApexPrivAppPermissions(vendorParser, vendorPermissionFile,
+ apexDir.toPath());
+
+ // Read allowed vendor apex list
+ final String allowedVendorContents =
+ "<config>\n"
+ + " <allowed-vendor-apex package=\"com.android.vendor\" "
+ + "installerPackage=\"com.installer\" />\n"
+ + "</config>";
+ final File allowedVendorFolder = createTempSubfolder("folder");
+ createTempFile(allowedVendorFolder, "vendor-apex-allowlist.xml", allowedVendorContents);
+ readPermissions(allowedVendorFolder, /* Grant all permission flags */ ~0);
+
+ // Finally, prune non-vendor allowlists.
+ // There is no guarantee in which order the above reads will be done, however pruning
+ // will always happen last.
+ mSysConfig.pruneVendorApexPrivappAllowlists();
+
+ assertThat(mSysConfig.getApexPrivAppPermissions("com.android.non_vendor",
+ "com.android.apk_in_non_vendor_apex"))
+ .containsExactly("android.permission.FOO");
+ assertThat(mSysConfig.getApexPrivAppDenyPermissions("com.android.non_vendor",
+ "com.android.apk_in_non_vendor_apex"))
+ .containsExactly("android.permission.BAR");
+ assertThat(mSysConfig.getApexPrivAppPermissions("com.android.vendor",
+ "com.android.apk_in_vendor_apex"))
+ .isNull();
+ assertThat(mSysConfig.getApexPrivAppDenyPermissions("com.android.vendor",
+ "com.android.apk_in_vendor_apex"))
+ .isNull();
+ }
+
/**
* Tests that readPermissions works correctly for a library with on-bootclasspath-before
* and on-bootclasspath-since.
@@ -492,6 +580,25 @@
}
/**
+ * Create an {@link XmlPullParser} for {@param permissionFile} and begin parsing it until
+ * reaching the root tag.
+ */
+ private XmlPullParser readXmlUntilStartTag(File permissionFile)
+ throws IOException, XmlPullParserException {
+ FileReader permReader = new FileReader(permissionFile);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(permReader);
+ int type;
+ do {
+ type = parser.next();
+ } while (type != parser.START_TAG && type != parser.END_DOCUMENT);
+ if (type != parser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+ return parser;
+ }
+
+ /**
* Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
*
* @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
@@ -500,7 +607,7 @@
private File createTempSubfolder(String folderName)
throws IOException {
File folder = new File(mTemporaryFolder.getRoot(), folderName);
- folder.mkdir();
+ folder.mkdirs();
return folder;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 55d6df9..0c8394e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -352,6 +352,6 @@
spyOn(callback);
mInterceptor.onActivityLaunched(null, null);
- verify(callback, times(1)).onActivityLaunched(any(), any());
+ verify(callback, times(1)).onActivityLaunched(any(), any(), any());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 16c5bfe..6fe2d33 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1085,7 +1085,7 @@
starter.setActivityOptions(options.toBundle())
.setReason("testWindowingModeOptionsLaunchAdjacent")
.setOutActivity(outActivity).execute();
- assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse();
+ assertThat(outActivity[0].inMultiWindowMode()).isFalse();
// Move activity to split-screen-primary stack and make sure it has the focus.
TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index c083870..925f4f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1914,7 +1914,7 @@
final WindowState nextImeTargetApp = createWindow(null /* parent */,
TYPE_BASE_APPLICATION, "nextImeTargetApp");
spyOn(child1);
- doReturn(true).when(child1).inSplitScreenWindowingMode();
+ doReturn(false).when(mDisplayContent).shouldImeAttachedToApp();
mDisplayContent.setImeLayeringTarget(child1);
spyOn(nextImeTargetApp);
@@ -2418,10 +2418,10 @@
public void testKeepClearAreasMultipleWindows() {
final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1");
final Rect rect1 = new Rect(0, 0, 10, 10);
- w1.setKeepClearAreas(Arrays.asList(rect1));
+ w1.setKeepClearAreas(Arrays.asList(rect1), Collections.emptyList());
final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2");
final Rect rect2 = new Rect(10, 10, 20, 20);
- w2.setKeepClearAreas(Arrays.asList(rect2));
+ w2.setKeepClearAreas(Arrays.asList(rect2), Collections.emptyList());
// No keep clear areas on display, because the windows are not visible
assertEquals(Arrays.asList(), mDisplayContent.getKeepClearAreas());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 3c3351c0..021568d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -451,7 +451,7 @@
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
final int topOrientation = DisplayContentTests.getRotatedOrientation(mDefaultDisplay);
assertFalse(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */));
- assertEquals(recents.mOrientation, displayRotation.getLastOrientation());
+ assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
final int prevRotation = mDisplayContent.getRotation();
mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 501f0c4..8474c38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -691,7 +692,7 @@
anyInt() /* orientation */, anyInt() /* lastRotation */);
// Rotation update is skipped while the recents animation is running.
assertFalse(mDisplayContent.updateRotationUnchecked());
- assertEquals(SCREEN_ORIENTATION_NOSENSOR, displayRotation.getLastOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
// Return to the app without fixed orientation from recents.
app.moveFocusableActivityToTop("test");
player.finish();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
index 338555e..7d2e9bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -30,6 +30,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static org.junit.Assert.assertEquals;
@@ -330,6 +331,23 @@
}
@Test
+ public void layoutExtendedToDisplayCutout() {
+ addDisplayCutout();
+ final int height = DISPLAY_HEIGHT / 2;
+ mRequestedHeight = UNSPECIFIED_LENGTH;
+ mAttrs.height = height;
+ mAttrs.gravity = Gravity.TOP;
+ mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mAttrs.setFitInsetsTypes(0);
+ mAttrs.privateFlags |= PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+ computeFrames();
+
+ assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayFrame);
+ assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mParentFrame);
+ assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mFrame);
+ }
+
+ @Test
public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() {
addDisplayCutout();
mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 4d5fb6d..25d7334 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -610,6 +611,50 @@
assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
}
+ @Test
+ public void testAddRectInsetsProvider() {
+ final Task rootTask = createTask(mDisplayContent);
+
+ final Task navigationBarInsetsReceiverTask = createTaskInRootTask(rootTask, 0);
+ navigationBarInsetsReceiverTask.getConfiguration().windowConfiguration.setBounds(new Rect(
+ 0, 200, 1080, 700));
+
+ final Rect navigationBarInsetsProviderRect = new Rect(0, 0, 1080, 200);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
+ .toWindowContainerToken(), navigationBarInsetsProviderRect,
+ new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders
+ .valueAt(0).getSource().getType()).isEqualTo(ITYPE_LOCAL_NAVIGATION_BAR_1);
+ }
+
+ @Test
+ public void testRemoveInsetsProvider() {
+ final Task rootTask = createTask(mDisplayContent);
+
+ final Task navigationBarInsetsReceiverTask = createTaskInRootTask(rootTask, 0);
+ navigationBarInsetsReceiverTask.getConfiguration().windowConfiguration.setBounds(new Rect(
+ 0, 200, 1080, 700));
+
+ final Rect navigationBarInsetsProviderRect = new Rect(0, 0, 1080, 200);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
+ .toWindowContainerToken(), navigationBarInsetsProviderRect,
+ new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ final WindowContainerTransaction wct2 = new WindowContainerTransaction();
+ wct2.removeInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
+ .toWindowContainerToken(), new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct2);
+
+ assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);
+ }
+
@UseTestDisplay
@Test
public void testTaskInfoCallback() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index ef600f0..a554fab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -97,7 +97,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -992,14 +994,18 @@
final Rect keepClearArea1 = new Rect(0, 0, 10, 10);
final Rect keepClearArea2 = new Rect(5, 10, 15, 20);
final List<Rect> keepClearAreas = Arrays.asList(keepClearArea1, keepClearArea2);
- window.setKeepClearAreas(keepClearAreas);
+ window.setKeepClearAreas(keepClearAreas, Collections.emptyList());
// Test that the keep-clear rects are stored and returned
- assertEquals(new ArraySet(keepClearAreas), new ArraySet(window.getKeepClearAreas()));
+ final List<Rect> windowKeepClearAreas = new ArrayList();
+ window.getKeepClearAreas(windowKeepClearAreas, new ArrayList());
+ assertEquals(new ArraySet(keepClearAreas), new ArraySet(windowKeepClearAreas));
// Test that keep-clear rects are overwritten
- window.setKeepClearAreas(Arrays.asList());
- assertEquals(0, window.getKeepClearAreas().size());
+ window.setKeepClearAreas(Collections.emptyList(), Collections.emptyList());
+ windowKeepClearAreas.clear();
+ window.getKeepClearAreas(windowKeepClearAreas, new ArrayList());
+ assertEquals(0, windowKeepClearAreas.size());
// Move the window position
final SurfaceControl.Transaction t = spy(StubTransaction.class);
@@ -1010,13 +1016,60 @@
assertEquals(new Point(frame.left, frame.top), window.mLastSurfacePosition);
// Test that the returned keep-clear rects are translated to display space
- window.setKeepClearAreas(keepClearAreas);
+ window.setKeepClearAreas(keepClearAreas, Collections.emptyList());
Rect expectedArea1 = new Rect(keepClearArea1);
expectedArea1.offset(frame.left, frame.top);
Rect expectedArea2 = new Rect(keepClearArea2);
expectedArea2.offset(frame.left, frame.top);
+ windowKeepClearAreas.clear();
+ window.getKeepClearAreas(windowKeepClearAreas, new ArrayList());
assertEquals(new ArraySet(Arrays.asList(expectedArea1, expectedArea2)),
- new ArraySet(window.getKeepClearAreas()));
+ new ArraySet(windowKeepClearAreas));
+ }
+
+ @Test
+ public void testUnrestrictedKeepClearAreas() {
+ final WindowState window = createWindow(null, TYPE_APPLICATION, "window");
+ makeWindowVisible(window);
+
+ final Rect keepClearArea1 = new Rect(0, 0, 10, 10);
+ final Rect keepClearArea2 = new Rect(5, 10, 15, 20);
+ final List<Rect> keepClearAreas = Arrays.asList(keepClearArea1, keepClearArea2);
+ window.setKeepClearAreas(Collections.emptyList(), keepClearAreas);
+
+ // Test that the keep-clear rects are stored and returned
+ final List<Rect> restrictedKeepClearAreas = new ArrayList();
+ final List<Rect> unrestrictedKeepClearAreas = new ArrayList();
+ window.getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
+ assertEquals(Collections.emptySet(), new ArraySet(restrictedKeepClearAreas));
+ assertEquals(new ArraySet(keepClearAreas), new ArraySet(unrestrictedKeepClearAreas));
+
+ // Test that keep-clear rects are overwritten
+ window.setKeepClearAreas(Collections.emptyList(), Collections.emptyList());
+ unrestrictedKeepClearAreas.clear();
+ window.getKeepClearAreas(unrestrictedKeepClearAreas, new ArrayList());
+ assertEquals(0, unrestrictedKeepClearAreas.size());
+
+ // Move the window position
+ final SurfaceControl.Transaction t = spy(StubTransaction.class);
+ window.mSurfaceControl = mock(SurfaceControl.class);
+ final Rect frame = window.getFrame();
+ frame.set(10, 20, 60, 80);
+ window.updateSurfacePosition(t);
+ assertEquals(new Point(frame.left, frame.top), window.mLastSurfacePosition);
+
+ // Test that the returned keep-clear rects are translated to display space
+ window.setKeepClearAreas(Collections.emptyList(), keepClearAreas);
+ Rect expectedArea1 = new Rect(keepClearArea1);
+ expectedArea1.offset(frame.left, frame.top);
+ Rect expectedArea2 = new Rect(keepClearArea2);
+ expectedArea2.offset(frame.left, frame.top);
+
+ unrestrictedKeepClearAreas.clear();
+ window.getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
+ assertEquals(Collections.emptySet(), new ArraySet(restrictedKeepClearAreas));
+ assertEquals(new ArraySet(Arrays.asList(expectedArea1, expectedArea2)),
+ new ArraySet(unrestrictedKeepClearAreas));
}
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index f43e5aa..df114db 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1292,31 +1292,22 @@
}
/**
- * Returns a list of {@link PhoneAccountHandle}s for self-managed {@link ConnectionService}s.
+ * Returns a list of {@link PhoneAccountHandle}s for all self-managed
+ * {@link ConnectionService}s owned by the calling {@link UserHandle}.
* <p>
* Self-Managed {@link ConnectionService}s have a {@link PhoneAccount} with
* {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.
* <p>
* Requires permission {@link android.Manifest.permission#READ_PHONE_STATE}, or that the caller
- * is the default dialer app to get all phone account handles.
- * <P>
- * If the caller doesn't meet any of the above requirements and has {@link
- * android.Manifest.permission#MANAGE_OWN_CALLS}, the caller can get only the phone account
- * handles they have registered.
+ * is the default dialer app.
* <p>
- * A {@link SecurityException} will be thrown if the caller is not the default dialer
- * or the caller does not have at least one of the following permissions:
- * {@link android.Manifest.permission#READ_PHONE_STATE} permission,
- * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission
+ * A {@link SecurityException} will be thrown if a called is not the default dialer, or lacks
+ * the {@link android.Manifest.permission#READ_PHONE_STATE} permission.
*
* @return A list of {@code PhoneAccountHandle} objects.
*/
- @RequiresPermission(anyOf = {
- READ_PRIVILEGED_PHONE_STATE,
- android.Manifest.permission.READ_PHONE_STATE,
- android.Manifest.permission.MANAGE_OWN_CALLS
- })
- public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public @NonNull List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
ITelecomService service = getTelecomService();
if (service != null) {
try {
@@ -1330,6 +1321,34 @@
}
/**
+ * Returns a list of {@link PhoneAccountHandle}s owned by the calling self-managed
+ * {@link ConnectionService}.
+ * <p>
+ * Self-Managed {@link ConnectionService}s have a {@link PhoneAccount} with
+ * {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.
+ * <p>
+ * Requires permission {@link android.Manifest.permission#MANAGE_OWN_CALLS}
+ * <p>
+ * A {@link SecurityException} will be thrown if a caller lacks the
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission.
+ *
+ * @return A list of {@code PhoneAccountHandle} objects.
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_OWN_CALLS)
+ public @NonNull List<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts() {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ return service.getOwnSelfManagedPhoneAccounts(mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ throw new IllegalStateException("Telecom is not available");
+ }
+
+ /**
* Returns a list of {@link PhoneAccountHandle}s including those which have not been enabled
* by the user.
*
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index b9936ce..9999c89 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -66,6 +66,12 @@
String callingFeatureId);
/**
+ * @see TelecomServiceImpl#getOwnSelfManagedPhoneAccounts
+ */
+ List<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts(String callingPackage,
+ String callingFeatureId);
+
+ /**
* @see TelecomManager#getPhoneAccountsSupportingScheme
*/
List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(in String uriScheme,
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 8e10f6b..fc94ebf 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -369,6 +369,10 @@
public @interface AuthType {}
// Possible values for protocol which is defined in TS 27.007 section 10.1.1.
+ /** Unknown protocol.
+ * @hide
+ */
+ public static final int PROTOCOL_UNKNOWN = -1;
/** Internet protocol. */
public static final int PROTOCOL_IP = 0;
/** Internet protocol, version 6. */
diff --git a/test-runner/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java
index dd4a9a3..d5f92a3 100644
--- a/test-runner/src/android/test/IsolatedContext.java
+++ b/test-runner/src/android/test/IsolatedContext.java
@@ -17,6 +17,7 @@
package android.test;
import android.accounts.AccountManager;
+import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,6 +27,7 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.Uri;
+import android.os.Process;
import android.test.mock.MockAccountManager;
import java.io.File;
@@ -64,6 +66,15 @@
}
@Override
+ public AttributionSource getAttributionSource() {
+ AttributionSource attributionSource = super.getAttributionSource();
+ if (attributionSource == null) {
+ return new AttributionSource.Builder(Process.myUid()).build();
+ }
+ return attributionSource;
+ }
+
+ @Override
public ContentResolver getContentResolver() {
// We need to return the real resolver so that MailEngine.makeRight can get to the
// subscribed feeds provider. TODO: mock out subscribed feeds too.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index c89e6a4..48b8779 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -97,6 +97,7 @@
transitions {
taplInstrumentation.launchedAppState.quickSwitchToPreviousApp()
wmHelper.waitForFullScreenApp(testApp1.component)
+ wmHelper.waitSnapshotGone()
wmHelper.waitForAppTransitionIdle()
wmHelper.waitForNavBarStatusBarVisible()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 5d172e2..d6c8f46 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -51,7 +51,7 @@
/**
* Test quick switching back to previous app from last opened app
*
- * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
*
* Actions:
* Launch an app [testApp1]
@@ -101,6 +101,7 @@
taplInstrumentation.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
wmHelper.waitForFullScreenApp(testApp2.component)
+ wmHelper.waitSnapshotGone()
wmHelper.waitForAppTransitionIdle()
wmHelper.waitForNavBarStatusBarVisible()
}
diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp
new file mode 100644
index 0000000..c9c6c5c
--- /dev/null
+++ b/tests/TrustTests/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "TrustTests",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "androidx.test.uiautomator",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml
new file mode 100644
index 0000000..c94152d
--- /dev/null
+++ b/tests/TrustTests/AndroidManifest.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.trust.test"
+ android:targetSandboxVersion="2">
+
+ <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+ <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
+ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
+ <uses-permission android:name="android.permission.DEVICE_POWER" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
+ <uses-permission android:name="android.permission.TRUST_LISTENER" />
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.trust.TrustTestActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <service
+ android:name=".UserUnlockRequestTrustAgent"
+ android:exported="true"
+ android:label="Test Agent"
+ android:permission="android.permission.BIND_TRUST_AGENT">
+ <intent-filter>
+ <action android:name="android.service.trust.TrustAgentService" />
+ </intent-filter>
+ </service>
+
+ <service
+ android:name=".LockUserTrustAgent"
+ android:exported="true"
+ android:label="Test Agent"
+ android:permission="android.permission.BIND_TRUST_AGENT">
+ <intent-filter>
+ <action android:name="android.service.trust.TrustAgentService" />
+ </intent-filter>
+ </service>
+
+ <service
+ android:name=".GrantAndRevokeTrustAgent"
+ android:exported="true"
+ android:label="Test Agent"
+ android:permission="android.permission.BIND_TRUST_AGENT">
+ <intent-filter>
+ <action android:name="android.service.trust.TrustAgentService" />
+ </intent-filter>
+ </service>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.trust.test">
+ </instrumentation>
+</manifest>
diff --git a/tests/TrustTests/AndroidTest.xml b/tests/TrustTests/AndroidTest.xml
new file mode 100644
index 0000000..61b711e
--- /dev/null
+++ b/tests/TrustTests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<configuration description="TrustTests configuration">
+ <option name="test-tag" value="TrustTests" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="TrustTests.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.trust.test" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/tests/TrustTests/OWNERS b/tests/TrustTests/OWNERS
new file mode 100644
index 0000000..e2c6ce1
--- /dev/null
+++ b/tests/TrustTests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/trust/OWNERS
diff --git a/tests/TrustTests/README.md b/tests/TrustTests/README.md
new file mode 100644
index 0000000..3427e30
--- /dev/null
+++ b/tests/TrustTests/README.md
@@ -0,0 +1,40 @@
+# TrustTests framework tests
+
+These tests test the "trust" part of the platform primarily implemented via TrustManagerService in
+the system server and TrustAgentService in system apps.
+
+Tests are separated into separate files based on major groupings. When creating new tests, find a
+_closely_ matching existing test file or create a new test file. Prefer many test files over large
+test files.
+
+Each test file has its own trust agent. To create a new trust agent:
+
+1. Create a new class extending from `BaseTrustAgentService` class in your test file
+2. Add a new `<service>` stanza to `AndroidManifest.xml` in this directory for the new agent
+ following the pattern fo the existing agents.
+
+To run:
+
+```atest TrustTests```
+
+## Testing approach:
+
+1. Test the agent service as a black box; avoid inspecting internal state of the service or
+ modifying the system code outside of this directory.
+2. The primary interface to the system is through these three points:
+ 1. `TrustAgentService`, your agent created by the `TrustAgentRule` and accessible via
+ the `agent` property of the rule.
+ 1. Call command methods (e.g. `grantTrust`) directly on the agent
+ 2. Listen to events (e.g. `onUserRequestedUnlock`) by implementing the method in
+ your test's agent class and tracking invocations. See `UserUnlockRequestTest` for an
+ example.
+ 2. `TrustManager` which is the interface the rest of the system (e.g. SystemUI) has to the
+ service.
+ 1. Through this API, simulate system events that the service cares about
+ (e.g. `reportUnlockAttempt`).
+ 3. `TrustListener` which is the interface the rest of the system (e.g. SystemUI) uses to receive
+ events from the service.
+ 1. Through this, verify behavior that affects the rest of the system. For example,
+ see `LockStateTrackingRule`.
+3. To re-use code between tests, prefer creating new rules alongside the existing rules or adding
+ functionality to a _closely_ matching existing rule.
diff --git a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt
new file mode 100644
index 0000000..493f3bd
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.trust
+
+import android.service.trust.TrustAgentService
+import android.util.Log
+import kotlin.reflect.KClass
+
+/**
+ * Base class for test trust agents.
+ */
+abstract class BaseTrustAgentService : TrustAgentService() {
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.d(TAG, "${this::class.simpleName} created")
+ instances[this::class] = this
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ instances.remove(this::class)
+ }
+
+ companion object {
+ private val instances =
+ mutableMapOf<KClass<out BaseTrustAgentService>, BaseTrustAgentService>()
+ private const val TAG = "BaseTrustAgentService"
+
+ fun instance(serviceClass: KClass<out BaseTrustAgentService>): BaseTrustAgentService? {
+ return instances[serviceClass]!!
+ }
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/TrustTestActivity.kt b/tests/TrustTests/src/android/trust/TrustTestActivity.kt
new file mode 100644
index 0000000..6c56fea
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/TrustTestActivity.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.trust
+
+import android.app.Activity
+import android.os.Bundle
+
+/**
+ * Activity for testing Trust.
+ */
+class TrustTestActivity : Activity() {
+
+ public override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ setTurnScreenOn(true)
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
new file mode 100644
index 0000000..790afd3
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test
+
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.LockStateTrackingRule
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TrustAgentRule
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.uiautomator.UiDevice
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for testing revokeTrust & grantTrust for non-renewable trust.
+ *
+ * atest TrustTests:GrantAndRevokeTrustTest
+ */
+@RunWith(AndroidJUnit4::class)
+class GrantAndRevokeTrustTest {
+ private val uiDevice = UiDevice.getInstance(getInstrumentation())
+ private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+ private val lockStateTrackingRule = LockStateTrackingRule()
+ private val trustAgentRule = TrustAgentRule<GrantAndRevokeTrustAgent>()
+
+ @get:Rule
+ val rule: RuleChain = RuleChain
+ .outerRule(activityScenarioRule)
+ .around(ScreenLockRule())
+ .around(lockStateTrackingRule)
+ .around(trustAgentRule)
+
+ @Before
+ fun manageTrust() {
+ trustAgentRule.agent.setManagingTrust(true)
+ }
+
+ // This test serves a baseline for Grant tests, verifying that the default behavior of the
+ // device is to lock when put to sleep
+ @Test
+ fun sleepingDeviceWithoutGrantLocksDevice() {
+ uiDevice.sleep()
+ await()
+
+ lockStateTrackingRule.assertLocked()
+ }
+
+ @Test
+ fun grantKeepsDeviceUnlocked() {
+ trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0)
+ uiDevice.sleep()
+ await()
+
+ lockStateTrackingRule.assertUnlocked()
+ }
+
+ @Test
+ fun grantKeepsDeviceUnlocked_untilRevoked() {
+ trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0)
+ await()
+ uiDevice.sleep()
+ trustAgentRule.agent.revokeTrust()
+ await()
+
+ lockStateTrackingRule.assertLocked()
+ }
+
+ companion object {
+ private const val TAG = "GrantAndRevokeTrustTest"
+ private const val GRANT_MESSAGE = "granted by test"
+ private fun await() = Thread.sleep(250)
+ }
+}
+
+class GrantAndRevokeTrustAgent : BaseTrustAgentService()
diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
new file mode 100644
index 0000000..83fc28f
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test
+
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.LockStateTrackingRule
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TrustAgentRule
+import android.util.Log
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for testing lockUser.
+ *
+ * atest TrustTests:LockUserTest
+ */
+@RunWith(AndroidJUnit4::class)
+class LockUserTest {
+ private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+ private val lockStateTrackingRule = LockStateTrackingRule()
+ private val trustAgentRule = TrustAgentRule<LockUserTrustAgent>()
+
+ @get:Rule
+ val rule: RuleChain = RuleChain
+ .outerRule(activityScenarioRule)
+ .around(ScreenLockRule())
+ .around(lockStateTrackingRule)
+ .around(trustAgentRule)
+
+ @Ignore("Causes issues with subsequent tests") // TODO: Enable test
+ @Test
+ fun lockUser_locksTheDevice() {
+ Log.i(TAG, "Locking user")
+ trustAgentRule.agent.lockUser()
+ await()
+
+ assertThat(lockStateTrackingRule.lockState.locked).isTrue()
+ }
+
+ companion object {
+ private const val TAG = "LockUserTest"
+ private fun await() = Thread.sleep(250)
+ }
+}
+
+class LockUserTrustAgent : BaseTrustAgentService()
diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt
new file mode 100644
index 0000000..f8783fb
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.trust.test
+
+import android.app.trust.TrustManager
+import android.content.Context
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TrustAgentRule
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for testing the user unlock trigger.
+ *
+ * atest TrustTests:UserUnlockRequestTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UserUnlockRequestTest {
+ private val context: Context = getApplicationContext()
+ private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+ private val userId = context.userId
+ private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+ private val trustAgentRule = TrustAgentRule<UserUnlockRequestTrustAgent>()
+
+ @get:Rule
+ val rule: RuleChain = RuleChain
+ .outerRule(activityScenarioRule)
+ .around(ScreenLockRule())
+ .around(trustAgentRule)
+
+ @Test
+ fun reportUserRequestedUnlock_propagatesToAgent() {
+ val oldCount = trustAgentRule.agent.onUserRequestedUnlockCallCount
+ trustManager.reportUserRequestedUnlock(userId)
+ await()
+
+ assertThat(trustAgentRule.agent.onUserRequestedUnlockCallCount)
+ .isEqualTo(oldCount + 1)
+ }
+
+ companion object {
+ private const val TAG = "UserUnlockRequestTest"
+ private fun await() = Thread.sleep(250)
+ }
+}
+
+class UserUnlockRequestTrustAgent : BaseTrustAgentService() {
+ var onUserRequestedUnlockCallCount: Long = 0
+ private set
+
+ override fun onUserRequestedUnlock() {
+ Log.i(TAG, "onUserRequestedUnlock")
+ onUserRequestedUnlockCallCount++
+ }
+
+ companion object {
+ private const val TAG = "UserUnlockRequestTrustAgent"
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
new file mode 100644
index 0000000..0023af8
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test.lib
+
+import android.app.trust.TrustManager
+import android.app.trust.TrustManager.TrustListener
+import android.content.Context
+import android.util.Log
+import android.view.WindowManagerGlobal
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.google.common.truth.Truth.assertThat
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule for tracking the lock state of the device based on events emitted to [TrustListener].
+ */
+class LockStateTrackingRule : TestRule {
+ private val context: Context = getApplicationContext()
+ private val windowManager = WindowManagerGlobal.getWindowManagerService()
+
+ @Volatile lateinit var lockState: LockState
+ private set
+
+ override fun apply(base: Statement, description: Description) = object : Statement() {
+ override fun evaluate() {
+ lockState = LockState(locked = windowManager.isKeyguardLocked)
+ val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+ val listener = Listener()
+
+ trustManager.registerTrustListener(listener)
+ try {
+ base.evaluate()
+ } finally {
+ trustManager.unregisterTrustListener(listener)
+ }
+ }
+ }
+
+ fun assertLocked() = assertThat(lockState.locked).isTrue()
+ fun assertUnlocked() = assertThat(lockState.locked).isFalse()
+
+ inner class Listener : TrustListener {
+ override fun onTrustChanged(
+ enabled: Boolean,
+ userId: Int,
+ flags: Int,
+ trustGrantedMessages: MutableList<String>
+ ) {
+ Log.d(TAG, "Device became trusted=$enabled")
+ lockState = lockState.copy(locked = !enabled)
+ }
+
+ override fun onTrustManagedChanged(enabled: Boolean, userId: Int) {
+ }
+
+ override fun onTrustError(message: CharSequence) {
+ }
+ }
+
+ data class LockState(
+ val locked: Boolean? = null
+ )
+
+ companion object {
+ private const val TAG = "LockStateTrackingRule"
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
new file mode 100644
index 0000000..c682a00
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test.lib
+
+import android.content.Context
+import android.util.Log
+import android.view.WindowManagerGlobal
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Sets a screen lock on the device for the duration of the test.
+ */
+class ScreenLockRule : TestRule {
+ private val context: Context = getApplicationContext()
+ private val windowManager = WindowManagerGlobal.getWindowManagerService()
+ private val lockPatternUtils = LockPatternUtils(context)
+ private var instantLockSavedValue = false
+
+ override fun apply(base: Statement, description: Description) = object : Statement() {
+ override fun evaluate() {
+ verifyNoScreenLockAlreadySet()
+ verifyKeyguardDismissed()
+ setScreenLock()
+ setLockOnPowerButton()
+
+ try {
+ base.evaluate()
+ } finally {
+ removeScreenLock()
+ revertLockOnPowerButton()
+ }
+ }
+ }
+
+ private fun verifyNoScreenLockAlreadySet() {
+ assertWithMessage("Screen Lock must not already be set on device")
+ .that(lockPatternUtils.isSecure(context.userId))
+ .isFalse()
+ }
+
+ private fun verifyKeyguardDismissed() {
+ windowManager.dismissKeyguard(null, null)
+ Thread.sleep(250)
+ assertWithMessage("Keyguard should be unlocked")
+ .that(windowManager.isKeyguardLocked)
+ .isFalse()
+ }
+
+ private fun setScreenLock() {
+ lockPatternUtils.setLockCredential(
+ LockscreenCredential.createPin(PIN),
+ LockscreenCredential.createNone(),
+ context.userId
+ )
+ assertWithMessage("Screen Lock should now be set")
+ .that(lockPatternUtils.isSecure(context.userId))
+ .isTrue()
+ Log.i(TAG, "Device PIN set to $PIN")
+ }
+
+ private fun setLockOnPowerButton() {
+ instantLockSavedValue = lockPatternUtils.getPowerButtonInstantlyLocks(context.userId)
+ lockPatternUtils.setPowerButtonInstantlyLocks(true, context.userId)
+ }
+
+ private fun removeScreenLock() {
+ lockPatternUtils.setLockCredential(
+ LockscreenCredential.createNone(),
+ LockscreenCredential.createPin(PIN),
+ context.userId
+ )
+ Log.i(TAG, "Device PIN cleared; waiting 50 ms then dismissing Keyguard")
+ Thread.sleep(50)
+ windowManager.dismissKeyguard(null, null)
+ }
+
+ private fun revertLockOnPowerButton() {
+ lockPatternUtils.setPowerButtonInstantlyLocks(instantLockSavedValue, context.userId)
+ }
+
+ companion object {
+ private const val TAG = "ScreenLockRule"
+ private const val PIN = "0000"
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
new file mode 100644
index 0000000..2a9e002
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.trust.test.lib
+
+import android.app.trust.TrustManager
+import android.content.ComponentName
+import android.content.Context
+import android.trust.BaseTrustAgentService
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.internal.widget.LockPatternUtils
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import kotlin.reflect.KClass
+
+/**
+ * Enables a trust agent and causes the system service to bind to it.
+ *
+ * The enabled agent can be accessed during the test via the [agent] property.
+ *
+ * @constructor Creates the rule. Do not use; instead, use [invoke].
+ */
+class TrustAgentRule<T : BaseTrustAgentService>(
+ private val serviceClass: KClass<T>
+) : TestRule {
+ private val context: Context = getApplicationContext()
+ private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+ private val lockPatternUtils = LockPatternUtils(context)
+
+ val agent get() = BaseTrustAgentService.instance(serviceClass) as T
+
+ override fun apply(base: Statement, description: Description) = object : Statement() {
+ override fun evaluate() {
+ verifyTrustServiceRunning()
+ unlockDeviceWithCredential()
+ enableTrustAgent()
+ waitForEnablement()
+
+ try {
+ verifyAgentIsRunning()
+ base.evaluate()
+ } finally {
+ disableTrustAgent()
+ }
+ }
+ }
+
+ private fun verifyTrustServiceRunning() {
+ assertWithMessage("Trust service is not running").that(trustManager).isNotNull()
+ }
+
+ private fun unlockDeviceWithCredential() {
+ Log.d(TAG, "Unlocking device with credential")
+ trustManager.reportUnlockAttempt(true, context.userId)
+ }
+
+ private fun enableTrustAgent() {
+ val componentName = ComponentName(context, serviceClass.java)
+ val userId = context.userId
+ Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId")
+ val agents = mutableListOf(componentName)
+ .plus(lockPatternUtils.getEnabledTrustAgents(userId))
+ .distinct()
+ lockPatternUtils.setEnabledTrustAgents(agents, userId)
+ }
+
+ private fun waitForEnablement() {
+ Log.d(TAG, "Waiting for $WAIT_TIME ms")
+ Thread.sleep(WAIT_TIME)
+ Log.d(TAG, "Done waiting")
+ }
+
+ private fun verifyAgentIsRunning() {
+ assertWithMessage("${serviceClass.simpleName} should be running")
+ .that(BaseTrustAgentService.instance(serviceClass)).isNotNull()
+ }
+
+ private fun disableTrustAgent() {
+ val componentName = ComponentName(context, serviceClass.java)
+ val userId = context.userId
+ Log.i(TAG, "Disabling trust agent ${componentName.flattenToString()} for user $userId")
+ val agents = lockPatternUtils.getEnabledTrustAgents(userId).toMutableList()
+ .distinct()
+ .minus(componentName)
+ lockPatternUtils.setEnabledTrustAgents(agents, userId)
+ }
+
+ companion object {
+ /**
+ * Creates a new rule for the specified agent class. Example usage:
+ * ```
+ * @get:Rule val rule = TrustAgentRule<MyTestAgent>()
+ * ```
+ */
+ inline operator fun <reified T : BaseTrustAgentService> invoke() =
+ TrustAgentRule(T::class)
+
+ private const val TAG = "TrustAgentRule"
+ private val WAIT_TIME = 1000L
+ }
+}