diff options
151 files changed, 3035 insertions, 1228 deletions
diff --git a/Android.bp b/Android.bp index 82b844b3d0a4..f3b2ebb4fc17 100644 --- a/Android.bp +++ b/Android.bp @@ -665,6 +665,7 @@ java_library { lint: { baseline_filename: "lint-baseline.xml", }, + apex_available: ["com.android.wifi"], } filegroup { diff --git a/WEAR_OWNERS b/WEAR_OWNERS index 4f3bc27c380f..4127f996da03 100644 --- a/WEAR_OWNERS +++ b/WEAR_OWNERS @@ -4,3 +4,9 @@ yeabkal@google.com adsule@google.com andriyn@google.com yfz@google.com +con@google.com +leetodd@google.com +sadrul@google.com +rwmyers@google.com +nalmalki@google.com +shijianli@google.com diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java index b7460cd6ead4..fa4387b405c9 100644 --- a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java +++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java @@ -433,6 +433,17 @@ public class SQLiteDatabasePerfTest { performMultithreadedReadWriteTest(); } + /** + * This test measures a multi-threaded read-write environment where there are 2 readers and + * 1 writer in the database using WAL journal mode and NORMAL syncMode. + */ + @Test + public void testMultithreadedReadWriteWithWalNormal() { + recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL); + insertT1TestDataSet(); + performMultithreadedReadWriteTest(); + } + private void doReadLoop(int totalIterations) { Random rnd = new Random(0); int currentIteration = 0; @@ -472,7 +483,6 @@ public class SQLiteDatabasePerfTest { } private void doUpdateLoop(int totalIterations) { - SQLiteDatabase db = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null); Random rnd = new Random(0); int i = 0; ContentValues cv = new ContentValues(); @@ -485,24 +495,12 @@ public class SQLiteDatabasePerfTest { cv.put("COL_B", "UpdatedValue"); cv.put("COL_C", i); argArray[0] = String.valueOf(id); - db.update("T1", cv, "_ID=?", argArray); + mDatabase.update("T1", cv, "_ID=?", argArray); i++; android.os.Trace.endSection(); } } - /** - * This test measures a multi-threaded read-write environment where there are 2 readers and - * 1 writer in the database using WAL journal mode and NORMAL syncMode. - */ - @Test - public void testMultithreadedReadWriteWithWalNormal() { - recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL); - insertT1TestDataSet(); - - performMultithreadedReadWriteTest(); - } - private void performMultithreadedReadWriteTest() { int totalBGIterations = 10000; // Writer - Fixed iterations to avoid consuming cycles from mainloop benchmark iterations @@ -555,4 +553,3 @@ public class SQLiteDatabasePerfTest { createOrOpenTestDatabase(journalMode, syncMode); } } - diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 9077d02be8be..506d2035c984 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5747,7 +5747,7 @@ package android.hardware.radio { method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void close(); - method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); + method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier); method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback); method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback); @@ -5799,7 +5799,7 @@ package android.hardware.radio { field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5 field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5 field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa - field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb + field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf @@ -5807,8 +5807,8 @@ package android.hardware.radio { field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 - field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd - field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc + field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd + field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8 @@ -5861,7 +5861,7 @@ package android.hardware.radio { field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8 field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7 field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9 - field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 + field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index d2af9db3714c..2e22071d72ad 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1882,6 +1882,7 @@ package android.media { method public void setRampingRingerEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setRs2Value(float); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean); + method @FlaggedApi("android.media.audio.focus_exclusive_with_recording") @RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE) public boolean shouldNotificationSoundPlay(@NonNull android.media.AudioAttributes); } public static final class AudioRecord.MetricsConstants { @@ -2343,6 +2344,9 @@ package android.os { field public static final int CPU_LOAD_RESET = 2; // 0x2 field public static final int CPU_LOAD_RESUME = 3; // 0x3 field public static final int CPU_LOAD_UP = 0; // 0x0 + field @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static final int GPU_LOAD_DOWN = 6; // 0x6 + field @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static final int GPU_LOAD_RESET = 7; // 0x7 + field @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static final int GPU_LOAD_UP = 5; // 0x5 } public final class PowerManager { diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index b4a6955325a3..845a346be593 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -1311,8 +1311,9 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (!node.mEnded) { float durationScale = ValueAnimator.getDurationScale(); durationScale = durationScale == 0 ? 1 : durationScale; - node.mEnded = node.mAnimation.pulseAnimationFrame( - (long) (animPlayTime * durationScale)); + if (node.mAnimation.pulseAnimationFrame((long) (animPlayTime * durationScale))) { + node.mEnded = true; + } } } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ee1d117bf71c..d5eee63fee12 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -8099,7 +8099,7 @@ public class Intent implements Parcelable, Cloneable { int end = data.indexOf('/', 14); if (end < 0) { // All we have is a package name. - intent.mPackage = data.substring(14); + intent.mPackage = Uri.decodeIfNeeded(data.substring(14)); if (!explicitAction) { intent.setAction(ACTION_MAIN); } @@ -8107,21 +8107,22 @@ public class Intent implements Parcelable, Cloneable { } else { // Target the Intent at the given package name always. String authority = null; - intent.mPackage = data.substring(14, end); + intent.mPackage = Uri.decodeIfNeeded(data.substring(14, end)); int newEnd; if ((end+1) < data.length()) { if ((newEnd=data.indexOf('/', end+1)) >= 0) { // Found a scheme, remember it. - scheme = data.substring(end+1, newEnd); + scheme = Uri.decodeIfNeeded(data.substring(end + 1, newEnd)); end = newEnd; if (end < data.length() && (newEnd=data.indexOf('/', end+1)) >= 0) { // Found a authority, remember it. - authority = data.substring(end+1, newEnd); + authority = Uri.decodeIfNeeded( + data.substring(end + 1, newEnd)); end = newEnd; } } else { // All we have is a scheme. - scheme = data.substring(end+1); + scheme = Uri.decodeIfNeeded(data.substring(end + 1)); } } if (scheme == null) { @@ -11762,27 +11763,33 @@ public class Intent implements Parcelable, Cloneable { + this); } uri.append("android-app://"); - uri.append(mPackage); + uri.append(Uri.encode(mPackage)); String scheme = null; if (mData != null) { - scheme = mData.getScheme(); + // All values here must be wrapped with Uri#encodeIfNotEncoded because it is + // possible to exploit the Uri API to return a raw unencoded value, which will + // not deserialize properly and may cause the resulting Intent to be transformed + // to a malicious value. + scheme = Uri.encodeIfNotEncoded(mData.getScheme(), null); if (scheme != null) { uri.append('/'); uri.append(scheme); - String authority = mData.getEncodedAuthority(); + String authority = Uri.encodeIfNotEncoded(mData.getEncodedAuthority(), null); if (authority != null) { uri.append('/'); uri.append(authority); - String path = mData.getEncodedPath(); + + // Multiple path segments are allowed, don't encode the path / separator + String path = Uri.encodeIfNotEncoded(mData.getEncodedPath(), "/"); if (path != null) { uri.append(path); } - String queryParams = mData.getEncodedQuery(); + String queryParams = Uri.encodeIfNotEncoded(mData.getEncodedQuery(), null); if (queryParams != null) { uri.append('?'); uri.append(queryParams); } - String fragment = mData.getEncodedFragment(); + String fragment = Uri.encodeIfNotEncoded(mData.getEncodedFragment(), null); if (fragment != null) { uri.append('#'); uri.append(fragment); diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index f0a89960cad1..a8dba5182e90 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -382,10 +382,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * start it unless initiated by a user interaction (typically launching its icon * from the launcher, could also include user actions like adding it as an app widget, * selecting it as a live wallpaper, selecting it as a keyboard, etc). Stopped - * applications will not receive broadcasts unless the sender specifies + * applications will not receive implicit broadcasts unless the sender specifies * {@link android.content.Intent#FLAG_INCLUDE_STOPPED_PACKAGES}. * - * <p>Applications should avoid launching activies, binding to or starting services, or + * <p>Applications should avoid launching activities, binding to or starting services, or * otherwise causing a stopped application to run unless initiated by the user. * * <p>An app can also return to the stopped state by a "force stop". diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 0e131b413d0c..433226413917 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -672,6 +672,13 @@ public class PackageInstaller { public @interface UserActionReason {} /** + * The unarchival status is not set. + * + * @hide + */ + public static final int UNARCHIVAL_STATUS_UNSET = -1; + + /** * The unarchival is possible and will commence. * * <p> Note that this does not mean that the unarchival has completed. This status should be @@ -736,6 +743,7 @@ public class PackageInstaller { * @hide */ @IntDef(value = { + UNARCHIVAL_STATUS_UNSET, UNARCHIVAL_OK, UNARCHIVAL_ERROR_USER_ACTION_NEEDED, UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE, @@ -2696,8 +2704,6 @@ public class PackageInstaller { public int developmentInstallFlags = 0; /** {@hide} */ public int unarchiveId = -1; - /** {@hide} */ - public IntentSender unarchiveIntentSender; private final ArrayMap<String, Integer> mPermissionStates; @@ -2750,7 +2756,6 @@ public class PackageInstaller { applicationEnabledSettingPersistent = source.readBoolean(); developmentInstallFlags = source.readInt(); unarchiveId = source.readInt(); - unarchiveIntentSender = source.readParcelable(null, IntentSender.class); } /** {@hide} */ @@ -2785,7 +2790,6 @@ public class PackageInstaller { ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent; ret.developmentInstallFlags = developmentInstallFlags; ret.unarchiveId = unarchiveId; - ret.unarchiveIntentSender = unarchiveIntentSender; return ret; } @@ -3495,7 +3499,6 @@ public class PackageInstaller { applicationEnabledSettingPersistent); pw.printHexPair("developmentInstallFlags", developmentInstallFlags); pw.printPair("unarchiveId", unarchiveId); - pw.printPair("unarchiveIntentSender", unarchiveIntentSender); pw.println(); } @@ -3540,7 +3543,6 @@ public class PackageInstaller { dest.writeBoolean(applicationEnabledSettingPersistent); dest.writeInt(developmentInstallFlags); dest.writeInt(unarchiveId); - dest.writeParcelable(unarchiveIntentSender, flags); } public static final Parcelable.Creator<SessionParams> diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 0b60977e48c7..a2cd3e153b3e 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -138,3 +138,11 @@ flag { description: "Add a new FGS type for media processing use cases." bug: "317788011" } + +flag { + name: "encode_app_intent" + namespace: "package_manager_service" + description: "Feature flag to encode app intent." + bug: "281848623" +} + diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index b96d83247591..ecffe9e5a8b2 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -121,8 +121,12 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen // The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY) private long mConnectionPtr; + // Restrict this connection to read-only operations. private boolean mOnlyAllowReadOnlyOperations; + // Allow this connection to treat updates to temporary tables as read-only operations. + private boolean mAllowTempTableRetry = Flags.sqliteAllowTempTables(); + // The number of times attachCancellationSignal has been called. // Because SQLite statement execution can be reentrant, we keep track of how many // times we have attempted to attach a cancellation signal to the connection so that @@ -142,6 +146,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr); private static native int nativeGetParameterCount(long connectionPtr, long statementPtr); private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr); + private static native boolean nativeUpdatesTempOnly(long connectionPtr, long statementPtr); private static native int nativeGetColumnCount(long connectionPtr, long statementPtr); private static native String nativeGetColumnName(long connectionPtr, long statementPtr, int index); @@ -1097,7 +1102,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen try { final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr); final int type = DatabaseUtils.getSqlStatementTypeExtended(sql); - final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); + boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly, seqNum); if (!skipCache && isCacheable(type)) { @@ -1265,13 +1270,20 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen /** * Verify that the statement is read-only, if the connection only allows read-only - * operations. + * operations. If the connection allows updates to temporary tables, then the statement is + * read-only if the only updates are to temporary tables. * @param statement The statement to check. * @throws SQLiteException if the statement could update the database inside a read-only * transaction. */ void throwIfStatementForbidden(PreparedStatement statement) { if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) { + if (mAllowTempTableRetry) { + statement.mReadOnly = + nativeUpdatesTempOnly(mConnectionPtr, statement.mStatementPtr); + if (statement.mReadOnly) return; + } + throw new SQLiteException("Cannot execute this statement because it " + "might modify the database but the connection is read-only."); } diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig index 62a51236a2e2..92ef9c24c4ef 100644 --- a/core/java/android/database/sqlite/flags.aconfig +++ b/core/java/android/database/sqlite/flags.aconfig @@ -7,3 +7,11 @@ flag { description: "SQLite APIs held back for Android 15" bug: "279043253" } + +flag { + name: "sqlite_allow_temp_tables" + namespace: "system_performance" + is_fixed_read_only: true + description: "Permit updates to TEMP tables in read-only transactions" + bug: "317993835" +} diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java index c5167dbc7d4c..a3a2a2e6fd16 100644 --- a/core/java/android/hardware/radio/ProgramList.java +++ b/core/java/android/hardware/radio/ProgramList.java @@ -304,11 +304,7 @@ public final class ProgramList implements AutoCloseable { * * @param id primary identifier of a program to fetch * @return the program info, or null if there is no such program on the list - * - * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs - * with the given primary identifier */ - @Deprecated public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) { Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries; synchronized (mLock) { diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 7e5c141a399a..4c95e026a180 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -312,20 +312,14 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; /** * 1: AM, 2:FM - * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead */ - @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; /** * 32bit primary identifier for SiriusXM Satellite Radio. - * - * @deprecated SiriusXM Satellite Radio is not supported */ public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; /** * 0-999 range - * - * @deprecated SiriusXM Satellite Radio is not supported */ public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; /** diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index f0f7e8a22e2a..41f21efd60dd 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -166,12 +166,7 @@ public class RadioManager { * analog handover state managed from the HAL implementation side. * * <p>Some radio technologies may not support this, i.e. DAB. - * - * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM} - * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet} - * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}. */ - @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; /** * Forces the digital playback for the supporting radio technology. diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 70de477e9e2e..05a3e182135c 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; +import android.content.pm.Flags; import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; @@ -1971,6 +1972,42 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } /** + * Encodes a value it wasn't already encoded. + * + * @param value string to encode + * @param allow characters to allow + * @return encoded value + * @hide + */ + public static String encodeIfNotEncoded(@Nullable String value, @Nullable String allow) { + if (value == null) return null; + if (!Flags.encodeAppIntent() || isEncoded(value, allow)) return value; + return encode(value, allow); + } + + /** + * Returns true if the given string is already encoded to safe characters. + * + * @param value string to check + * @param allow characters to allow + * @return true if the string is already encoded or false if it should be encoded + */ + private static boolean isEncoded(@Nullable String value, @Nullable String allow) { + if (value == null) return true; + for (int index = 0; index < value.length(); index++) { + char c = value.charAt(index); + + // Allow % because that's the prefix for an encoded character. This method will fail + // for decoded strings whose onlyinvalid character is %, but it's assumed that % alone + // cannot cause malicious behavior in the framework. + if (!isAllowed(c, allow) && c != '%') { + return false; + } + } + return true; + } + + /** * Decodes '%'-escaped octets in the given string using the UTF-8 scheme. * Replaces invalid octets with the unicode replacement character * ("\\uFFFD"). @@ -1988,6 +2025,18 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } /** + * Decodes a string if it was encoded, indicated by containing a %. + * @param value encoded string to decode + * @return decoded value + * @hide + */ + public static String decodeIfNeeded(@Nullable String value) { + if (value == null) return null; + if (Flags.encodeAppIntent() && value.contains("%")) return decode(value); + return value; + } + + /** * Support for part implementations. */ static abstract class AbstractPart { diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 5a40e424ea91..8219d2f873de 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -1802,7 +1802,7 @@ public final class NfcAdapter { * Use {@link #FLAG_LISTEN_DISABLE} to disable listening. * Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes. * </p> - * The pollTech, listenTech parameters can be one or several of below list. + * The pollTechnology, listenTechnology parameters can be one or several of below list. * <pre> * Poll Listen * Passive A 0x01 (NFC_A) 0x01 (NFC_PASSIVE_A) @@ -1820,25 +1820,25 @@ public final class NfcAdapter { * NfcAdapter.FLAG_READER_DISABLE, NfcAdapter.FLAG_LISTEN_KEEP); * }</pre></p> * @param activity The Activity that requests NFC controller to enable specific technologies. - * @param pollTech Flags indicating poll technologies. - * @param listenTech Flags indicating listen technologies. + * @param pollTechnology Flags indicating poll technologies. + * @param listenTechnology Flags indicating listen technologies. * @throws UnsupportedOperationException if FEATURE_NFC, * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable. */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH) public void setDiscoveryTechnology(@NonNull Activity activity, - @PollTechnology int pollTech, @ListenTechnology int listenTech) { - if (listenTech == FLAG_LISTEN_DISABLE) { + @PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) { + if (listenTechnology == FLAG_LISTEN_DISABLE) { synchronized (sLock) { if (!sHasNfcFeature) { throw new UnsupportedOperationException(); } } - mNfcActivityManager.enableReaderMode(activity, null, pollTech, null); + mNfcActivityManager.enableReaderMode(activity, null, pollTechnology, null); return; } - if (pollTech == FLAG_READER_DISABLE) { + if (pollTechnology == FLAG_READER_DISABLE) { synchronized (sLock) { if (!sHasCeFeature) { throw new UnsupportedOperationException(); @@ -1851,7 +1851,7 @@ public final class NfcAdapter { } } } - mNfcActivityManager.setDiscoveryTech(activity, pollTech, listenTech); + mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology); } /** diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index e0059105c21f..37bde3db5e14 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -149,13 +149,47 @@ public final class PerformanceHintManager { @TestApi public static final int CPU_LOAD_RESUME = 3; + /** + * This hint indicates an increase in GPU workload intensity. It means that + * this hint session needs extra GPU resources to meet the target duration. + * This hint must be sent before reporting the actual duration to the session. + * + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) + public static final int GPU_LOAD_UP = 5; + + /** + * This hint indicates a decrease in GPU workload intensity. It means that + * this hint session can reduce GPU resources and still meet the target duration. + * + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) + public static final int GPU_LOAD_DOWN = 6; + + /** + * This hint indicates an upcoming GPU workload that is completely changed and + * unknown. It means that the hint session should reset GPU resources to a known + * baseline to prepare for an arbitrary load, and must wake up if inactive. + * + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) + public static final int GPU_LOAD_RESET = 7; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"CPU_LOAD_"}, value = { CPU_LOAD_UP, CPU_LOAD_DOWN, CPU_LOAD_RESET, - CPU_LOAD_RESUME + CPU_LOAD_RESUME, + GPU_LOAD_UP, + GPU_LOAD_DOWN }) public @interface Hint {} diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index c14810bbcd64..f3496e7f2592 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -425,6 +425,8 @@ public class ZygoteProcess { throw new ZygoteStartFailedEx("Embedded newlines not allowed"); } else if (arg.indexOf('\r') >= 0) { throw new ZygoteStartFailedEx("Embedded carriage returns not allowed"); + } else if (arg.indexOf('\u0000') >= 0) { + throw new ZygoteStartFailedEx("Embedded nulls not allowed"); } } @@ -965,6 +967,14 @@ public class ZygoteProcess { return true; } + for (/* NonNull */ String s : mApiDenylistExemptions) { + // indexOf() is intrinsified and faster than contains(). + if (s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0 || s.indexOf('\u0000') >= 0) { + Slog.e(LOG_TAG, "Failed to set API denylist exemptions: Bad character"); + mApiDenylistExemptions = Collections.emptyList(); + return false; + } + } try { state.mZygoteOutputWriter.write(Integer.toString(mApiDenylistExemptions.size() + 1)); state.mZygoteOutputWriter.newLine(); diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 3ecf74e75367..54ed73c34830 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -134,16 +134,16 @@ interface IStorageManager { @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS") void setDebugFlags(int flags, int mask) = 60; @EnforcePermission("STORAGE_INTERNAL") - void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) = 61; + void createUserStorageKeys(int userId, boolean ephemeral) = 61; @EnforcePermission("STORAGE_INTERNAL") void destroyUserStorageKeys(int userId) = 62; @EnforcePermission("STORAGE_INTERNAL") - void unlockCeStorage(int userId, int serialNumber, in byte[] secret) = 63; + void unlockCeStorage(int userId, in byte[] secret) = 63; @EnforcePermission("STORAGE_INTERNAL") void lockCeStorage(int userId) = 64; boolean isCeStorageUnlocked(int userId) = 65; @EnforcePermission("STORAGE_INTERNAL") - void prepareUserStorage(in String volumeUuid, int userId, int serialNumber, int flags) = 66; + void prepareUserStorage(in String volumeUuid, int userId, int flags) = 66; @EnforcePermission("STORAGE_INTERNAL") void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67; @EnforcePermission("STORAGE_INTERNAL") diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 78a12f75a508..9587db13ea87 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1602,14 +1602,13 @@ public class StorageManager { * This is only intended to be called by UserManagerService, as part of creating a user. * * @param userId ID of the user - * @param serialNumber serial number of the user * @param ephemeral whether the user is ephemeral * @throws RuntimeException on error. The user's keys already existing is considered an error. * @hide */ - public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) { + public void createUserStorageKeys(int userId, boolean ephemeral) { try { - mStorageManager.createUserStorageKeys(userId, serialNumber, ephemeral); + mStorageManager.createUserStorageKeys(userId, ephemeral); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1653,9 +1652,9 @@ public class StorageManager { } /** {@hide} */ - public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) { + public void prepareUserStorage(String volumeUuid, int userId, int flags) { try { - mStorageManager.prepareUserStorage(volumeUuid, userId, serialNumber, flags); + mStorageManager.prepareUserStorage(volumeUuid, userId, flags); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 144e64f9c27b..e8da0d9341d3 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3603,12 +3603,17 @@ public final class Settings { + " type:" + mUri.getPath() + " in package:" + cr.getPackageName()); } + // When a generation number changes, remove cached values, remove the old + // generation tracker and request a new one + generationTracker.destroy(); + mGenerationTrackers.remove(prefix); for (int i = mValues.size() - 1; i >= 0; i--) { String key = mValues.keyAt(i); if (key.startsWith(prefix)) { mValues.remove(key); } } + needsGenerationTracker = true; } else { boolean prefixCached = mValues.containsKey(prefix); if (prefixCached) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index b4ac9a22aad3..d8fa41589f29 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1260,7 +1260,7 @@ public interface WindowManager extends ViewManager { * <p>When this compat override is enabled the min aspect ratio given in the app's manifest can * be overridden by the device manufacturer using their discretion to improve display * compatibility unless the app's manifest value is higher. This treatment will also apply if - * no min aspect ratio value is provided in the manifest. These treatments can apply only in + * no min aspect ratio value is provided in the manifest. These treatments can apply either in * specific cases (e.g. device is in portrait) or each time the app is displayed on screen. * * <p>Setting this property to {@code false} informs the system that the app must be diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index bb49679453ac..dbeffc89fa09 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1469,7 +1469,8 @@ public final class AutofillManager { if (infos.size() == 0) { throw new IllegalArgumentException("No VirtualViewInfo found"); } - if (isCredmanRequested(view) && mIsFillAndSaveDialogDisabledForCredentialManager) { + if (shouldSuppressDialogsForCredman(view) + && mIsFillAndSaveDialogDisabledForCredentialManager) { if (sDebug) { Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:" + view.getAutofillId().toString()); @@ -1493,7 +1494,7 @@ public final class AutofillManager { * @hide */ public void notifyViewEnteredForFillDialog(View v) { - if (isCredmanRequested(v) + if (shouldSuppressDialogsForCredman(v) && mIsFillAndSaveDialogDisabledForCredentialManager) { if (sDebug) { Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:" @@ -3390,19 +3391,39 @@ public final class AutofillManager { } } - private boolean isCredmanRequested(View view) { + private boolean shouldSuppressDialogsForCredman(View view) { if (view == null) { return false; } + // isCredential field indicates that the developer might be calling Credman, and we should + // suppress autofill dialogs. But it is not a good enough indicator that there is a valid + // credman option. if (view.isCredential()) { return true; } + return containsAutofillHintPrefix(view, View.AUTOFILL_HINT_CREDENTIAL_MANAGER); + } + + private boolean isCredmanRequested(View view) { + if (view == null) { + return false; + } + String[] hints = view.getAutofillHints(); + if (hints == null) { + return false; + } + // if hint starts with 'credential=', then we assume that there is a valid + // credential option set by the client. + return containsAutofillHintPrefix(view, View.AUTOFILL_HINT_CREDENTIAL_MANAGER + "="); + } + + private boolean containsAutofillHintPrefix(View view, String prefix) { String[] hints = view.getAutofillHints(); if (hints == null) { return false; } for (String hint : hints) { - if (hint != null && hint.startsWith(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) { + if (hint != null && hint.startsWith(prefix)) { return true; } } diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java index e62d5c95a1f8..64fe66e36bc3 100644 --- a/core/java/android/window/TransitionFilter.java +++ b/core/java/android/window/TransitionFilter.java @@ -212,7 +212,9 @@ public final class TransitionFilter implements Parcelable { continue; } } - if (!matchesTopActivity(change.getTaskInfo())) continue; + if (!matchesTopActivity(change.getTaskInfo(), change.getActivityComponent())) { + continue; + } if (mModes != null) { boolean pass = false; for (int m = 0; m < mModes.length; ++m) { @@ -234,11 +236,15 @@ public final class TransitionFilter implements Parcelable { return false; } - private boolean matchesTopActivity(ActivityManager.RunningTaskInfo info) { + private boolean matchesTopActivity(ActivityManager.RunningTaskInfo taskInfo, + @Nullable ComponentName activityComponent) { if (mTopActivity == null) return true; - if (info == null) return false; - final ComponentName component = info.topActivity; - return mTopActivity.equals(component); + if (activityComponent != null) { + return mTopActivity.equals(activityComponent); + } else if (taskInfo != null) { + return mTopActivity.equals(taskInfo.topActivity); + } + return false; } /** Check if the request matches this filter. It may generate false positives */ @@ -247,7 +253,7 @@ public final class TransitionFilter implements Parcelable { if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true; return request.getTriggerTask() != null && request.getTriggerTask().getActivityType() == mActivityType - && matchesTopActivity(request.getTriggerTask()); + && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */); } @Override diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 7c9340e72f72..bceb8726b1cb 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -44,6 +44,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.content.ComponentName; import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; @@ -635,6 +636,7 @@ public final class TransitionInfo implements Parcelable { private @ColorInt int mBackgroundColor; private SurfaceControl mSnapshot = null; private float mSnapshotLuma; + private ComponentName mActivityComponent = null; public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { mContainer = container; @@ -663,6 +665,7 @@ public final class TransitionInfo implements Parcelable { mBackgroundColor = in.readInt(); mSnapshot = in.readTypedObject(SurfaceControl.CREATOR); mSnapshotLuma = in.readFloat(); + mActivityComponent = in.readTypedObject(ComponentName.CREATOR); } private Change localRemoteCopy() { @@ -685,6 +688,7 @@ public final class TransitionInfo implements Parcelable { out.mBackgroundColor = mBackgroundColor; out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null; out.mSnapshotLuma = mSnapshotLuma; + out.mActivityComponent = mActivityComponent; return out; } @@ -780,6 +784,11 @@ public final class TransitionInfo implements Parcelable { mSnapshotLuma = luma; } + /** Sets the component-name of the container. Container must be an Activity. */ + public void setActivityComponent(@Nullable ComponentName component) { + mActivityComponent = component; + } + /** @return the container that is changing. May be null if non-remotable (eg. activity) */ @Nullable public WindowContainerToken getContainer() { @@ -913,6 +922,12 @@ public final class TransitionInfo implements Parcelable { return mSnapshotLuma; } + /** @return the component-name of this container (if it is an activity). */ + @Nullable + public ComponentName getActivityComponent() { + return mActivityComponent; + } + /** @hide */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -936,6 +951,7 @@ public final class TransitionInfo implements Parcelable { dest.writeInt(mBackgroundColor); dest.writeTypedObject(mSnapshot, flags); dest.writeFloat(mSnapshotLuma); + dest.writeTypedObject(mActivityComponent, flags); } @NonNull @@ -994,6 +1010,10 @@ public final class TransitionInfo implements Parcelable { if (mLastParent != null) { sb.append(" lastParent="); sb.append(mLastParent); } + if (mActivityComponent != null) { + sb.append(" component="); + sb.append(mActivityComponent.flattenToShortString()); + } sb.append('}'); return sb.toString(); } diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp index 893cc98ff9b1..6f1c76385120 100644 --- a/core/jni/android_database_SQLiteConnection.cpp +++ b/core/jni/android_database_SQLiteConnection.cpp @@ -82,10 +82,16 @@ struct SQLiteConnection { const String8 path; const String8 label; + // The prepared statement used to determine which tables are updated by a statement. This + // is is initially null. It is set non-null on first use. + sqlite3_stmt* tableQuery; + volatile bool canceled; SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) : - db(db), openFlags(openFlags), path(path), label(label), canceled(false) { } + db(db), openFlags(openFlags), path(path), label(label), tableQuery(nullptr), + canceled(false) { } + }; // Called each time a statement begins execution, when tracing is enabled. @@ -188,6 +194,9 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) { if (connection) { ALOGV("Closing connection %p", connection->db); + if (connection->tableQuery != nullptr) { + sqlite3_finalize(connection->tableQuery); + } int err = sqlite3_close(connection->db); if (err != SQLITE_OK) { // This can happen if sub-objects aren't closed first. Make sure the caller knows. @@ -419,6 +428,46 @@ static jboolean nativeIsReadOnly(JNIEnv* env, jclass clazz, jlong connectionPtr, return sqlite3_stmt_readonly(statement) != 0; } +static jboolean nativeUpdatesTempOnly(JNIEnv* env, jclass, + jlong connectionPtr, jlong statementPtr) { + sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr); + SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr); + + int result = SQLITE_OK; + if (connection->tableQuery == nullptr) { + static char const* sql = + "SELECT COUNT(*) FROM tables_used(?) WHERE schema != 'temp' AND wr != 0"; + result = sqlite3_prepare_v2(connection->db, sql, -1, &connection->tableQuery, nullptr); + if (result != SQLITE_OK) { + ALOGE("failed to compile query table: %s", + sqlite3_errstr(sqlite3_extended_errcode(connection->db))); + return false; + } + } + + // A temporary, to simplify the code. + sqlite3_stmt* query = connection->tableQuery; + sqlite3_reset(query); + sqlite3_clear_bindings(query); + result = sqlite3_bind_text(query, 1, sqlite3_sql(statement), -1, SQLITE_STATIC); + if (result != SQLITE_OK) { + ALOGE("tables bind pointer returns %s", sqlite3_errstr(result)); + return false; + } + result = sqlite3_step(query); + if (result != SQLITE_ROW) { + ALOGE("tables query error: %d/%s", result, sqlite3_errstr(result)); + // Make sure the query is no longer bound to the statement. + sqlite3_clear_bindings(query); + return false; + } + + int count = sqlite3_column_int(query, 0); + // Make sure the query is no longer bound to the statement SQL string. + sqlite3_clear_bindings(query); + return count == 0; +} + static jint nativeGetColumnCount(JNIEnv* env, jclass clazz, jlong connectionPtr, jlong statementPtr) { sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr); @@ -915,6 +964,8 @@ static const JNINativeMethod sMethods[] = (void*)nativeGetParameterCount }, { "nativeIsReadOnly", "(JJ)Z", (void*)nativeIsReadOnly }, + { "nativeUpdatesTempOnly", "(JJ)Z", + (void*)nativeUpdatesTempOnly }, { "nativeGetColumnCount", "(JJ)I", (void*)nativeGetColumnCount }, { "nativeGetColumnName", "(JJI)Ljava/lang/String;", diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java index 43266a51502b..cb3f99c37a4f 100644 --- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java +++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java @@ -17,6 +17,7 @@ package android.animation; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.util.PollingCheck; @@ -343,6 +344,20 @@ public class AnimatorSetCallsTest { } @Test + public void childAnimatorCancelsDuringUpdate_animatorSetIsEnded() throws Throwable { + mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + animation.cancel(); + } + }); + mActivity.runOnUiThread(() -> { + mSet1.start(); + assertFalse(mSet1.isRunning()); + }); + } + + @Test public void reentrantStart() throws Throwable { CountDownLatch latch = new CountDownLatch(3); AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java index 3fc08ee6fd2e..bd5f809dc689 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java @@ -26,6 +26,9 @@ import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; import android.os.SystemClock; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.test.AndroidTestCase; import android.util.Log; @@ -35,6 +38,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -53,6 +57,10 @@ import java.util.concurrent.TimeUnit; @SmallTest public class SQLiteDatabaseTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final String TAG = "SQLiteDatabaseTest"; private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); @@ -347,4 +355,50 @@ public class SQLiteDatabaseTest { assertTrue("ReadThread failed with errors: " + errors, errors.isEmpty()); } + + @RequiresFlagsEnabled(Flags.FLAG_SQLITE_ALLOW_TEMP_TABLES) + @Test + public void testTempTable() { + boolean allowed; + allowed = true; + mDatabase.beginTransactionReadOnly(); + try { + mDatabase.execSQL("CREATE TEMP TABLE t1 (i int, j int);"); + mDatabase.execSQL("INSERT INTO t1 (i, j) VALUES (2, 20)"); + mDatabase.execSQL("INSERT INTO t1 (i, j) VALUES (3, 30)"); + + final String sql = "SELECT i FROM t1 WHERE j = 30"; + try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) { + assertTrue(s.step()); + assertEquals(3, s.getColumnInt(0)); + } + + } catch (SQLiteException e) { + allowed = false; + } finally { + mDatabase.endTransaction(); + } + assertTrue(allowed); + + // Repeat the test on the main schema. + allowed = true; + mDatabase.beginTransactionReadOnly(); + try { + mDatabase.execSQL("CREATE TABLE t2 (i int, j int);"); + mDatabase.execSQL("INSERT INTO t2 (i, j) VALUES (2, 20)"); + mDatabase.execSQL("INSERT INTO t2 (i, j) VALUES (3, 30)"); + + final String sql = "SELECT i FROM t2 WHERE j = 30"; + try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) { + assertTrue(s.step()); + assertEquals(3, s.getColumnInt(0)); + } + + } catch (SQLiteException e) { + allowed = false; + } finally { + mDatabase.endTransaction(); + } + assertFalse(allowed); + } } diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml index 681a52bea2b2..e04ab817215c 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml @@ -21,4 +21,9 @@ android:orientation="vertical" android:id="@+id/bubble_bar_expanded_view"> + <com.android.wm.shell.bubbles.bar.BubbleBarHandleView + android:id="@+id/bubble_bar_handle_view" + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> + </com.android.wm.shell.bubbles.bar.BubbleBarExpandedView> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index d073f1df938a..66c0c9640477 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -70,7 +70,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private @Nullable Supplier<Rect> mLayerBoundsSupplier; private @Nullable Listener mListener; - private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext()); + private BubbleBarHandleView mHandleView; private @Nullable TaskView mTaskView; private @Nullable BubbleOverflowContainerView mOverflowView; @@ -111,7 +111,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation)); mCaptionHeight = context.getResources().getDimensionPixelSize( R.dimen.bubble_bar_expanded_view_caption_height); - addView(mHandleView); + mHandleView = findViewById(R.id.bubble_bar_handle_view); applyThemeAttrs(); setClipToOutline(true); setOutlineProvider(new ViewOutlineProvider() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 7dec12aac39b..bc1a57572c63 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -388,6 +388,7 @@ class SplitScreenTransitions { IBinder startResizeTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, + @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishCallback) { if (mPendingResize != null) { mPendingResize.cancel(null); @@ -396,13 +397,14 @@ class SplitScreenTransitions { } IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler); - setResizeTransition(transition, finishCallback); + setResizeTransition(transition, consumedCallback, finishCallback); return transition; } void setResizeTransition(@NonNull IBinder transition, + @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishCallback) { - mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback); + mPendingResize = new TransitSession(transition, consumedCallback, finishCallback); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Resize split screen"); } 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 96e57e71f05c..0781a9e440f3 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 @@ -2236,8 +2236,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, sendOnBoundsChanged(); if (ENABLE_SHELL_TRANSITIONS) { mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart"); - mSplitTransitions.startResizeTransition(wct, this, (finishWct, t) -> - mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish")); + mSplitTransitions.startResizeTransition(wct, this, (aborted) -> { + mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed"); + }, (finishWct, t) -> { + mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"); + }); } else { // Only need screenshot for legacy case because shell transition should screenshot // itself during transition. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index e22bf3de30e4..e9da25813510 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -64,6 +64,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager.RunningTaskInfo; import android.app.IApplicationThread; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Binder; @@ -420,6 +421,30 @@ public class ShellTransitionTests extends ShellTestCase { } @Test + public void testTransitionFilterActivityComponent() { + TransitionFilter filter = new TransitionFilter(); + ComponentName cmpt = new ComponentName("testpak", "testcls"); + filter.mRequirements = + new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()}; + filter.mRequirements[0].mTopActivity = cmpt; + filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + + final RunningTaskInfo taskInf = createTaskInfo(1); + final TransitionInfo openTask = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN, taskInf).build(); + assertFalse(filter.matches(openTask)); + + taskInf.topActivity = cmpt; + final TransitionInfo openTaskCmpt = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN, taskInf).build(); + assertTrue(filter.matches(openTaskCmpt)); + + final TransitionInfo openAct = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN, cmpt).build(); + assertTrue(filter.matches(openAct)); + } + + @Test public void testRegisteredRemoteTransition() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java index 834385832e4a..b8939e6ff623 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java @@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static org.mockito.Mockito.mock; import android.app.ActivityManager; +import android.content.ComponentName; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -50,20 +51,34 @@ public class TransitionInfoBuilder { } public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode, - @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) { + @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo, + ComponentName activityComponent) { final TransitionInfo.Change change = new TransitionInfo.Change( taskInfo != null ? taskInfo.token : null, createMockSurface(true /* valid */)); change.setMode(mode); change.setFlags(flags); change.setTaskInfo(taskInfo); + change.setActivityComponent(activityComponent); return addChange(change); } + /** Add a change to the TransitionInfo */ + public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode, + @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) { + return addChange(mode, flags, taskInfo, null /* activityComponent */); + } + public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode, ActivityManager.RunningTaskInfo taskInfo) { return addChange(mode, TransitionInfo.FLAG_NONE, taskInfo); } + /** Add a change to the TransitionInfo */ + public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode, + ComponentName activityComponent) { + return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskinfo */, activityComponent); + } + public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode) { return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskInfo */); } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index a5a69f987113..4918289e8b5c 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -21,6 +21,7 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; +import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING; import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API; import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; @@ -10081,6 +10082,28 @@ public class AudioManager { } } + /** + * @hide + * Checks whether a notification sound should be played or not, as reported by the state + * of the audio framework. Querying whether playback should proceed is favored over + * playing and letting the sound be muted or not. + * @param aa the {@link AudioAttributes} of the notification about to maybe play + * @return true if the audio framework state is such that the notification should be played + * because at time of checking, and the notification will be heard, + * false otherwise + */ + @TestApi + @FlaggedApi(FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING) + @RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE) + public boolean shouldNotificationSoundPlay(@NonNull final AudioAttributes aa) { + final IAudioService service = getService(); + try { + return service.shouldNotificationSoundPlay(Objects.requireNonNull(aa)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + //==================================================================== // Mute await connection diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 5c268d4ab652..2eec9b3d4a09 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -775,4 +775,8 @@ interface IAudioService { @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)") FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss(); + + @EnforcePermission("QUERY_AUDIO_STATE") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE)") + boolean shouldNotificationSoundPlay(in AudioAttributes aa); } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 8ac364e72fef..b2c23a401117 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -69,7 +69,8 @@ class CredentialAutofillService : AutofillService() { companion object { private const val TAG = "CredAutofill" - private const val SESSION_ID_KEY = "session_id" + private const val SESSION_ID_KEY = "autofill_session_id" + private const val REQUEST_ID_KEY = "autofill_request_id" private const val CRED_HINT_PREFIX = "credential=" private const val REQUEST_DATA_KEY = "requestData" private const val CANDIDATE_DATA_KEY = "candidateQueryData" @@ -97,16 +98,23 @@ class CredentialAutofillService : AutofillService() { val callingPackage = structure.activityComponent.packageName Log.i(TAG, "onFillCredentialRequest called for $callingPackage") - var sessionId = request.clientState?.getInt(SESSION_ID_KEY) - - Log.i(TAG, "Autofill sessionId: " + sessionId) - if (sessionId == null) { - Log.i(TAG, "Session Id not found") - callback.onFailure("Session Id not found") + val clientState = request.clientState + if (clientState == null) { + Log.i(TAG, "Client state not found") + callback.onFailure("Client state not found") + return + } + val sessionId = clientState.getInt(SESSION_ID_KEY) + val requestId = clientState.getInt(REQUEST_ID_KEY) + Log.i(TAG, "Autofill sessionId: $sessionId, autofill requestId: $requestId") + if (sessionId == 0 || requestId == 0) { + Log.i(TAG, "Session Id or request Id not found") + callback.onFailure("Session Id or request Id not found") return } - val getCredRequest: GetCredentialRequest? = getCredManRequest(structure) + val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId, + requestId) if (getCredRequest == null) { Log.i(TAG, "No credential manager request found") callback.onFailure("No credential manager request found") @@ -515,12 +523,19 @@ class CredentialAutofillService : AutofillService() { TODO("Not yet implemented") } - private fun getCredManRequest(structure: AssistStructure): GetCredentialRequest? { + private fun getCredManRequest( + structure: AssistStructure, + sessionId: Int, + requestId: Int + ): GetCredentialRequest? { val credentialOptions: MutableList<CredentialOption> = mutableListOf() traverseStructure(structure, credentialOptions) if (credentialOptions.isNotEmpty()) { - return GetCredentialRequest.Builder(Bundle.EMPTY) + val dataBundle = Bundle() + dataBundle.putInt(SESSION_ID_KEY, sessionId) + dataBundle.putInt(REQUEST_ID_KEY, requestId) + return GetCredentialRequest.Builder(dataBundle) .setCredentialOptions(credentialOptions) .build() } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index 170cb4546d0c..9ad3e3c0af0f 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -91,7 +91,8 @@ public class UninstallerActivity extends Activity { // be stale, if e.g. the app was uninstalled while the activity was destroyed. super.onCreate(null); - if (usePiaV2() && !isTv()) { + // TODO(b/318521110) Enable PIA v2 for archive dialog. + if (usePiaV2() && !isTv() && !isArchiveDialog(getIntent())) { Log.i(TAG, "Using Pia V2"); boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); @@ -224,6 +225,11 @@ public class UninstallerActivity extends Activity { showConfirmationDialog(); } + private boolean isArchiveDialog(Intent intent) { + return (intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0) + & PackageManager.DELETE_ARCHIVE) != 0; + } + /** * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent} * to archive an app if requested. diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml index 0e40db23c66c..f44b16104f99 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml @@ -14,11 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<resources - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> +<resources> <style name="TextAppearance.PreferenceTitle.SettingsLib" parent="@android:style/TextAppearance.Material.Subhead"> - <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> + <item name="android:textColor">@color/settingslib_text_color_primary</item> <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> <item name="android:textSize">20sp</item> </style> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index d20154437e02..d3d8e4ed3523 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -172,7 +172,7 @@ private fun BoxScope.CommunalHubLazyGrid( gridModifier = gridModifier .fillMaxSize() - .dragContainer(dragDropState, beforeContentPadding(contentPadding)) + .dragContainer(dragDropState, beforeContentPadding(contentPadding), viewModel) .onGloballyPositioned { setGridCoordinates(it) } // for widgets dropped from other activities val dragAndDropTargetState = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt index 1b40de4ef5df..113822167ca7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.unit.toOffset import androidx.compose.ui.unit.toSize import androidx.compose.ui.zIndex import com.android.systemui.communal.ui.compose.extensions.plus +import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch @@ -207,7 +208,8 @@ internal constructor( fun Modifier.dragContainer( dragDropState: GridDragDropState, - beforeContentPadding: ContentPaddingInPx + beforeContentPadding: ContentPaddingInPx, + viewModel: BaseCommunalViewModel, ): Modifier { return pointerInput(dragDropState, beforeContentPadding) { detectDragGesturesAfterLongPress( @@ -220,9 +222,16 @@ fun Modifier.dragContainer( offset, Offset(beforeContentPadding.startPadding, beforeContentPadding.topPadding) ) + viewModel.onReorderWidgetStart() }, - onDragEnd = { dragDropState.onDragInterrupted() }, - onDragCancel = { dragDropState.onDragInterrupted() } + onDragEnd = { + dragDropState.onDragInterrupted() + viewModel.onReorderWidgetEnd() + }, + onDragCancel = { + dragDropState.onDragInterrupted() + viewModel.onReorderWidgetCancel() + } ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 449ee6f414dd..4079f1241f31 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -19,14 +19,14 @@ package com.android.systemui.communal.data.repository import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo -import android.content.BroadcastReceiver import android.content.ComponentName +import android.content.Intent +import android.content.Intent.ACTION_USER_UNLOCKED import android.os.UserHandle import android.os.UserManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.data.db.CommunalItemRank import com.android.systemui.communal.data.db.CommunalWidgetDao import com.android.systemui.communal.data.db.CommunalWidgetItem @@ -38,15 +38,12 @@ import com.android.systemui.log.core.FakeLogBuffer import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.kotlinArgumentCaptor -import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -55,8 +52,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.anyInt +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -70,8 +67,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var appWidgetHost: AppWidgetHost - @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var userManager: UserManager @Mock private lateinit var userHandle: UserHandle @@ -125,10 +120,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { testScope.runTest { communalEnabled(false) val repository = initCommunalWidgetRepository() - collectLastValue(repository.communalWidgets)() + repository.communalWidgets.launchIn(backgroundScope) runCurrent() - verify(communalWidgetDao, Mockito.never()).getWidgets() + verify(communalWidgetDao, never()).getWidgets() } @Test @@ -136,10 +131,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { testScope.runTest { userUnlocked(false) val repository = initCommunalWidgetRepository() - collectLastValue(repository.communalWidgets)() + repository.communalWidgets.launchIn(backgroundScope) runCurrent() - verify(communalWidgetDao, Mockito.never()).getWidgets() + verify(communalWidgetDao, never()).getWidgets() } @Test @@ -147,8 +142,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { testScope.runTest { userUnlocked(false) val repository = initCommunalWidgetRepository() - val communalWidgets = collectLastValue(repository.communalWidgets) - communalWidgets() + val communalWidgets by collectLastValue(repository.communalWidgets) runCurrent() val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1) val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L) @@ -158,11 +152,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { userUnlocked(true) installedProviders(listOf(stopwatchProviderInfo)) - broadcastReceiverUpdate() + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(ACTION_USER_UNLOCKED) + ) runCurrent() verify(communalWidgetDao).getWidgets() - assertThat(communalWidgets()) + assertThat(communalWidgets) .containsExactly( CommunalWidgetContentModel( appWidgetId = communalWidgetItemEntry.widgetId, @@ -182,9 +179,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val priority = 1 + whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true) whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>())) .thenReturn(id) - repository.addWidget(provider, priority) + repository.addWidget(provider, priority) { true } runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider) @@ -192,75 +190,117 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { } @Test - fun deleteWidget_removeWidgetId_andDeleteFromDb() = + fun addWidget_configurationFails_doNotAddWidgetToDb() = testScope.runTest { userUnlocked(true) val repository = initCommunalWidgetRepository() runCurrent() + val provider = ComponentName("pkg_name", "cls_name") val id = 1 - repository.deleteWidget(id) + val priority = 1 + whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true) + whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id) + repository.addWidget(provider, priority) { false } runCurrent() - verify(communalWidgetDao).deleteWidgetById(id) + verify(communalWidgetHost).allocateIdAndBindWidget(provider) + verify(communalWidgetDao, never()).addWidget(id, provider, priority) verify(appWidgetHost).deleteAppWidgetId(id) } @Test - fun reorderWidgets_queryDb() = + fun addWidget_configurationThrowsError_doNotAddWidgetToDb() = testScope.runTest { userUnlocked(true) val repository = initCommunalWidgetRepository() runCurrent() - val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3) - repository.updateWidgetOrder(widgetIdToPriorityMap) + val provider = ComponentName("pkg_name", "cls_name") + val id = 1 + val priority = 1 + whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true) + whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id) + repository.addWidget(provider, priority) { throw IllegalStateException("some error") } runCurrent() - verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap) + verify(communalWidgetHost).allocateIdAndBindWidget(provider) + verify(communalWidgetDao, never()).addWidget(id, provider, priority) + verify(appWidgetHost).deleteAppWidgetId(id) } @Test - fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() = + fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() = testScope.runTest { - communalEnabled(false) + userUnlocked(true) val repository = initCommunalWidgetRepository() - collectLastValue(repository.communalWidgets)() - verifyBroadcastReceiverNeverRegistered() + runCurrent() + + val provider = ComponentName("pkg_name", "cls_name") + val id = 1 + val priority = 1 + whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(false) + whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>())) + .thenReturn(id) + var configured = false + repository.addWidget(provider, priority) { + configured = true + true + } + runCurrent() + + verify(communalWidgetHost).allocateIdAndBindWidget(provider) + verify(communalWidgetDao).addWidget(id, provider, priority) + assertThat(configured).isFalse() } @Test - fun broadcastReceiver_featureEnabledAndUserUnlocked_doNotRegisterBroadcastReceiver() = + fun deleteWidget_removeWidgetId_andDeleteFromDb() = testScope.runTest { userUnlocked(true) val repository = initCommunalWidgetRepository() - collectLastValue(repository.communalWidgets)() - verifyBroadcastReceiverNeverRegistered() + runCurrent() + + val id = 1 + repository.deleteWidget(id) + runCurrent() + + verify(communalWidgetDao).deleteWidgetById(id) + verify(appWidgetHost).deleteAppWidgetId(id) } @Test - fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() = + fun reorderWidgets_queryDb() = testScope.runTest { - userUnlocked(false) + userUnlocked(true) val repository = initCommunalWidgetRepository() - collectLastValue(repository.communalWidgets)() - verifyBroadcastReceiverRegistered() + runCurrent() + + val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3) + repository.updateWidgetOrder(widgetIdToPriorityMap) + runCurrent() + + verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap) } @Test - fun broadcastReceiver_whenFlowFinishes_unregisterBroadcastReceiver() = + fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() = testScope.runTest { - userUnlocked(false) + communalEnabled(false) val repository = initCommunalWidgetRepository() - - val job = launch { repository.communalWidgets.collect() } + repository.communalWidgets.launchIn(backgroundScope) runCurrent() - val receiver = broadcastReceiverUpdate() + assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0) + } - job.cancel() + @Test + fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() = + testScope.runTest { + userUnlocked(false) + val repository = initCommunalWidgetRepository() + repository.communalWidgets.launchIn(backgroundScope) runCurrent() - - verify(broadcastDispatcher).unregisterReceiver(receiver) + assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1) } @Test @@ -268,12 +308,16 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { testScope.runTest { userUnlocked(false) val repository = initCommunalWidgetRepository() - collectLastValue(repository.communalWidgets)() - verify(appWidgetHost, Mockito.never()).startListening() + repository.communalWidgets.launchIn(backgroundScope) + runCurrent() + verify(appWidgetHost, never()).startListening() userUnlocked(true) - broadcastReceiverUpdate() - collectLastValue(repository.communalWidgets)() + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(ACTION_USER_UNLOCKED) + ) + runCurrent() verify(appWidgetHost).startListening() } @@ -283,18 +327,25 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { testScope.runTest { userUnlocked(false) val repository = initCommunalWidgetRepository() - collectLastValue(repository.communalWidgets)() + repository.communalWidgets.launchIn(backgroundScope) + runCurrent() userUnlocked(true) - broadcastReceiverUpdate() - collectLastValue(repository.communalWidgets)() + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(ACTION_USER_UNLOCKED) + ) + runCurrent() verify(appWidgetHost).startListening() - verify(appWidgetHost, Mockito.never()).stopListening() + verify(appWidgetHost, never()).stopListening() userUnlocked(false) - broadcastReceiverUpdate() - collectLastValue(repository.communalWidgets)() + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(ACTION_USER_UNLOCKED) + ) + runCurrent() verify(appWidgetHost).stopListening() } @@ -305,7 +356,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { appWidgetHost, testScope.backgroundScope, testDispatcher, - broadcastDispatcher, + fakeBroadcastDispatcher, communalRepository, communalWidgetHost, communalWidgetDao, @@ -315,45 +366,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { ) } - private fun verifyBroadcastReceiverRegistered() { - verify(broadcastDispatcher) - .registerReceiver( - any(), - any(), - nullable(), - nullable(), - anyInt(), - nullable(), - ) - } - - private fun verifyBroadcastReceiverNeverRegistered() { - verify(broadcastDispatcher, Mockito.never()) - .registerReceiver( - any(), - any(), - nullable(), - nullable(), - anyInt(), - nullable(), - ) - } - - private fun broadcastReceiverUpdate(): BroadcastReceiver { - val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>() - verify(broadcastDispatcher) - .registerReceiver( - broadcastReceiverCaptor.capture(), - any(), - nullable(), - nullable(), - anyInt(), - nullable(), - ) - broadcastReceiverCaptor.value.onReceive(null, null) - return broadcastReceiverCaptor.value - } - private fun communalEnabled(enabled: Boolean) { communalRepository.setIsCommunalEnabled(enabled) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 4a935d0e229a..ff6fd43745df 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -16,12 +16,17 @@ package com.android.systemui.communal.view.viewmodel +import android.app.Activity.RESULT_CANCELED +import android.app.Activity.RESULT_OK import android.app.smartspace.SmartspaceTarget +import android.appwidget.AppWidgetHost +import android.content.ComponentName import android.os.PowerManager import android.provider.Settings import android.widget.RemoteViews import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository @@ -29,6 +34,7 @@ import com.android.systemui.communal.data.repository.FakeCommunalTutorialReposit import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.coroutines.collectLastValue @@ -42,20 +48,26 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import javax.inject.Provider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class CommunalEditModeViewModelTest : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost @Mock private lateinit var shadeViewController: ShadeViewController @Mock private lateinit var powerManager: PowerManager + @Mock private lateinit var appWidgetHost: AppWidgetHost + @Mock private lateinit var uiEventLogger: UiEventLogger private val kosmos = testKosmos() private val testScope = kosmos.testScope @@ -73,7 +85,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - val withDeps = CommunalInteractorFactory.create() + val withDeps = CommunalInteractorFactory.create(testScope) keyguardRepository = withDeps.keyguardRepository communalRepository = withDeps.communalRepository tutorialRepository = withDeps.tutorialRepository @@ -84,9 +96,11 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { underTest = CommunalEditModeViewModel( withDeps.communalInteractor, + appWidgetHost, Provider { shadeViewController }, powerManager, mediaHost, + uiEventLogger, ) } @@ -145,4 +159,71 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { ) .isEqualTo(false) } + + @Test + fun addingWidgetTriggersConfiguration() = + testScope.runTest { + val provider = ComponentName("pkg.test", "testWidget") + val widgetToConfigure by collectLastValue(underTest.widgetsToConfigure) + assertThat(widgetToConfigure).isNull() + underTest.onAddWidget(componentName = provider, priority = 0) + assertThat(widgetToConfigure).isEqualTo(1) + } + + @Test + fun settingResultOkAddsWidget() = + testScope.runTest { + val provider = ComponentName("pkg.test", "testWidget") + val widgetAdded by collectLastValue(widgetRepository.widgetAdded) + assertThat(widgetAdded).isNull() + underTest.onAddWidget(componentName = provider, priority = 0) + assertThat(widgetAdded).isNull() + underTest.setConfigurationResult(RESULT_OK) + assertThat(widgetAdded).isEqualTo(1) + } + + @Test + fun settingResultCancelledDoesNotAddWidget() = + testScope.runTest { + val provider = ComponentName("pkg.test", "testWidget") + val widgetAdded by collectLastValue(widgetRepository.widgetAdded) + assertThat(widgetAdded).isNull() + underTest.onAddWidget(componentName = provider, priority = 0) + assertThat(widgetAdded).isNull() + underTest.setConfigurationResult(RESULT_CANCELED) + assertThat(widgetAdded).isNull() + } + + @Test(expected = IllegalStateException::class) + fun settingResultBeforeWidgetAddedThrowsException() { + underTest.setConfigurationResult(RESULT_OK) + } + + @Test(expected = IllegalStateException::class) + fun addingWidgetWhileConfigurationActiveFails() = + testScope.runTest { + val providerOne = ComponentName("pkg.test", "testWidget") + underTest.onAddWidget(componentName = providerOne, priority = 0) + runCurrent() + val providerTwo = ComponentName("pkg.test", "testWidget2") + underTest.onAddWidget(componentName = providerTwo, priority = 0) + } + + @Test + fun reorderWidget_uiEventLogging_start() { + underTest.onReorderWidgetStart() + verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START) + } + + @Test + fun reorderWidget_uiEventLogging_end() { + underTest.onReorderWidgetEnd() + verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH) + } + + @Test + fun reorderWidget_uiEventLogging_cancel() { + underTest.onReorderWidgetCancel() + verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 7c3dc972cfd0..5b88ebe69bfe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -32,7 +32,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope -import com.android.systemui.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor import com.android.systemui.statusbar.phone.dozeParameters import com.android.systemui.statusbar.phone.screenOffAnimationController import com.android.systemui.testKosmos @@ -56,8 +56,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() { private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val screenOffAnimationController = kosmos.screenOffAnimationController private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository - private val fakeNotificationsKeyguardViewStateRepository = - kosmos.fakeNotificationsKeyguardViewStateRepository + private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor private val dozeParameters = kosmos.dozeParameters private val underTest = kosmos.keyguardRootViewModel @@ -118,7 +117,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() { testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true) + notificationsKeyguardInteractor.setPulseExpanding(true) deviceEntryRepository.setBypassEnabled(false) runCurrent() @@ -130,9 +129,9 @@ class KeyguardRootViewModelTest : SysuiTestCase() { testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + notificationsKeyguardInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(true) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + notificationsKeyguardInteractor.setNotificationsFullyHidden(true) runCurrent() assertThat(isVisible?.value).isTrue() @@ -144,10 +143,10 @@ class KeyguardRootViewModelTest : SysuiTestCase() { testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + notificationsKeyguardInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(false) whenever(dozeParameters.alwaysOn).thenReturn(false) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + notificationsKeyguardInteractor.setNotificationsFullyHidden(true) runCurrent() assertThat(isVisible?.value).isTrue() @@ -159,11 +158,11 @@ class KeyguardRootViewModelTest : SysuiTestCase() { testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + notificationsKeyguardInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(false) whenever(dozeParameters.alwaysOn).thenReturn(true) whenever(dozeParameters.displayNeedsBlanking).thenReturn(true) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + notificationsKeyguardInteractor.setNotificationsFullyHidden(true) runCurrent() assertThat(isVisible?.value).isTrue() @@ -175,11 +174,11 @@ class KeyguardRootViewModelTest : SysuiTestCase() { testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + notificationsKeyguardInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(false) whenever(dozeParameters.alwaysOn).thenReturn(true) whenever(dozeParameters.displayNeedsBlanking).thenReturn(false) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + notificationsKeyguardInteractor.setNotificationsFullyHidden(true) runCurrent() assertThat(isVisible?.value).isTrue() @@ -191,11 +190,11 @@ class KeyguardRootViewModelTest : SysuiTestCase() { testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false) + notificationsKeyguardInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(false) whenever(dozeParameters.alwaysOn).thenReturn(true) whenever(dozeParameters.displayNeedsBlanking).thenReturn(false) - fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true) + notificationsKeyguardInteractor.setNotificationsFullyHidden(true) runCurrent() assertThat(isVisible?.isAnimating).isEqualTo(true) diff --git a/packages/SystemUI/res/color/notification_state_color_dark.xml b/packages/SystemUI/res/color/notification_state_color_dark.xml new file mode 100644 index 000000000000..d26cbd5578df --- /dev/null +++ b/packages/SystemUI/res/color/notification_state_color_dark.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <!-- Pressed state's alpha is set to 0.00 temporarily until this bug is resolved permanently + b/313920497 Design intended alpha is 0.15--> + <item android:state_pressed="true" android:color="#ffffff" android:alpha="0.00" /> + <item android:state_hovered="true" android:color="#ffffff" android:alpha="0.11" /> + <item android:color="@color/transparent" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/notification_overlay_color.xml b/packages/SystemUI/res/color/notification_state_color_default.xml index a14a7ad9d2da..a14a7ad9d2da 100644 --- a/packages/SystemUI/res/color/notification_overlay_color.xml +++ b/packages/SystemUI/res/color/notification_state_color_default.xml diff --git a/packages/SystemUI/res/color/notification_state_color_light.xml b/packages/SystemUI/res/color/notification_state_color_light.xml new file mode 100644 index 000000000000..3e8bcf3e69e2 --- /dev/null +++ b/packages/SystemUI/res/color/notification_state_color_light.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <!-- Pressed state's alpha is set to 0.00 temporarily until this bug is resolved permanently + b/313920497 Design intended alpha is 0.15--> + <item android:state_pressed="true" android:color="#000000" android:alpha="0.00" /> + <item android:state_hovered="true" android:color="#000000" android:alpha="0.11" /> + <item android:color="@color/transparent" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml index 355e75d0716b..3f903aece0b5 100644 --- a/packages/SystemUI/res/drawable/notification_material_bg.xml +++ b/packages/SystemUI/res/drawable/notification_material_bg.xml @@ -25,7 +25,7 @@ </item> <item> <shape> - <solid android:color="@color/notification_overlay_color" /> + <solid android:color="@color/notification_state_color_default" /> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 64a1d248b221..17719d11345b 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -1002,4 +1002,7 @@ <item>com.android.switchaccess.SwitchAccessService</item> <item>com.google.android.apps.accessibility.voiceaccess.JustSpeakService</item> </string-array> + + <!-- Whether to use a machine learning model for back gesture falsing. --> + <bool name="config_useBackGestureML">true</bool> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 3f026a4cec8a..7d7c050a666c 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -921,7 +921,7 @@ <style name="Theme.ControlsActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar"> <item name="android:windowActivityTransitions">true</item> <item name="android:windowContentTransitions">false</item> - <item name="android:windowIsTranslucent">false</item> + <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/black</item> <item name="android:windowAnimationStyle">@null</item> <item name="android:statusBarColor">@android:color/black</item> diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index cab8adfc0bd9..e6816e954b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -18,14 +18,12 @@ package com.android.systemui.communal.data.repository import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager -import android.content.BroadcastReceiver import android.content.ComponentName -import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.UserManager +import androidx.annotation.WorkerThread import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.communal.data.db.CommunalItemRank import com.android.systemui.communal.data.db.CommunalWidgetDao import com.android.systemui.communal.data.db.CommunalWidgetItem @@ -40,17 +38,21 @@ import com.android.systemui.log.dagger.CommunalLog import com.android.systemui.settings.UserTracker import java.util.Optional import javax.inject.Inject +import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** Encapsulates the state of widgets for communal mode. */ interface CommunalWidgetRepository { @@ -58,7 +60,11 @@ interface CommunalWidgetRepository { val communalWidgets: Flow<List<CommunalWidgetContentModel>> /** Add a widget at the specified position in the app widget service and the database. */ - fun addWidget(provider: ComponentName, priority: Int) {} + fun addWidget( + provider: ComponentName, + priority: Int, + configureWidget: suspend (id: Int) -> Boolean + ) {} /** Delete a widget by id from app widget service and the database. */ fun deleteWidget(widgetId: Int) {} @@ -97,37 +103,22 @@ constructor( // Whether the [AppWidgetHost] is listening for updates. private var isHostListening = false - private val isUserUnlocked: Flow<Boolean> = - callbackFlow { - if (!communalRepository.isCommunalEnabled) { - awaitClose() - } + private suspend fun isUserUnlockingOrUnlocked(): Boolean = + withContext(bgDispatcher) { userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) } - fun isUserUnlockingOrUnlocked(): Boolean { - return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) - } - - fun send() { - trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG) - } - - if (isUserUnlockingOrUnlocked()) { - send() - awaitClose() + private val isUserUnlocked: Flow<Boolean> = + flowOf(communalRepository.isCommunalEnabled) + .flatMapLatest { enabled -> + if (enabled) { + broadcastDispatcher + .broadcastFlow( + filter = IntentFilter(Intent.ACTION_USER_UNLOCKED), + user = userTracker.userHandle + ) + .onStart { emit(Unit) } + .mapLatest { isUserUnlockingOrUnlocked() } } else { - val receiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - send() - } - } - - broadcastDispatcher.registerReceiver( - receiver, - IntentFilter(Intent.ACTION_USER_UNLOCKED), - ) - - awaitClose { broadcastDispatcher.unregisterReceiver(receiver) } + emptyFlow() } } .distinctUntilChanged() @@ -148,18 +139,52 @@ constructor( if (!isHostActive || !appWidgetManager.isPresent) { return@flatMapLatest flowOf(emptyList()) } - communalWidgetDao.getWidgets().map { it.map(::mapToContentModel) } + communalWidgetDao + .getWidgets() + .map { it.map(::mapToContentModel) } + // As this reads from a database and triggers IPCs to AppWidgetManager, + // it should be executed in the background. + .flowOn(bgDispatcher) } - override fun addWidget(provider: ComponentName, priority: Int) { + override fun addWidget( + provider: ComponentName, + priority: Int, + configureWidget: suspend (id: Int) -> Boolean + ) { applicationScope.launch(bgDispatcher) { val id = communalWidgetHost.allocateIdAndBindWidget(provider) - id?.let { - communalWidgetDao.addWidget( - widgetId = it, - provider = provider, - priority = priority, - ) + if (id != null) { + val configured = + if (communalWidgetHost.requiresConfiguration(id)) { + logger.i("Widget ${provider.flattenToString()} requires configuration.") + try { + configureWidget.invoke(id) + } catch (ex: Exception) { + // Cleanup the app widget id if an error happens during configuration. + logger.e("Error during widget configuration, cleaning up id $id", ex) + if (ex is CancellationException) { + appWidgetHost.deleteAppWidgetId(id) + // Re-throw cancellation to ensure the parent coroutine also gets + // cancelled. + throw ex + } else { + false + } + } + } else { + logger.i("Skipping configuration for ${provider.flattenToString()}") + true + } + if (configured) { + communalWidgetDao.addWidget( + widgetId = id, + provider = provider, + priority = priority, + ) + } else { + appWidgetHost.deleteAppWidgetId(id) + } } logger.i("Added widget ${provider.flattenToString()} at position $priority.") } @@ -182,6 +207,7 @@ constructor( } } + @WorkerThread private fun mapToContentModel( entry: Map.Entry<CommunalItemRank, CommunalWidgetItem> ): CommunalWidgetContentModel { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 827123882c74..24d4c6c4c397 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -99,9 +99,17 @@ constructor( /** Dismiss the CTA tile from the hub in view mode. */ fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false) - /** Add a widget at the specified position. */ - fun addWidget(componentName: ComponentName, priority: Int) = - widgetRepository.addWidget(componentName, priority) + /** + * Add a widget at the specified position. + * + * @param configureWidget The callback to trigger if widget configuration is needed. Should + * return whether configuration was successful. + */ + fun addWidget( + componentName: ComponentName, + priority: Int, + configureWidget: suspend (id: Int) -> Boolean + ) = widgetRepository.addWidget(componentName, priority, configureWidget) /** Delete a widget by id. */ fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id) diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt index 155de323d3a6..41f9cb4c98ed 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt @@ -18,6 +18,8 @@ package com.android.systemui.communal.shared import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL +import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE import android.content.ComponentName import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger @@ -63,4 +65,23 @@ constructor( } return false } + + /** + * Returns whether a particular widget requires configuration when it is first added. + * + * Must be called after the widget id has been bound. + */ + fun requiresConfiguration(widgetId: Int): Boolean { + if (appWidgetManager.isPresent) { + val widgetInfo = appWidgetManager.get().getAppWidgetInfo(widgetId) + val featureFlags: Int = widgetInfo.widgetFeatures + // A widget's configuration is optional only if it's configuration is marked as optional + // AND it can be reconfigured later. + val configurationOptional = + (featureFlags and WIDGET_FEATURE_CONFIGURATION_OPTIONAL != 0 && + featureFlags and WIDGET_FEATURE_RECONFIGURABLE != 0) + return widgetInfo.configure != null && !configurationOptional + } + return false + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 4fabd97531b1..4cb83a3b51dc 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -58,8 +58,16 @@ abstract class BaseCommunalViewModel( /** * Called when a widget is added via drag and drop from the widget picker into the communal hub. */ - fun onAddWidget(componentName: ComponentName, priority: Int) { - communalInteractor.addWidget(componentName, priority) + open fun onAddWidget(componentName: ComponentName, priority: Int) { + communalInteractor.addWidget(componentName, priority, ::configureWidget) + } + + /** + * Called when a widget needs to be configured, with the id of the widget. The return value + * should represent whether configuring the widget was successful. + */ + protected open suspend fun configureWidget(widgetId: Int): Boolean { + return true } // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block @@ -108,4 +116,13 @@ abstract class BaseCommunalViewModel( /** Gets the interaction handler used to handle taps on a remote view */ abstract fun getInteractionHandler(): RemoteViews.InteractionHandler + + /** Called as the user starts dragging a widget to reorder. */ + open fun onReorderWidgetStart() {} + + /** Called as the user finishes dragging a widget to reorder. */ + open fun onReorderWidgetEnd() {} + + /** Called as the user cancels dragging a widget to reorder. */ + open fun onReorderWidgetCancel() {} } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index d8e831c5a40d..0cbf3f1312e2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -16,18 +16,30 @@ package com.android.systemui.communal.ui.viewmodel +import android.app.Activity +import android.app.Activity.RESULT_CANCELED +import android.app.Activity.RESULT_OK +import android.app.ActivityOptions +import android.appwidget.AppWidgetHost +import android.content.ActivityNotFoundException +import android.content.ComponentName import android.os.PowerManager import android.widget.RemoteViews +import com.android.internal.logging.UiEventLogger import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.media.dagger.MediaModule import com.android.systemui.shade.ShadeViewController +import com.android.systemui.util.nullableAtomicReference import javax.inject.Inject import javax.inject.Named import javax.inject.Provider +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.map /** The view model for communal hub in edit mode. */ @@ -36,11 +48,28 @@ class CommunalEditModeViewModel @Inject constructor( private val communalInteractor: CommunalInteractor, + private val appWidgetHost: AppWidgetHost, shadeViewController: Provider<ShadeViewController>, powerManager: PowerManager, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, + private val uiEventLogger: UiEventLogger, ) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) { + private companion object { + private const val KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle" + private const val SPLASH_SCREEN_STYLE_EMPTY = 0 + } + + private val _widgetsToConfigure = MutableSharedFlow<Int>() + + /** + * Flow emitting ids of widgets which need to be configured. The consumer of this flow should + * trigger [startConfigurationActivity] to initiate configuration. + */ + val widgetsToConfigure: Flow<Int> = _widgetsToConfigure + + private var pendingConfiguration: CompletableDeferred<Int>? by nullableAtomicReference() + override val isEditMode = true // Only widgets are editable. The CTA tile comes last in the list and remains visible. @@ -58,4 +87,67 @@ constructor( // Ignore all interactions in edit mode. return RemoteViews.InteractionHandler { _, _, _ -> false } } + + override fun onAddWidget(componentName: ComponentName, priority: Int) { + if (pendingConfiguration != null) { + throw IllegalStateException( + "Cannot add $componentName widget while widget configuration is pending" + ) + } + super.onAddWidget(componentName, priority) + } + + fun startConfigurationActivity(activity: Activity, widgetId: Int, requestCode: Int) { + val options = + ActivityOptions.makeBasic().apply { + setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) + } + val bundle = options.toBundle() + bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY) + try { + appWidgetHost.startAppWidgetConfigureActivityForResult( + activity, + widgetId, + 0, + // Use the widget id as the request code. + requestCode, + bundle + ) + } catch (e: ActivityNotFoundException) { + setConfigurationResult(RESULT_CANCELED) + } + } + + override suspend fun configureWidget(widgetId: Int): Boolean { + if (pendingConfiguration != null) { + throw IllegalStateException( + "Attempting to configure $widgetId while another configuration is already active" + ) + } + pendingConfiguration = CompletableDeferred() + _widgetsToConfigure.emit(widgetId) + val resultCode = pendingConfiguration?.await() ?: RESULT_CANCELED + pendingConfiguration = null + return resultCode == RESULT_OK + } + + /** Sets the result of widget configuration. */ + fun setConfigurationResult(resultCode: Int) { + pendingConfiguration?.complete(resultCode) + ?: throw IllegalStateException("No widget pending configuration") + } + + override fun onReorderWidgetStart() { + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START) + } + + override fun onReorderWidgetEnd() { + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH) + } + + override fun onReorderWidgetCancel() { + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL) + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 0f94a92dd7ce..bfc6f2b14acd 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -27,23 +27,26 @@ import android.view.WindowInsets import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -import com.android.systemui.communal.domain.interactor.CommunalInteractor +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent import javax.inject.Inject +import kotlinx.coroutines.launch /** An Activity for editing the widgets that appear in hub mode. */ class EditWidgetsActivity @Inject constructor( private val communalViewModel: CommunalEditModeViewModel, - private val communalInteractor: CommunalInteractor, private var windowManagerService: IWindowManager? = null, ) : ComponentActivity() { companion object { private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag" private const val EXTRA_FILTER_STRATEGY = "filter_strategy" private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1 + private const val REQUEST_CODE_CONFIGURE_WIDGET = 1 private const val TAG = "EditWidgetsActivity" } @@ -63,7 +66,7 @@ constructor( Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java ) - ?.let { communalInteractor.addWidget(it, 0) } + ?.let { communalViewModel.onAddWidget(it, 0) } ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") } } } @@ -84,14 +87,26 @@ constructor( windowInsetsController?.hide(WindowInsets.Type.systemBars()) window.setDecorFitsSystemWindows(false) + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + // Start the configuration activity when new widgets are added. + communalViewModel.widgetsToConfigure.collect { widgetId -> + communalViewModel.startConfigurationActivity( + activity = this@EditWidgetsActivity, + widgetId = widgetId, + requestCode = REQUEST_CODE_CONFIGURE_WIDGET + ) + } + } + } + setCommunalEditWidgetActivityContent( activity = this, viewModel = communalViewModel, onOpenWidgetPicker = { - val localPackageManager: PackageManager = getPackageManager() val intent = Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) } - localPackageManager + packageManager .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) ?.activityInfo ?.packageName @@ -122,4 +137,11 @@ constructor( } ) } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_CODE_CONFIGURE_WIDGET) { + communalViewModel.setConfigurationResult(resultCode) + } + } } 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 e660b97b5c6b..0d641ac9c688 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -758,7 +758,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } private void updateMLModelState() { - boolean newState = mIsGestureHandlingEnabled && DeviceConfig.getBoolean( + boolean newState = mIsGestureHandlingEnabled && mContext.getResources().getBoolean( + R.bool.config_useBackGestureML) && DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index a5c1cf1af191..6f4a1e7754f5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -4362,8 +4362,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onHeadsUpPinned(NotificationEntry entry) { if (!isKeyguardShowing()) { - mNotificationStackScrollLayoutController.generateHeadsUpAnimation( - entry.getHeadsUpAnimationView(), true); + mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true); } } @@ -4375,8 +4374,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // notification // will stick to the top without any interaction. if (isFullyCollapsed() && entry.isRowHeadsUp() && !isKeyguardShowing()) { - mNotificationStackScrollLayoutController.generateHeadsUpAnimation( - entry.getHeadsUpAnimationView(), false); + mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, false); entry.setHeadsUpIsVisible(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 0c67279c1660..3f2c818399d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -31,6 +31,7 @@ import com.android.systemui.shade.ShadeExpansionListener import com.android.systemui.shade.ShadeViewController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.StackStateAnimator @@ -58,6 +59,7 @@ constructor( private val dozeParameters: DozeParameters, private val screenOffAnimationController: ScreenOffAnimationController, private val logger: NotificationWakeUpCoordinatorLogger, + private val notifsKeyguardInteractor: NotificationsKeyguardInteractor, ) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, @@ -144,6 +146,7 @@ constructor( for (listener in wakeUpListeners) { listener.onFullyHiddenChanged(value) } + notifsKeyguardInteractor.setNotificationsFullyHidden(value) } } @@ -216,6 +219,7 @@ constructor( for (listener in wakeUpListeners) { listener.onPulseExpandingChanged(pulseExpanding) } + notifsKeyguardInteractor.setPulseExpanding(pulseExpanding) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt index 5435fb5449cd..2cac0002f013 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt @@ -15,8 +15,6 @@ */ package com.android.systemui.statusbar.notification.data -import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardStateRepositoryModule import dagger.Module -@Module(includes = [NotificationsKeyguardStateRepositoryModule::class]) -interface NotificationDataLayerModule +@Module(includes = []) interface NotificationDataLayerModule diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt index 2cc1403a80a5..bd6ea30c44e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt @@ -15,59 +15,16 @@ */ package com.android.systemui.statusbar.notification.data.repository -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator -import dagger.Binds -import dagger.Module import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow /** View-states pertaining to notifications on the keyguard. */ -interface NotificationsKeyguardViewStateRepository { +@SysUISingleton +class NotificationsKeyguardViewStateRepository @Inject constructor() { /** Are notifications fully hidden from view? */ - val areNotificationsFullyHidden: Flow<Boolean> + val areNotificationsFullyHidden = MutableStateFlow(false) /** Is a pulse expansion occurring? */ - val isPulseExpanding: Flow<Boolean> -} - -@Module -interface NotificationsKeyguardStateRepositoryModule { - @Binds - fun bindImpl( - impl: NotificationsKeyguardViewStateRepositoryImpl - ): NotificationsKeyguardViewStateRepository -} - -@SysUISingleton -class NotificationsKeyguardViewStateRepositoryImpl -@Inject -constructor( - wakeUpCoordinator: NotificationWakeUpCoordinator, -) : NotificationsKeyguardViewStateRepository { - override val areNotificationsFullyHidden: Flow<Boolean> = conflatedCallbackFlow { - val listener = - object : NotificationWakeUpCoordinator.WakeUpListener { - override fun onFullyHiddenChanged(isFullyHidden: Boolean) { - trySend(isFullyHidden) - } - } - trySend(wakeUpCoordinator.notificationsFullyHidden) - wakeUpCoordinator.addListener(listener) - awaitClose { wakeUpCoordinator.removeListener(listener) } - } - - override val isPulseExpanding: Flow<Boolean> = conflatedCallbackFlow { - val listener = - object : NotificationWakeUpCoordinator.WakeUpListener { - override fun onPulseExpandingChanged(isPulseExpanding: Boolean) { - trySend(isPulseExpanding) - } - } - trySend(wakeUpCoordinator.isPulseExpanding()) - wakeUpCoordinator.addListener(listener) - awaitClose { wakeUpCoordinator.removeListener(listener) } - } + val isPulseExpanding = MutableStateFlow(false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt index 73341dbc4999..a6361cbc9f9c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt @@ -15,24 +15,29 @@ */ package com.android.systemui.statusbar.notification.domain.interactor -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOn /** Domain logic pertaining to notifications on the keyguard. */ class NotificationsKeyguardInteractor @Inject constructor( - repository: NotificationsKeyguardViewStateRepository, - @Background backgroundDispatcher: CoroutineDispatcher, + private val repository: NotificationsKeyguardViewStateRepository, ) { /** Is a pulse expansion occurring? */ - val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding.flowOn(backgroundDispatcher) + val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding /** Are notifications fully hidden from view? */ - val areNotificationsFullyHidden: Flow<Boolean> = - repository.areNotificationsFullyHidden.flowOn(backgroundDispatcher) + val areNotificationsFullyHidden: Flow<Boolean> = repository.areNotificationsFullyHidden + + /** Updates whether notifications are fully hidden from view. */ + fun setNotificationsFullyHidden(fullyHidden: Boolean) { + repository.areNotificationsFullyHidden.value = fullyHidden + } + + /** Updates whether a pulse expansion is occurring. */ + fun setPulseExpanding(pulseExpanding: Boolean) { + repository.isPulseExpanding.value = pulseExpanding + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index f6431a2e65ad..7ea9b14353d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -32,6 +32,8 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.util.ContrastColorUtil; +import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.res.R; @@ -58,11 +60,19 @@ public class NotificationBackgroundView extends View implements Dumpable { private int mExpandAnimationWidth = -1; private int mExpandAnimationHeight = -1; private int mDrawableAlpha = 255; + private final ColorStateList mLightColoredStatefulColors; + private final ColorStateList mDarkColoredStatefulColors; + private final int mNormalColor; public NotificationBackgroundView(Context context, AttributeSet attrs) { super(context, attrs); - mDontModifyCorners = getResources().getBoolean( - R.bool.config_clipNotificationsToOutline); + mDontModifyCorners = getResources().getBoolean(R.bool.config_clipNotificationsToOutline); + mLightColoredStatefulColors = getResources().getColorStateList( + R.color.notification_state_color_light); + mDarkColoredStatefulColors = getResources().getColorStateList( + R.color.notification_state_color_dark); + mNormalColor = Utils.getColorAttrDefaultColor(mContext, + com.android.internal.R.attr.materialColorSurfaceContainerHigh); } @Override @@ -122,6 +132,18 @@ public class NotificationBackgroundView extends View implements Dumpable { } /** + * Stateful colors are colors that will overlay on the notification original color when one of + * hover states, pressed states or other similar states is activated. + */ + private void setStatefulColors() { + if (mTintColor != mNormalColor) { + ColorStateList newColor = ContrastColorUtil.isColorDark(mTintColor) + ? mDarkColoredStatefulColors : mLightColoredStatefulColors; + ((GradientDrawable) getStatefulBackgroundLayer().mutate()).setColor(newColor); + } + } + + /** * Sets a background drawable. As we need to change our bounds independently of layout, we need * the notion of a background independently of the regular View background.. */ @@ -149,21 +171,20 @@ public class NotificationBackgroundView extends View implements Dumpable { setCustomBackground(d); } + private Drawable getBaseBackgroundLayer() { + return ((LayerDrawable) mBackground).getDrawable(0); + } + + private Drawable getStatefulBackgroundLayer() { + return ((LayerDrawable) mBackground).getDrawable(1); + } + public void setTint(int tintColor) { - if (tintColor != 0) { - ColorStateList stateList = new ColorStateList(new int[][]{ - new int[]{com.android.internal.R.attr.state_pressed}, - new int[]{com.android.internal.R.attr.state_hovered}, - new int[]{}}, - - new int[]{tintColor, 0, tintColor} - ); - mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP); - mBackground.setTintList(stateList); - } else { - mBackground.setTintList(null); - } + Drawable baseLayer = getBaseBackgroundLayer(); + baseLayer.mutate().setTintMode(PorterDuff.Mode.SRC_ATOP); + baseLayer.setTint(tintColor); mTintColor = tintColor; + setStatefulColors(); invalidate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 805b44cc0673..ea414d2c78d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -4869,10 +4869,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) { ExpandableNotificationRow row = entry.getHeadsUpAnimationView(); - generateHeadsUpAnimation(row, isHeadsUp); - } - - public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed); if (SPEW) { Log.v(TAG, "generateHeadsUpAnimation:" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 2e545126c634..abc04b87f831 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1495,14 +1495,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { return mView.getFirstChildNotGone(); } - private void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) { + public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) { mView.generateHeadsUpAnimation(entry, isHeadsUp); } - public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { - mView.generateHeadsUpAnimation(row, isHeadsUp); - } - public void setMaxTopPadding(int padding) { mView.setMaxTopPadding(padding); } diff --git a/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt b/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt index ac04d31041b6..4f7dce363a2b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt @@ -2,6 +2,7 @@ package com.android.systemui.util import java.lang.ref.SoftReference import java.lang.ref.WeakReference +import java.util.concurrent.atomic.AtomicReference import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -48,3 +49,25 @@ fun <T> softReference(obj: T? = null): ReadWriteProperty<Any?, T?> { } } } + +/** + * Creates a nullable Kotlin idiomatic [AtomicReference]. + * + * Usage: + * ``` + * var atomicReferenceObj: Object? by nullableAtomicReference(null) + * atomicReferenceObj = Object() + * ``` + */ +fun <T> nullableAtomicReference(obj: T? = null): ReadWriteProperty<Any?, T?> { + return object : ReadWriteProperty<Any?, T?> { + val t = AtomicReference(obj) + override fun getValue(thisRef: Any?, property: KProperty<*>): T? { + return t.get() + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { + t.set(value) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java index df5162af70c5..3d724e1caa5d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java @@ -22,12 +22,17 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; +import android.util.IndentingPrintWriter; import android.util.Log; +import androidx.annotation.NonNull; + import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.annotations.WeaklyReferencedCallback; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; @@ -244,6 +249,21 @@ public class ObservableServiceConnection<T> implements ServiceConnection { }); } + void dump(@NonNull PrintWriter pw) { + IndentingPrintWriter ipw = DumpUtilsKt.asIndenting(pw); + ipw.println("ObservableServiceConnection state:"); + DumpUtilsKt.withIncreasedIndent(ipw, () -> { + ipw.println("mServiceIntent: " + mServiceIntent); + ipw.println("mLastDisconnectReason: " + mLastDisconnectReason.orElse(-1)); + ipw.println("Callbacks:"); + DumpUtilsKt.withIncreasedIndent(ipw, () -> { + for (WeakReference<Callback<T>> cbRef : mCallbacks) { + ipw.println(cbRef.get()); + } + }); + }); + } + private void applyToCallbacksLocked(Consumer<Callback<T>> applicator) { final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator(); diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java index 6e19bed49626..9b72eb710588 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java @@ -17,6 +17,7 @@ package com.android.systemui.util.service; import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS; +import static com.android.systemui.util.service.dagger.ObservableServiceModule.DUMPSYS_NAME; import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS; import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS; import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER; @@ -24,9 +25,15 @@ import static com.android.systemui.util.service.dagger.ObservableServiceModule.S import android.util.Log; +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; +import com.android.systemui.dump.DumpManager; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; +import java.io.PrintWriter; + import javax.inject.Inject; import javax.inject.Named; @@ -35,7 +42,7 @@ import javax.inject.Named; * {@link ObservableServiceConnection}. * @param <T> The transformed connection type handled by the service. */ -public class PersistentConnectionManager<T> { +public class PersistentConnectionManager<T> implements Dumpable { private static final String TAG = "PersistentConnManager"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -45,6 +52,8 @@ public class PersistentConnectionManager<T> { private final int mMaxReconnectAttempts; private final int mMinConnectionDuration; private final Observer mObserver; + private final DumpManager mDumpManager; + private final String mDumpsysName; private int mReconnectAttempts = 0; private Runnable mCurrentReconnectCancelable; @@ -89,6 +98,8 @@ public class PersistentConnectionManager<T> { public PersistentConnectionManager( SystemClock clock, DelayableExecutor mainExecutor, + DumpManager dumpManager, + @Named(DUMPSYS_NAME) String dumpsysName, @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection, @Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts, @Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs, @@ -98,6 +109,8 @@ public class PersistentConnectionManager<T> { mMainExecutor = mainExecutor; mConnection = serviceConnection; mObserver = observer; + mDumpManager = dumpManager; + mDumpsysName = TAG + "#" + dumpsysName; mMaxReconnectAttempts = maxReconnectAttempts; mBaseReconnectDelayMs = baseReconnectDelayMs; @@ -108,6 +121,7 @@ public class PersistentConnectionManager<T> { * Begins the {@link PersistentConnectionManager} by connecting to the associated service. */ public void start() { + mDumpManager.registerCriticalDumpable(mDumpsysName, this); mConnection.addCallback(mConnectionCallback); mObserver.addCallback(mObserverCallback); initiateConnectionAttempt(); @@ -120,6 +134,32 @@ public class PersistentConnectionManager<T> { mConnection.removeCallback(mConnectionCallback); mObserver.removeCallback(mObserverCallback); mConnection.unbind(); + mDumpManager.unregisterDumpable(mDumpsysName); + } + + /** + * Add a callback to the {@link ObservableServiceConnection}. + * @param callback The callback to add. + */ + public void addConnectionCallback(ObservableServiceConnection.Callback<T> callback) { + mConnection.addCallback(callback); + } + + /** + * Remove a callback from the {@link ObservableServiceConnection}. + * @param callback The callback to remove. + */ + public void removeConnectionCallback(ObservableServiceConnection.Callback<T> callback) { + mConnection.removeCallback(callback); + } + + @Override + public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mMaxReconnectAttempts: " + mMaxReconnectAttempts); + pw.println("mBaseReconnectDelayMs: " + mBaseReconnectDelayMs); + pw.println("mMinConnectionDuration: " + mMinConnectionDuration); + pw.println("mReconnectAttempts: " + mReconnectAttempts); + mConnection.dump(pw); } private void initiateConnectionAttempt() { diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java index bcf34f833d32..c52c524d1fe8 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java @@ -19,14 +19,14 @@ package com.android.systemui.util.service.dagger; import android.content.res.Resources; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Main; - -import javax.inject.Named; +import com.android.systemui.res.R; import dagger.Module; import dagger.Provides; +import javax.inject.Named; + /** * Module containing components and parameters for * {@link com.android.systemui.util.service.ObservableServiceConnection} @@ -41,6 +41,7 @@ public class ObservableServiceModule { public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms"; public static final String SERVICE_CONNECTION = "service_connection"; public static final String OBSERVER = "observer"; + public static final String DUMPSYS_NAME = "dumpsys_name"; @Provides @Named(MAX_RECONNECT_ATTEMPTS) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 313276727caf..a20658197a8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -152,7 +152,9 @@ import com.android.systemui.statusbar.notification.ConversationNotificationManag import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger; +import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository; import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; +import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -586,6 +588,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha()) .thenReturn(emptyFlow()); + NotificationsKeyguardViewStateRepository notifsKeyguardViewStateRepository = + new NotificationsKeyguardViewStateRepository(); + NotificationsKeyguardInteractor notifsKeyguardInteractor = + new NotificationsKeyguardInteractor(notifsKeyguardViewStateRepository); NotificationWakeUpCoordinator coordinator = new NotificationWakeUpCoordinator( mDumpManager, @@ -596,7 +602,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardBypassController, mDozeParameters, mScreenOffAnimationController, - new NotificationWakeUpCoordinatorLogger(logcatLogBuffer())); + new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()), + notifsKeyguardInteractor); mConfigurationController = new ConfigurationControllerImpl(mContext); PulseExpansionHandler expansionHandler = new PulseExpansionHandler( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 438b33d9afbc..039fef9c1df5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -22,12 +22,14 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.dump.DumpManager +import com.android.systemui.kosmos.Kosmos import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_WAKEUP +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.ScreenOffAnimationController @@ -54,6 +56,8 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule() + private val kosmos = Kosmos() + private val dumpManager: DumpManager = mock() private val headsUpManager: HeadsUpManager = mock() private val statusBarStateController: StatusBarStateController = mock() @@ -100,6 +104,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { dozeParameters, screenOffAnimationController, logger, + kosmos.notificationsKeyguardInteractor, ) statusBarStateCallback = withArgCaptor { verify(statusBarStateController).addCallback(capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt deleted file mode 100644 index 170f651aed91..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2023 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.statusbar.notification.data.repository - -import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule -import com.android.systemui.SysuiTestCase -import com.android.systemui.collectLastValue -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator -import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.mockito.withArgCaptor -import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component -import org.junit.Test -import org.mockito.Mockito.verify - -@SmallTest -class NotificationsKeyguardViewStateRepositoryTest : SysuiTestCase() { - - @SysUISingleton - @Component(modules = [SysUITestModule::class]) - interface TestComponent : SysUITestComponent<NotificationsKeyguardViewStateRepositoryImpl> { - - val mockWakeUpCoordinator: NotificationWakeUpCoordinator - - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - ): TestComponent - } - } - - private val testComponent: TestComponent = - DaggerNotificationsKeyguardViewStateRepositoryTest_TestComponent.factory() - .create(test = this) - - @Test - fun areNotifsFullyHidden_reflectsWakeUpCoordinator() = - testComponent.runTest { - whenever(mockWakeUpCoordinator.notificationsFullyHidden).thenReturn(false) - val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden) - runCurrent() - - assertThat(notifsFullyHidden).isFalse() - - withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) } - .onFullyHiddenChanged(true) - runCurrent() - - assertThat(notifsFullyHidden).isTrue() - } - - @Test - fun isPulseExpanding_reflectsWakeUpCoordinator() = - testComponent.runTest { - whenever(mockWakeUpCoordinator.isPulseExpanding()).thenReturn(false) - val isPulseExpanding by collectLastValue(underTest.isPulseExpanding) - runCurrent() - - assertThat(isPulseExpanding).isFalse() - - withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) } - .onPulseExpandingChanged(true) - runCurrent() - - assertThat(isPulseExpanding).isTrue() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt index bb3113a72e92..3593f5b4963e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt @@ -21,7 +21,6 @@ import com.android.systemui.collectLastValue import com.android.systemui.dagger.SysUISingleton import com.android.systemui.runCurrent import com.android.systemui.runTest -import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component @@ -33,9 +32,6 @@ class NotificationsKeyguardInteractorTest : SysuiTestCase() { @SysUISingleton @Component(modules = [SysUITestModule::class]) interface TestComponent : SysUITestComponent<NotificationsKeyguardInteractor> { - - val repository: FakeNotificationsKeyguardViewStateRepository - @Component.Factory interface Factory { fun create(@BindsInstance test: SysuiTestCase): TestComponent @@ -48,13 +44,13 @@ class NotificationsKeyguardInteractorTest : SysuiTestCase() { @Test fun areNotifsFullyHidden_reflectsRepository() = testComponent.runTest { - repository.setNotificationsFullyHidden(false) + underTest.setNotificationsFullyHidden(false) val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden) runCurrent() assertThat(notifsFullyHidden).isFalse() - repository.setNotificationsFullyHidden(true) + underTest.setNotificationsFullyHidden(true) runCurrent() assertThat(notifsFullyHidden).isTrue() @@ -63,13 +59,13 @@ class NotificationsKeyguardInteractorTest : SysuiTestCase() { @Test fun isPulseExpanding_reflectsRepository() = testComponent.runTest { - repository.setPulseExpanding(false) + underTest.setPulseExpanding(false) val isPulseExpanding by collectLastValue(underTest.isPulseExpanding) runCurrent() assertThat(isPulseExpanding).isFalse() - repository.setPulseExpanding(true) + underTest.setPulseExpanding(true) runCurrent() assertThat(isPulseExpanding).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt index 47feccf4bdcf..7faf5628b40a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt @@ -29,8 +29,8 @@ import com.android.systemui.statusbar.data.repository.NotificationListenerSettin import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore -import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor +import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor import com.android.systemui.statusbar.notification.shared.byIsAmbient import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply import com.android.systemui.statusbar.notification.shared.byIsPulsing @@ -61,7 +61,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() { interface TestComponent : SysUITestComponent<NotificationIconsInteractor> { val activeNotificationListRepository: ActiveNotificationListRepository - val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository + val notificationsKeyguardInteractor: NotificationsKeyguardInteractor @Component.Factory interface Factory { @@ -136,7 +136,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() { fun filteredEntrySet_noPulsing_notifsNotFullyHidden() = testComponent.runTest { val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false)) - keyguardViewStateRepository.setNotificationsFullyHidden(false) + notificationsKeyguardInteractor.setNotificationsFullyHidden(false) assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true) } @@ -144,7 +144,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() { fun filteredEntrySet_noPulsing_notifsFullyHidden() = testComponent.runTest { val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false)) - keyguardViewStateRepository.setNotificationsFullyHidden(true) + notificationsKeyguardInteractor.setNotificationsFullyHidden(true) assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true) } } @@ -161,7 +161,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { val activeNotificationListRepository: ActiveNotificationListRepository val deviceEntryRepository: FakeDeviceEntryRepository - val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository + val notificationsKeyguardInteractor: NotificationsKeyguardInteractor @Component.Factory interface Factory { @@ -222,7 +222,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { testComponent.runTest { val filteredSet by collectLastValue(underTest.aodNotifs) deviceEntryRepository.setBypassEnabled(false) - keyguardViewStateRepository.setNotificationsFullyHidden(false) + notificationsKeyguardInteractor.setNotificationsFullyHidden(false) assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true) } @@ -231,7 +231,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { testComponent.runTest { val filteredSet by collectLastValue(underTest.aodNotifs) deviceEntryRepository.setBypassEnabled(false) - keyguardViewStateRepository.setNotificationsFullyHidden(true) + notificationsKeyguardInteractor.setNotificationsFullyHidden(true) assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true) } @@ -240,7 +240,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { testComponent.runTest { val filteredSet by collectLastValue(underTest.aodNotifs) deviceEntryRepository.setBypassEnabled(true) - keyguardViewStateRepository.setNotificationsFullyHidden(false) + notificationsKeyguardInteractor.setNotificationsFullyHidden(false) assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true) } @@ -249,7 +249,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { testComponent.runTest { val filteredSet by collectLastValue(underTest.aodNotifs) deviceEntryRepository.setBypassEnabled(true) - keyguardViewStateRepository.setNotificationsFullyHidden(true) + notificationsKeyguardInteractor.setNotificationsFullyHidden(true) assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true) } } @@ -266,7 +266,7 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() { val activeNotificationListRepository: ActiveNotificationListRepository val headsUpIconsInteractor: HeadsUpNotificationIconInteractor - val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository + val notificationsKeyguardInteractor: NotificationsKeyguardInteractor val notificationListenerSettingsRepository: NotificationListenerSettingsRepository @Component.Factory diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java index db0139c9b0d1..55c49ee4360d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java @@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -41,6 +42,7 @@ public class PersistentConnectionManagerTest extends SysuiTestCase { private static final int MAX_RETRIES = 5; private static final int RETRY_DELAY_MS = 1000; private static final int CONNECTION_MIN_DURATION_MS = 5000; + private static final String DUMPSYS_NAME = "dumpsys_name"; private FakeSystemClock mFakeClock = new FakeSystemClock(); private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock); @@ -49,8 +51,14 @@ public class PersistentConnectionManagerTest extends SysuiTestCase { private ObservableServiceConnection<Proxy> mConnection; @Mock + private ObservableServiceConnection.Callback<Proxy> mConnectionCallback; + + @Mock private Observer mObserver; + @Mock + private DumpManager mDumpManager; + private static class Proxy { } @@ -63,6 +71,8 @@ public class PersistentConnectionManagerTest extends SysuiTestCase { mConnectionManager = new PersistentConnectionManager<>( mFakeClock, mFakeExecutor, + mDumpManager, + DUMPSYS_NAME, mConnection, MAX_RETRIES, RETRY_DELAY_MS, @@ -154,4 +164,16 @@ public class PersistentConnectionManagerTest extends SysuiTestCase { callbackCaptor.getValue().onSourceChanged(); verify(mConnection).bind(); } + + @Test + public void testAddConnectionCallback() { + mConnectionManager.addConnectionCallback(mConnectionCallback); + verify(mConnection).addCallback(mConnectionCallback); + } + + @Test + public void testRemoveConnectionCallback() { + mConnectionManager.removeConnectionCallback(mConnectionCallback); + verify(mConnection).removeCallback(mConnectionCallback); + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index c6f12e2014b3..397dc1a464bd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -1,15 +1,38 @@ package com.android.systemui.communal.data.repository +import android.content.ComponentName import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch /** Fake implementation of [CommunalWidgetRepository] */ -class FakeCommunalWidgetRepository : CommunalWidgetRepository { +class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) : + CommunalWidgetRepository { private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList()) override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets + private val _widgetAdded = MutableSharedFlow<Int>() + val widgetAdded: Flow<Int> = _widgetAdded + + private var nextWidgetId = 1 fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) { _communalWidgets.value = inventory } + + override fun addWidget( + provider: ComponentName, + priority: Int, + configureWidget: suspend (id: Int) -> Boolean + ) { + coroutineScope.launch { + val id = nextWidgetId++ + if (configureWidget.invoke(id)) { + _widgetAdded.emit(id) + } + } + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt index faacce64b2e4..eb287ee522c0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt @@ -36,7 +36,8 @@ object CommunalInteractorFactory { fun create( testScope: TestScope = TestScope(), communalRepository: FakeCommunalRepository = FakeCommunalRepository(), - widgetRepository: FakeCommunalWidgetRepository = FakeCommunalWidgetRepository(), + widgetRepository: FakeCommunalWidgetRepository = + FakeCommunalWidgetRepository(testScope.backgroundScope), mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(), smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(), tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt index 788e3aa9c41a..1ffc9f4e30b4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt @@ -15,8 +15,6 @@ */ package com.android.systemui.statusbar.notification.data -import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardStateRepositoryModule import dagger.Module -@Module(includes = [FakeNotificationsKeyguardStateRepositoryModule::class]) -object FakeStatusBarNotificationsDataLayerModule +@Module(includes = []) object FakeStatusBarNotificationsDataLayerModule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt deleted file mode 100644 index 5d3cb4db9c7e..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 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.statusbar.notification.data.repository - -import com.android.systemui.dagger.SysUISingleton -import dagger.Binds -import dagger.Module -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow - -@SysUISingleton -class FakeNotificationsKeyguardViewStateRepository @Inject constructor() : - NotificationsKeyguardViewStateRepository { - private val _notificationsFullyHidden = MutableStateFlow(false) - override val areNotificationsFullyHidden: Flow<Boolean> = _notificationsFullyHidden - - private val _isPulseExpanding = MutableStateFlow(false) - override val isPulseExpanding: Flow<Boolean> = _isPulseExpanding - - fun setNotificationsFullyHidden(fullyHidden: Boolean) { - _notificationsFullyHidden.value = fullyHidden - } - - fun setPulseExpanding(expanding: Boolean) { - _isPulseExpanding.value = expanding - } -} - -@Module -interface FakeNotificationsKeyguardStateRepositoryModule { - @Binds - fun bindFake( - fake: FakeNotificationsKeyguardViewStateRepository - ): NotificationsKeyguardViewStateRepository -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt index f2b9da413c22..df7fd94d19b9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt @@ -18,7 +18,5 @@ package com.android.systemui.statusbar.notification.data.repository import com.android.systemui.kosmos.Kosmos -var Kosmos.notificationsKeyguardViewStateRepository: NotificationsKeyguardViewStateRepository by - Kosmos.Fixture { fakeNotificationsKeyguardViewStateRepository } -val Kosmos.fakeNotificationsKeyguardViewStateRepository by - Kosmos.Fixture { FakeNotificationsKeyguardViewStateRepository() } +val Kosmos.notificationsKeyguardViewStateRepository: NotificationsKeyguardViewStateRepository by + Kosmos.Fixture { NotificationsKeyguardViewStateRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt index 432464e86c3f..61a38b864c40 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt @@ -18,13 +18,11 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.kosmos.testDispatcher import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor val Kosmos.notificationsKeyguardInteractor by Fixture { NotificationsKeyguardInteractor( repository = notificationsKeyguardViewStateRepository, - backgroundDispatcher = testDispatcher, ) } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 513c09587026..d96787480cfa 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -142,6 +142,11 @@ public class RavenwoodRule implements TestRule { Assume.assumeFalse(IS_UNDER_RAVENWOOD); } + // Stopgap for http://g/ravenwood/EPAD-N5ntxM + if (description.getMethodName().endsWith("$noRavenwood")) { + Assume.assumeFalse(IS_UNDER_RAVENWOOD); + } + RavenwoodRuleImpl.init(RavenwoodRule.this); try { base.evaluate(); diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java index 085c18622885..7abfecf0e424 100644 --- a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java +++ b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java @@ -42,4 +42,9 @@ public class RavenwoodMinimumTest { public void testIgnored() { throw new RuntimeException("Shouldn't be executed under ravenwood"); } + + @Test + public void testIgnored$noRavenwood() { + throw new RuntimeException("Shouldn't be executed under ravenwood"); + } } diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java index 553ba124402c..7fc1738f3172 100644 --- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java +++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java @@ -16,6 +16,7 @@ package com.android.server.autofill; +import static com.android.server.autofill.Session.REQUEST_ID_KEY; import static com.android.server.autofill.Session.SESSION_ID_KEY; import android.annotation.NonNull; @@ -107,15 +108,16 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal mLastFlag = flag; if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) { Slog.v(TAG, "About to call CredAutofill service as secondary provider"); - addSessionIdToClientState(pendingFillRequest, pendingInlineSuggestionsRequest, id); + addSessionIdAndRequestIdToClientState(pendingFillRequest, + pendingInlineSuggestionsRequest, id); mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client); } else { mRemoteFillService.onFillRequest(pendingFillRequest); } } - private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest, - InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) { + private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest, + InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) { if (pendingFillRequest.getClientState() == null) { pendingFillRequest = new FillRequest(pendingFillRequest.getId(), pendingFillRequest.getFillContexts(), @@ -125,7 +127,8 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal pendingInlineSuggestionsRequest, pendingFillRequest.getDelayedFillIntentSender()); } - pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id); + pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId); + pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId()); return pendingFillRequest; } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 007be05ff929..6a81425c1443 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -234,7 +234,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState new ComponentName("com.android.credentialmanager", "com.android.credentialmanager.autofill.CredentialAutofillService"); - static final String SESSION_ID_KEY = "session_id"; + static final String SESSION_ID_KEY = "autofill_session_id"; + static final String REQUEST_ID_KEY = "autofill_request_id"; final Object mLock; @@ -729,7 +730,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingFillRequest.getFlags(), id, mClient); } else if (mRemoteFillService != null) { if (mIsPrimaryCredential) { - mPendingFillRequest = addSessionIdToClientState(mPendingFillRequest, + mPendingFillRequest = addSessionIdAndRequestIdToClientState(mPendingFillRequest, mPendingInlineSuggestionsRequest, id); mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient); } else { @@ -877,8 +878,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest, - InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) { + private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest, + InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) { if (pendingFillRequest.getClientState() == null) { pendingFillRequest = new FillRequest(pendingFillRequest.getId(), pendingFillRequest.getFillContexts(), @@ -888,7 +889,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pendingInlineSuggestionsRequest, pendingFillRequest.getDelayedFillIntentSender()); } - pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id); + pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId); + pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId()); return pendingFillRequest; } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 19a9239752cd..7a4ac6ac4500 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1356,8 +1356,8 @@ class StorageManagerService extends IStorageManager.Stub final int flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE; for (UserInfo user : users) { - prepareUserStorageInternal(fromVolumeUuid, user.id, user.serialNumber, flags); - prepareUserStorageInternal(toVolumeUuid, user.id, user.serialNumber, flags); + prepareUserStorageInternal(fromVolumeUuid, user.id, flags); + prepareUserStorageInternal(toVolumeUuid, user.id, flags); } } @@ -3231,7 +3231,7 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL) @Override - public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) { + public void createUserStorageKeys(int userId, boolean ephemeral) { super.createUserStorageKeys_enforcePermission(); @@ -3276,8 +3276,7 @@ class StorageManagerService extends IStorageManager.Stub /* Only for use by LockSettingsService */ @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL) @Override - public void unlockCeStorage(@UserIdInt int userId, int serialNumber, byte[] secret) - throws RemoteException { + public void unlockCeStorage(@UserIdInt int userId, byte[] secret) throws RemoteException { super.unlockCeStorage_enforcePermission(); if (StorageManager.isFileEncrypted()) { @@ -3348,25 +3347,25 @@ class StorageManagerService extends IStorageManager.Stub continue; } - prepareUserStorageInternal(vol.fsUuid, user.id, user.serialNumber, flags); + prepareUserStorageInternal(vol.fsUuid, user.id, flags); } } @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL) @Override - public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) { + public void prepareUserStorage(String volumeUuid, int userId, int flags) { super.prepareUserStorage_enforcePermission(); try { - prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags); + prepareUserStorageInternal(volumeUuid, userId, flags); } catch (Exception e) { throw new RuntimeException(e); } } - private void prepareUserStorageInternal(String volumeUuid, int userId, int serialNumber, - int flags) throws Exception { + private void prepareUserStorageInternal(String volumeUuid, int userId, int flags) + throws Exception { try { mVold.prepareUserStorage(volumeUuid, userId, flags); // After preparing user storage, we should check if we should mount data mirror again, diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 72e62c37106d..8ad60e6a0782 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -243,7 +243,7 @@ final class ActivityManagerConstants extends ContentObserver { /** * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}. */ - private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false; + private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = Flags.oomadjusterCorrectnessRewrite(); /** * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index f49e25af79d3..ef7a0e058db0 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -1385,6 +1385,8 @@ public class OomAdjuster { break; } + // TODO: b/319163103 - limit isolated/sandbox trimming to just the processes + // evaluated in the current update. if (app.isolated && psr.numberOfRunningServices() <= 0 && app.getIsolatedEntryPoint() == null) { // If this is an isolated process, there are no services diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 7cc7c517fa9c..5a3fbe9a66ac 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -724,24 +724,13 @@ public class OomAdjusterModernImpl extends OomAdjuster { if (fullUpdate) { assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); - postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); } else { activeProcesses.clear(); activeProcesses.addAll(targetProcesses); assignCachedAdjIfNecessary(activeProcesses); - - for (int i = activeUids.size() - 1; i >= 0; i--) { - final UidRecord uidRec = activeUids.valueAt(i); - uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP); - } - updateUidsLSP(activeUids, nowElapsed); - - for (int i = 0, size = targetProcesses.size(); i < size; i++) { - applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason); - } - activeProcesses.clear(); } + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); targetProcesses.clear(); if (startProfiling) { diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 08b129eeb8e5..2771572edb01 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -39,7 +39,7 @@ import android.app.PendingIntent; import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledAfter; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -49,6 +49,7 @@ import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Build.VERSION_CODES; import android.os.IBinder; import android.os.PowerExemptionManager; import android.os.SystemClock; @@ -94,16 +95,14 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN * (See also android.app.ForegroundServiceTypePolicy) */ @ChangeId - // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE) - @Disabled + @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE) static final long USE_NEW_WIU_LOGIC_FOR_START = 311208629L; /** * Compat ID to enable the new FGS start logic, for capability calculation. */ @ChangeId - // Always enabled - @Disabled + @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE) static final long USE_NEW_WIU_LOGIC_FOR_CAPABILITIES = 313677553L; /** @@ -111,8 +110,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN * the background. */ @ChangeId - // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE) - @Disabled + @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE) static final long USE_NEW_BFSL_LOGIC = 311208749L; final ActivityManagerService ams; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index a1b6f297f287..91d533c73b5d 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -13591,6 +13591,46 @@ public class AudioService extends IAudioService.Stub } } + + /** + * @see AudioManager#shouldNotificationSoundPlay(AudioAttributes) + */ + @android.annotation.EnforcePermission( + android.Manifest.permission.QUERY_AUDIO_STATE) + public boolean shouldNotificationSoundPlay(@NonNull final AudioAttributes aa) { + super.shouldNotificationSoundPlay_enforcePermission(); + Objects.requireNonNull(aa); + + // don't play notifications if the stream volume associated with the + // AudioAttributes of the notification record is 0 (non-zero volume implies + // not silenced by SILENT or VIBRATE ringer mode) + final int stream = AudioAttributes.toLegacyStreamType(aa); + final boolean mutingFromVolume = getStreamVolume(stream) == 0; + if (mutingFromVolume) { + if (DEBUG_VOL) { + Slog.d(TAG, "notification should not play due to muted stream " + stream); + } + return false; + } + + // don't play notifications if there is a user of GAIN_TRANSIENT_EXCLUSIVE audio focus + // and the focus owner is recording + final int uid = mMediaFocusControl.getExclusiveFocusOwnerUid(); + if (uid == -1) { // return value is -1 if focus isn't GAIN_TRANSIENT_EXCLUSIVE + return true; + } + // is the owner of GAIN_TRANSIENT_EXCLUSIVE focus also recording? + final boolean mutingFromFocusAndRecording = mRecordMonitor.isRecordingActiveForUid(uid); + if (mutingFromFocusAndRecording) { + if (DEBUG_VOL) { + Slog.d(TAG, "notification should not play due to exclusive focus owner recording " + + " uid:" + uid); + } + return false; + } + return true; + } + //====================== // Audioserver state dispatch //====================== diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 0df0006c7be3..1376bde2fb71 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -297,6 +297,23 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } /** + * Return the UID of the focus owner that has focus with exclusive focus gain + * @return -1 if nobody has exclusive focus, the UID of the owner otherwise + */ + protected int getExclusiveFocusOwnerUid() { + synchronized (mAudioFocusLock) { + if (mFocusStack.empty()) { + return -1; + } + final FocusRequester owner = mFocusStack.peek(); + if (owner.getGainRequest() != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) { + return -1; + } + return owner.getClientUid(); + } + } + + /** * Send AUDIOFOCUS_LOSS to a specific stack entry. * Note this method is supporting an external API, and is restricted to LOSS in order to * prevent allowing the stack to be in an invalid state (e.g. entry inside stack has focus) diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index c72632fb367d..c2bc1e4f6be2 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -893,7 +893,7 @@ public class SoundDoseHelper { if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC && safeDevicesContains(device)) { soundDose.updateAttenuation( - AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC, + -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC, (newIndex + 5) / 10, device), device); } 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 89b638be3500..89e08c165373 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors; +import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED; + import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; @@ -28,6 +30,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -35,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.modules.expresslog.Counter; import com.android.server.biometrics.BiometricSchedulerProto; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import java.io.PrintWriter; @@ -48,6 +52,7 @@ import java.util.Deque; import java.util.List; import java.util.Locale; import java.util.function.Consumer; +import java.util.function.Supplier; /** * A scheduler for biometric HAL operations. Maintains a queue of {@link BaseClientMonitor} @@ -56,11 +61,16 @@ import java.util.function.Consumer; * * We currently assume (and require) that each biometric sensor have its own instance of a * {@link BiometricScheduler}. + * + * @param <T> Hal instance for starting the user. + * @param <U> Session associated with the current user id. + * + * TODO: (b/304604965) Update thread annotation when FLAGS_DE_HIDL is removed. */ @MainThread -public class BiometricScheduler { +public class BiometricScheduler<T, U> { - private static final String BASE_TAG = "BiometricScheduler"; + private static final String TAG = "BiometricScheduler"; // Number of recent operations to keep in our logs for dumpsys protected static final int LOG_NUM_RECENT_OPERATIONS = 50; @@ -89,30 +99,6 @@ public class BiometricScheduler { @Retention(RetentionPolicy.SOURCE) public @interface SensorType {} - public static @SensorType int sensorTypeFromFingerprintProperties( - @NonNull FingerprintSensorPropertiesInternal props) { - if (props.isAnyUdfpsType()) { - return SENSOR_TYPE_UDFPS; - } - - return SENSOR_TYPE_FP_OTHER; - } - - public static String sensorTypeToString(@SensorType int sensorType) { - switch (sensorType) { - case SENSOR_TYPE_UNKNOWN: - return "Unknown"; - case SENSOR_TYPE_FACE: - return "Face"; - case SENSOR_TYPE_UDFPS: - return "Udfps"; - case SENSOR_TYPE_FP_OTHER: - return "OtherFp"; - default: - return "UnknownUnknown"; - } - } - private static final class CrashState { static final int NUM_ENTRIES = 10; final String timestamp; @@ -145,8 +131,8 @@ public class BiometricScheduler { } } - @NonNull protected final String mBiometricTag; - private final @SensorType int mSensorType; + @SensorType + private final int mSensorType; @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @NonNull private final IBiometricService mBiometricService; @NonNull protected final Handler mHandler; @@ -157,6 +143,43 @@ public class BiometricScheduler { private int mTotalOperationsHandled; private final int mRecentOperationsLimit; @NonNull private final List<Integer> mRecentOperations; + @Nullable private StopUserClient<U> mStopUserClient; + @NonNull private Supplier<Integer> mCurrentUserRetriever; + @Nullable private UserSwitchProvider<T, U> mUserSwitchProvider; + + private class UserSwitchClientCallback implements ClientMonitorCallback { + @NonNull private final BaseClientMonitor mOwner; + + UserSwitchClientCallback(@NonNull BaseClientMonitor owner) { + mOwner = owner; + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { + mHandler.post(() -> { + Slog.d(TAG, "[Client finished] " + clientMonitor + ", success: " + success); + + // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible + // for that the queue will wait indefinitely until the field is cleared. + if (clientMonitor instanceof StopUserClient<?>) { + if (!success) { + Slog.w(TAG, "StopUserClient failed(), is the HAL stuck? " + + "Clearing mStopUserClient"); + } + mStopUserClient = null; + } + if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) { + mCurrentOperation = null; + } else { + // can happen if the hal dies and is usually okay + // do not unset the current operation that may be newer + Slog.w(TAG, "operation is already null or different (reset?): " + + mCurrentOperation); + } + startNextOperationIfIdle(); + }); + } + } // Internal callback, notified when an operation is complete. Notifies the requester // that the operation is complete, before performing internal scheduler work (such as @@ -164,26 +187,26 @@ public class BiometricScheduler { private final ClientMonitorCallback mInternalCallback = new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - Slog.d(getTag(), "[Started] " + clientMonitor); + Slog.d(TAG, "[Started] " + clientMonitor); } @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { mHandler.post(() -> { if (mCurrentOperation == null) { - Slog.e(getTag(), "[Finishing] " + clientMonitor + Slog.e(TAG, "[Finishing] " + clientMonitor + " but current operation is null, success: " + success + ", possible lifecycle bug in clientMonitor implementation?"); return; } if (!mCurrentOperation.isFor(clientMonitor)) { - Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match" + Slog.e(TAG, "[Ignoring Finish] " + clientMonitor + " does not match" + " current: " + mCurrentOperation); return; } - Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success); + Slog.d(TAG, "[Finishing] " + clientMonitor + ", success: " + success); if (mGestureAvailabilityDispatcher != null) { mGestureAvailabilityDispatcher.markSensorActive( @@ -202,13 +225,11 @@ public class BiometricScheduler { }; @VisibleForTesting - public BiometricScheduler(@NonNull String tag, - @NonNull Handler handler, + public BiometricScheduler(@NonNull Handler handler, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull IBiometricService biometricService, int recentOperationsLimit) { - mBiometricTag = tag; mHandler = handler; mSensorType = sensorType; mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher; @@ -219,49 +240,140 @@ public class BiometricScheduler { mRecentOperations = new ArrayList<>(); } + @VisibleForTesting + public BiometricScheduler(@NonNull Handler handler, + @SensorType int sensorType, + @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull IBiometricService biometricService, + int recentOperationsLimit, + @NonNull Supplier<Integer> currentUserRetriever, + @Nullable UserSwitchProvider<T, U> userSwitchProvider) { + mHandler = handler; + mSensorType = sensorType; + mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher; + mPendingOperations = new ArrayDeque<>(); + mBiometricService = biometricService; + mCrashStates = new ArrayDeque<>(); + mRecentOperationsLimit = recentOperationsLimit; + mRecentOperations = new ArrayList<>(); + mCurrentUserRetriever = currentUserRetriever; + mUserSwitchProvider = userSwitchProvider; + } + + public BiometricScheduler(@NonNull Handler handler, + @SensorType int sensorType, + @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull Supplier<Integer> currentUserRetriever, + @NonNull UserSwitchProvider<T, U> userSwitchProvider) { + this(handler, sensorType, gestureAvailabilityDispatcher, + IBiometricService.Stub.asInterface(ServiceManager.getService( + Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS, + currentUserRetriever, userSwitchProvider); + } + /** * Creates a new scheduler. * - * @param tag for the specific instance of the scheduler. Should be unique. * @param sensorType the sensorType that this scheduler is handling. * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures * (such as fingerprint swipe). */ - public BiometricScheduler(@NonNull String tag, - @SensorType int sensorType, + public BiometricScheduler(@SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher, + this(new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS); } + /** + * Returns sensor type for a fingerprint sensor. + */ + @SensorType + public static int sensorTypeFromFingerprintProperties( + @NonNull FingerprintSensorPropertiesInternal props) { + if (props.isAnyUdfpsType()) { + return SENSOR_TYPE_UDFPS; + } + + return SENSOR_TYPE_FP_OTHER; + } + @VisibleForTesting public ClientMonitorCallback getInternalCallback() { return mInternalCallback; } - protected String getTag() { - return BASE_TAG + "/" + mBiometricTag; + protected void startNextOperationIfIdle() { + if (Flags.deHidl()) { + startNextOperation(); + } else { + startNextOperationIfIdleLegacy(); + } + } + + protected void startNextOperation() { + if (mCurrentOperation != null) { + Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation); + return; + } + if (mPendingOperations.isEmpty()) { + Slog.d(TAG, "No operations, returning to idle"); + return; + } + + final int currentUserId = mCurrentUserRetriever.get(); + final int nextUserId = mPendingOperations.getFirst().getTargetUserId(); + + if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) { + startNextOperationIfIdleLegacy(); + } else if (currentUserId == UserHandle.USER_NULL && mUserSwitchProvider != null) { + final BaseClientMonitor startClient = + mUserSwitchProvider.getStartUserClient(nextUserId); + final UserSwitchClientCallback finishedCallback = + new UserSwitchClientCallback(startClient); + + Slog.d(TAG, "[Starting User] " + startClient); + mCurrentOperation = new BiometricSchedulerOperation( + startClient, finishedCallback, STATE_STARTED); + startClient.start(finishedCallback); + } else if (mUserSwitchProvider != null) { + if (mStopUserClient != null) { + Slog.d(TAG, "[Waiting for StopUser] " + mStopUserClient); + } else { + mStopUserClient = mUserSwitchProvider + .getStopUserClient(currentUserId); + final UserSwitchClientCallback finishedCallback = + new UserSwitchClientCallback(mStopUserClient); + + Slog.d(TAG, "[Stopping User] current: " + currentUserId + + ", next: " + nextUserId + ". " + mStopUserClient); + mCurrentOperation = new BiometricSchedulerOperation( + mStopUserClient, finishedCallback, STATE_STARTED); + mStopUserClient.start(finishedCallback); + } + } else { + Slog.e(TAG, "Cannot start next operation."); + } } - protected void startNextOperationIfIdle() { + protected void startNextOperationIfIdleLegacy() { if (mCurrentOperation != null) { - Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation); + Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation); return; } if (mPendingOperations.isEmpty()) { - Slog.d(getTag(), "No operations, returning to idle"); + Slog.d(TAG, "No operations, returning to idle"); return; } mCurrentOperation = mPendingOperations.poll(); - Slog.d(getTag(), "[Polled] " + mCurrentOperation); + Slog.d(TAG, "[Polled] " + mCurrentOperation); // If the operation at the front of the queue has been marked for cancellation, send // ERROR_CANCELED. No need to start this client. if (mCurrentOperation.isMarkedCanceling()) { - Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation); + Slog.d(TAG, "[Now Cancelling] " + mCurrentOperation); mCurrentOperation.cancel(mHandler, mInternalCallback); // Now we wait for the client to send its FinishCallback, which kicks off the next // operation. @@ -289,7 +401,7 @@ public class BiometricScheduler { // Note down current length of queue final int pendingOperationsLength = mPendingOperations.size(); final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast(); - Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation + Slog.e(TAG, "[Unable To Start] " + mCurrentOperation + ". Last pending operation: " + lastOperation); // Then for each operation currently in the pending queue at the time of this @@ -298,10 +410,10 @@ public class BiometricScheduler { for (int i = 0; i < pendingOperationsLength; i++) { final BiometricSchedulerOperation operation = mPendingOperations.pollFirst(); if (operation != null) { - Slog.w(getTag(), "[Aborting Operation] " + operation); + Slog.w(TAG, "[Aborting Operation] " + operation); operation.abort(); } else { - Slog.e(getTag(), "Null operation, index: " + i + Slog.e(TAG, "Null operation, index: " + i + ", expected length: " + pendingOperationsLength); } } @@ -317,9 +429,9 @@ public class BiometricScheduler { mBiometricService.onReadyForAuthentication( mCurrentOperation.getClientMonitor().getRequestId(), cookie); } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when contacting BiometricService", e); + Slog.e(TAG, "Remote exception when contacting BiometricService", e); } - Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation); + Slog.d(TAG, "Waiting for cookie before starting: " + mCurrentOperation); } } @@ -338,14 +450,14 @@ public class BiometricScheduler { */ public void startPreparedClient(int cookie) { if (mCurrentOperation == null) { - Slog.e(getTag(), "Current operation is null"); + Slog.e(TAG, "Current operation is null"); return; } if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) { - Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation); + Slog.d(TAG, "[Started] Prepared client: " + mCurrentOperation); } else { - Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation); + Slog.e(TAG, "[Unable To Start] Prepared client: " + mCurrentOperation); mCurrentOperation = null; startNextOperationIfIdle(); } @@ -374,13 +486,13 @@ public class BiometricScheduler { if (clientMonitor.interruptsPrecedingClients()) { for (BiometricSchedulerOperation operation : mPendingOperations) { if (operation.markCanceling()) { - Slog.d(getTag(), "New client, marking pending op as canceling: " + operation); + Slog.d(TAG, "New client, marking pending op as canceling: " + operation); } } } mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback)); - Slog.d(getTag(), "[Added] " + clientMonitor + Slog.d(TAG, "[Added] " + clientMonitor + ", new queue size: " + mPendingOperations.size()); // If the new operation should interrupt preceding clients, and if the current operation is @@ -389,7 +501,7 @@ public class BiometricScheduler { && mCurrentOperation != null && mCurrentOperation.isInterruptable() && mCurrentOperation.isStarted()) { - Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation); + Slog.d(TAG, "[Cancelling Interruptable]: " + mCurrentOperation); mCurrentOperation.cancel(mHandler, mInternalCallback); } else { startNextOperationIfIdle(); @@ -401,16 +513,16 @@ public class BiometricScheduler { * @param token from the caller, should match the token passed in when requesting enrollment */ public void cancelEnrollment(IBinder token, long requestId) { - Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId); + Slog.d(TAG, "cancelEnrollment, requestId: " + requestId); if (mCurrentOperation != null && canCancelEnrollOperation(mCurrentOperation, token, requestId)) { - Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation); + Slog.d(TAG, "Cancelling enrollment op: " + mCurrentOperation); mCurrentOperation.cancel(mHandler, mInternalCallback); } else { for (BiometricSchedulerOperation operation : mPendingOperations) { if (canCancelEnrollOperation(operation, token, requestId)) { - Slog.d(getTag(), "Cancelling pending enrollment op: " + operation); + Slog.d(TAG, "Cancelling pending enrollment op: " + operation); operation.markCanceling(); } } @@ -423,16 +535,16 @@ public class BiometricScheduler { * @param requestId the id returned when requesting authentication */ public void cancelAuthenticationOrDetection(IBinder token, long requestId) { - Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId); + Slog.d(TAG, "cancelAuthenticationOrDetection, requestId: " + requestId); if (mCurrentOperation != null && canCancelAuthOperation(mCurrentOperation, token, requestId)) { - Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation); + Slog.d(TAG, "Cancelling auth/detect op: " + mCurrentOperation); mCurrentOperation.cancel(mHandler, mInternalCallback); } else { for (BiometricSchedulerOperation operation : mPendingOperations) { if (canCancelAuthOperation(operation, token, requestId)) { - Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation); + Slog.d(TAG, "Cancelling pending auth/detect op: " + operation); operation.markCanceling(); } } @@ -504,11 +616,11 @@ public class BiometricScheduler { mCurrentOperation != null ? mCurrentOperation.toString() : null, pendingOperations); mCrashStates.add(crashState); - Slog.e(getTag(), "Recorded crash state: " + crashState.toString()); + Slog.e(TAG, "Recorded crash state: " + crashState.toString()); } public void dump(PrintWriter pw) { - pw.println("Dump of BiometricScheduler " + getTag()); + pw.println("Dump of BiometricScheduler " + TAG); pw.println("Type: " + mSensorType); pw.println("Current operation: " + mCurrentOperation); pw.println("Pending operations: " + mPendingOperations.size()); @@ -548,7 +660,7 @@ public class BiometricScheduler { * HAL dies. */ public void reset() { - Slog.d(getTag(), "Resetting scheduler"); + Slog.d(TAG, "Resetting scheduler"); mPendingOperations.clear(); mCurrentOperation = null; } @@ -562,11 +674,11 @@ public class BiometricScheduler { return; } for (BiometricSchedulerOperation pendingOperation : mPendingOperations) { - Slog.d(getTag(), "[Watchdog cancelling pending] " + Slog.d(TAG, "[Watchdog cancelling pending] " + pendingOperation.getClientMonitor()); pendingOperation.markCancelingForWatchdog(); } - Slog.d(getTag(), "[Watchdog cancelling current] " + Slog.d(TAG, "[Watchdog cancelling current] " + mCurrentOperation.getClientMonitor()); mCurrentOperation.cancel(mHandler, getInternalCallback()); } @@ -590,9 +702,23 @@ public class BiometricScheduler { /** * Handle stop user client when user switching occurs. */ - public void onUserStopped() {} + public void onUserStopped() { + if (mStopUserClient == null) { + Slog.e(TAG, "Unexpected onUserStopped"); + return; + } + + Slog.d(TAG, "[OnUserStopped]: " + mStopUserClient); + mStopUserClient.onUserStopped(); + mStopUserClient = null; + } public Handler getHandler() { return mHandler; } + + @Nullable + public StopUserClient<?> getStopUserClient() { + return mStopUserClient; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java index e8654dc059a4..e01c4ec76ed2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java @@ -30,7 +30,10 @@ import java.util.function.Supplier; /** * Abstract class for stopping a user. - * @param <T> Interface for stopping the user. + * + * @param <T> Session for stopping the user. It should be either an instance of + * {@link com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession} or + * {@link com.android.server.biometrics.sensors.face.aidl.AidlSession}. */ public abstract class StopUserClient<T> extends HalClientMonitor<T> { diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index 3753bbdba276..7ca10e3f9c98 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -33,10 +33,14 @@ import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDisp /** * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId. + * TODO (b/304604965): Remove class when Flags.FLAG_DE_HIDL is removed. + * + * @param <T> Hal instance for starting the user. + * @param <U> Session associated with the current user id. */ -public class UserAwareBiometricScheduler extends BiometricScheduler { +public class UserAwareBiometricScheduler<T, U> extends BiometricScheduler<T, U> { - private static final String BASE_TAG = "UaBiometricScheduler"; + private static final String TAG = "UaBiometricScheduler"; /** * Interface to retrieve the owner's notion of the current userId. Note that even though @@ -66,13 +70,13 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { mHandler.post(() -> { - Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success); + Slog.d(TAG, "[Client finished] " + clientMonitor + ", success: " + success); // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible // for that the queue will wait indefinitely until the field is cleared. if (clientMonitor instanceof StopUserClient<?>) { if (!success) { - Slog.w(getTag(), "StopUserClient failed(), is the HAL stuck? " + Slog.w(TAG, "StopUserClient failed(), is the HAL stuck? " + "Clearing mStopUserClient"); } mStopUserClient = null; @@ -82,7 +86,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } else { // can happen if the hal dies and is usually okay // do not unset the current operation that may be newer - Slog.w(getTag(), "operation is already null or different (reset?): " + Slog.w(TAG, "operation is already null or different (reset?): " + mCurrentOperation); } startNextOperationIfIdle(); @@ -98,7 +102,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { @NonNull IBiometricService biometricService, @NonNull CurrentUserRetriever currentUserRetriever, @NonNull UserSwitchCallback userSwitchCallback) { - super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService, + super(handler, sensorType, gestureAvailabilityDispatcher, biometricService, LOG_NUM_RECENT_OPERATIONS); mCurrentUserRetriever = currentUserRetriever; @@ -117,18 +121,13 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } @Override - protected String getTag() { - return BASE_TAG + "/" + mBiometricTag; - } - - @Override protected void startNextOperationIfIdle() { if (mCurrentOperation != null) { - Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation); + Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation); return; } if (mPendingOperations.isEmpty()) { - Slog.d(getTag(), "No operations, returning to idle"); + Slog.d(TAG, "No operations, returning to idle"); return; } @@ -143,20 +142,20 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { final ClientFinishedCallback finishedCallback = new ClientFinishedCallback(startClient); - Slog.d(getTag(), "[Starting User] " + startClient); + Slog.d(TAG, "[Starting User] " + startClient); mCurrentOperation = new BiometricSchedulerOperation( startClient, finishedCallback, STATE_STARTED); startClient.start(finishedCallback); } else { if (mStopUserClient != null) { - Slog.d(getTag(), "[Waiting for StopUser] " + mStopUserClient); + Slog.d(TAG, "[Waiting for StopUser] " + mStopUserClient); } else { mStopUserClient = mUserSwitchCallback .getStopUserClient(currentUserId); final ClientFinishedCallback finishedCallback = new ClientFinishedCallback(mStopUserClient); - Slog.d(getTag(), "[Stopping User] current: " + currentUserId + Slog.d(TAG, "[Stopping User] current: " + currentUserId + ", next: " + nextUserId + ". " + mStopUserClient); mCurrentOperation = new BiometricSchedulerOperation( mStopUserClient, finishedCallback, STATE_STARTED); @@ -168,11 +167,11 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { @Override public void onUserStopped() { if (mStopUserClient == null) { - Slog.e(getTag(), "Unexpected onUserStopped"); + Slog.e(TAG, "Unexpected onUserStopped"); return; } - Slog.d(getTag(), "[OnUserStopped]: " + mStopUserClient); + Slog.d(TAG, "[OnUserStopped]: " + mStopUserClient); mStopUserClient.onUserStopped(); mStopUserClient = null; } diff --git a/services/core/java/com/android/server/biometrics/sensors/UserSwitchProvider.java b/services/core/java/com/android/server/biometrics/sensors/UserSwitchProvider.java new file mode 100644 index 000000000000..bc5c55b99ab3 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/UserSwitchProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors; + +import android.annotation.NonNull; + +/** + * Interface to get the appropriate start and stop user clients. + * + * @param <T> Hal instance for starting the user. + * @param <U> Session associated with the current user id. + */ +public interface UserSwitchProvider<T, U> { + @NonNull + StartUserClient<T, U> getStartUserClient(int newUserId); + @NonNull + StopUserClient<U> getStopUserClient(int userId); +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java index af46f441d6ce..3d61f993b7d2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java @@ -53,12 +53,10 @@ public class AidlSession { mAidlResponseHandler = aidlResponseHandler; } - /** The underlying {@link ISession}. */ @NonNull public ISession getSession() { return mSession; } - /** The user id associated with the session. */ public int getUserId() { return mUserId; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 9fa15b8ea3a1..e4ecf1a61155 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -39,6 +39,7 @@ import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; @@ -88,6 +89,8 @@ import java.util.concurrent.atomic.AtomicLong; * Provider for a single instance of the {@link IFace} HAL. */ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { + + private static final String TAG = "FaceProvider"; private static final int ENROLL_TIMEOUT_SEC = 75; private boolean mTestHalEnabled; @@ -159,7 +162,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull BiometricContext biometricContext, boolean resetLockoutRequiresChallenge) { this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher, - biometricContext, null /* daemon */, resetLockoutRequiresChallenge, + biometricContext, null /* daemon */, getHandler(), resetLockoutRequiresChallenge, false /* testHalEnabled */); } @@ -169,13 +172,19 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull BiometricContext biometricContext, - @Nullable IFace daemon, boolean resetLockoutRequiresChallenge, + @Nullable IFace daemon, + @NonNull Handler handler, + boolean resetLockoutRequiresChallenge, boolean testHalEnabled) { mContext = context; mBiometricStateCallback = biometricStateCallback; mHalInstanceName = halInstanceName; mFaceSensors = new SensorList<>(ActivityManager.getService()); - mHandler = new Handler(Looper.getMainLooper()); + if (Flags.deHidl()) { + mHandler = handler; + } else { + mHandler = new Handler(Looper.getMainLooper()); + } mUsageStats = new UsageStats(context); mLockoutResetDispatcher = lockoutResetDispatcher; mActivityTaskManager = ActivityTaskManager.getInstance(); @@ -189,6 +198,13 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { initSensors(resetLockoutRequiresChallenge, props); } + @NonNull + private static Handler getHandler() { + HandlerThread handlerThread = new HandlerThread(TAG); + handlerThread.start(); + return new Handler(handlerThread.getLooper()); + } + private void initAuthenticationBroadcastReceiver() { new AuthenticationStatsBroadcastReceiver( mContext, @@ -230,8 +246,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType, prop.supportsDetectInteraction, prop.halControlsPreview, false /* resetLockoutRequiresChallenge */); - final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, - mContext, mHandler, internalProp, mLockoutResetDispatcher, + final Sensor sensor = new Sensor(this, + mContext, mHandler, internalProp, mBiometricContext); sensor.init(mLockoutResetDispatcher, this); final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL : @@ -250,9 +266,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { private void addHidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) { final int sensorId = prop.commonProps.sensorId; - final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/" + sensorId, this, - mContext, mHandler, prop, mLockoutResetDispatcher, - mBiometricContext, resetLockoutRequiresChallenge, + final Sensor sensor = new HidlToAidlSensorAdapter(this, mContext, mHandler, prop, + mLockoutResetDispatcher, mBiometricContext, resetLockoutRequiresChallenge, () -> { //TODO: update to make this testable scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(), @@ -279,8 +294,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { private void addAidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) { final int sensorId = prop.commonProps.sensorId; - final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, - mHandler, prop, mLockoutResetDispatcher, mBiometricContext, + final Sensor sensor = new Sensor(this, mContext, mHandler, prop, mBiometricContext, resetLockoutRequiresChallenge); sensor.init(mLockoutResetDispatcher, this); final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL : @@ -296,7 +310,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } private String getTag() { - return "FaceProvider/" + mHalInstanceName; + return TAG + "/" + mHalInstanceName; } boolean hasHalInstance() { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java index 0110ae991ae4..e5ae8e336dcb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.biometrics.face.ISession; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -30,10 +31,10 @@ import com.android.server.biometrics.sensors.StopUserClient; import java.util.function.Supplier; -public class FaceStopUserClient extends StopUserClient<AidlSession> { +public class FaceStopUserClient extends StopUserClient<ISession> { private static final String TAG = "FaceStopUserClient"; - public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<ISession> lazyDaemon, @Nullable IBinder token, int userId, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull UserStoppedCallback callback) { @@ -49,7 +50,7 @@ public class FaceStopUserClient extends StopUserClient<AidlSession> { @Override protected void startHalOperation() { try { - getFreshDaemon().getSession().close(); + getFreshDaemon().close(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); getCallback().onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 3e5c59914913..635e79a31a96 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -58,6 +58,7 @@ import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.StartUserClient; import com.android.server.biometrics.sensors.StopUserClient; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; +import com.android.server.biometrics.sensors.UserSwitchProvider; import com.android.server.biometrics.sensors.face.FaceUtils; import java.util.ArrayList; @@ -71,15 +72,16 @@ import java.util.function.Supplier; */ public class Sensor { + private static final String TAG = "Sensor"; + private boolean mTestHalEnabled; - @NonNull private final String mTag; @NonNull private final FaceProvider mProvider; @NonNull private final Context mContext; @NonNull private final IBinder mToken; @NonNull private final Handler mHandler; @NonNull private final FaceSensorPropertiesInternal mSensorProperties; - @NonNull private BiometricScheduler mScheduler; + @NonNull private BiometricScheduler<IFace, ISession> mScheduler; @Nullable private LockoutTracker mLockoutTracker; @NonNull private final Map<Integer, Long> mAuthenticatorIds; @@ -88,11 +90,9 @@ public class Sensor { @NonNull BiometricContext mBiometricContext; - Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, + Sensor(@NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull BiometricContext biometricContext, @Nullable AidlSession session) { - mTag = tag; + @NonNull BiometricContext biometricContext) { mProvider = provider; mContext = context; mToken = new Binder(); @@ -102,105 +102,135 @@ public class Sensor { mAuthenticatorIds = new HashMap<>(); } - Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, - @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull BiometricContext biometricContext) { - this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher, - biometricContext, null); - } - - public Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, + public Sensor(@NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull SensorProps prop, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull BiometricContext biometricContext, boolean resetLockoutRequiresChallenge) { - this(tag, provider, context, handler, + this(provider, context, handler, getFaceSensorPropertiesInternal(prop, resetLockoutRequiresChallenge), - lockoutResetDispatcher, biometricContext, null); + biometricContext); } /** * Initialize biometric scheduler, lockout tracker and session for the sensor. */ - public void init(LockoutResetDispatcher lockoutResetDispatcher, - FaceProvider provider) { - mScheduler = new UserAwareBiometricScheduler(mTag, - BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */, + public void init(@NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull FaceProvider provider) { + if (Flags.deHidl()) { + setScheduler(getBiometricSchedulerForInit(lockoutResetDispatcher, provider)); + } else { + setScheduler(getUserAwareBiometricSchedulerForInit(lockoutResetDispatcher, provider)); + } + mLazySession = () -> mCurrentSession != null ? mCurrentSession : null; + mLockoutTracker = new LockoutCache(); + } + + private BiometricScheduler<IFace, ISession> getBiometricSchedulerForInit( + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull FaceProvider provider) { + return new BiometricScheduler<>(mHandler, + BiometricScheduler.SENSOR_TYPE_FACE, + null /* gestureAvailabilityDispatcher */, () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL, - new UserAwareBiometricScheduler.UserSwitchCallback() { + new UserSwitchProvider<IFace, ISession>() { @NonNull @Override - public StopUserClient<?> getStopUserClient(int userId) { - return new FaceStopUserClient(mContext, mLazySession, mToken, userId, - mSensorProperties.sensorId, - BiometricLogger.ofUnknown(mContext), mBiometricContext, - () -> mCurrentSession = null); + public StopUserClient<ISession> getStopUserClient(int userId) { + return new FaceStopUserClient(mContext, + () -> mLazySession.get().getSession(), mToken, userId, + mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext), + mBiometricContext, () -> mCurrentSession = null); } @NonNull @Override - public StartUserClient<?, ?> getStartUserClient(int newUserId) { + public StartUserClient<IFace, ISession> getStartUserClient(int newUserId) { final int sensorId = mSensorProperties.sensorId; + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutTracker, lockoutResetDispatcher, + mBiometricContext.getAuthSessionCoordinator(), () -> { + }, + new AidlResponseHandler.AidlResponseHandlerCallback() { + @Override + public void onEnrollSuccess() { + mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId, + newUserId); + mProvider.scheduleInvalidationRequest(sensorId, + newUserId); + } - final AidlResponseHandler resultController; - if (Flags.deHidl()) { - resultController = new AidlResponseHandler( - mContext, mScheduler, sensorId, newUserId, - mLockoutTracker, lockoutResetDispatcher, - mBiometricContext.getAuthSessionCoordinator(), () -> {}, - new AidlResponseHandler.AidlResponseHandlerCallback() { - @Override - public void onEnrollSuccess() { - mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId, - newUserId); - mProvider.scheduleInvalidationRequest(sensorId, - newUserId); - } - - @Override - public void onHardwareUnavailable() { - Slog.e(mTag, "Face sensor hardware unavailable."); - mCurrentSession = null; - } - }); - } else { - resultController = new AidlResponseHandler( - mContext, mScheduler, sensorId, newUserId, - mLockoutTracker, lockoutResetDispatcher, - mBiometricContext.getAuthSessionCoordinator(), () -> { - Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); - mCurrentSession = null; - }); - } - - final StartUserClient.UserStartedCallback<ISession> userStartedCallback = - (userIdStarted, newSession, halInterfaceVersion) -> { - Slog.d(mTag, "New session created for user: " - + userIdStarted + " with hal version: " - + halInterfaceVersion); - mCurrentSession = new AidlSession(halInterfaceVersion, - newSession, userIdStarted, resultController); - if (FaceUtils.getLegacyInstance(sensorId) - .isInvalidationInProgress(mContext, userIdStarted)) { - Slog.w(mTag, - "Scheduling unfinished invalidation request for " - + "sensor: " - + sensorId - + ", user: " + userIdStarted); - provider.scheduleInvalidationRequest(sensorId, - userIdStarted); + @Override + public void onHardwareUnavailable() { + Slog.e(TAG, "Face sensor hardware unavailable."); + mCurrentSession = null; } - }; + }); - return new FaceStartUserClient(mContext, provider::getHalInstance, - mToken, newUserId, mSensorProperties.sensorId, - BiometricLogger.ofUnknown(mContext), mBiometricContext, - resultController, userStartedCallback); + return Sensor.this.getStartUserClient(resultController, sensorId, + newUserId, provider); } }); - mLazySession = () -> mCurrentSession != null ? mCurrentSession : null; - mLockoutTracker = new LockoutCache(); + } + + private UserAwareBiometricScheduler<IFace, ISession> getUserAwareBiometricSchedulerForInit( + LockoutResetDispatcher lockoutResetDispatcher, + FaceProvider provider) { + return new UserAwareBiometricScheduler<>(TAG, + BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */, + () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL, + new UserAwareBiometricScheduler.UserSwitchCallback() { + @NonNull + @Override + public StopUserClient<ISession> getStopUserClient(int userId) { + return new FaceStopUserClient(mContext, + () -> mLazySession.get().getSession(), mToken, userId, + mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext), + mBiometricContext, () -> mCurrentSession = null); + } + + @NonNull + @Override + public StartUserClient<IFace, ISession> getStartUserClient(int newUserId) { + final int sensorId = mSensorProperties.sensorId; + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutTracker, lockoutResetDispatcher, + mBiometricContext.getAuthSessionCoordinator(), () -> { + Slog.e(TAG, "Face sensor hardware unavailable."); + mCurrentSession = null; + }); + + return Sensor.this.getStartUserClient(resultController, sensorId, + newUserId, provider); + } + }); + } + + private FaceStartUserClient getStartUserClient(@NonNull AidlResponseHandler resultController, + int sensorId, int newUserId, @NonNull FaceProvider provider) { + final StartUserClient.UserStartedCallback<ISession> userStartedCallback = + (userIdStarted, newSession, halInterfaceVersion) -> { + Slog.d(TAG, "New face session created for user: " + + userIdStarted + " with hal version: " + + halInterfaceVersion); + mCurrentSession = new AidlSession(halInterfaceVersion, + newSession, userIdStarted, resultController); + if (FaceUtils.getLegacyInstance(sensorId) + .isInvalidationInProgress(mContext, userIdStarted)) { + Slog.w(TAG, + "Scheduling unfinished invalidation request for " + + "face sensor: " + + sensorId + + ", user: " + userIdStarted); + provider.scheduleInvalidationRequest(sensorId, + userIdStarted); + } + }; + + return new FaceStartUserClient(mContext, provider::getHalInstance, mToken, newUserId, + mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, + resultController, userStartedCallback); } private static FaceSensorPropertiesInternal getFaceSensorPropertiesInternal(SensorProps prop, @@ -213,13 +243,11 @@ public class Sensor { info.softwareVersion)); } } - final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( + return new FaceSensorPropertiesInternal( prop.commonProps.sensorId, prop.commonProps.sensorStrength, prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType, prop.supportsDetectInteraction, prop.halControlsPreview, resetLockoutRequiresChallenge); - - return internalProp; } @NonNull public Supplier<AidlSession> getLazySession() { @@ -243,7 +271,7 @@ public class Sensor { mProvider, this); } - @NonNull public BiometricScheduler getScheduler() { + @NonNull public BiometricScheduler<IFace, ISession> getScheduler() { return mScheduler; } @@ -259,17 +287,17 @@ public class Sensor { } void setTestHalEnabled(boolean enabled) { - Slog.w(mTag, "setTestHalEnabled: " + enabled); + Slog.w(TAG, "Face setTestHalEnabled: " + enabled); if (enabled != mTestHalEnabled) { // The framework should retrieve a new session from the HAL. try { if (mCurrentSession != null) { // TODO(181984005): This should be scheduled instead of directly invoked - Slog.d(mTag, "Closing old session"); + Slog.d(TAG, "Closing old face session"); mCurrentSession.getSession().close(); } } catch (RemoteException e) { - Slog.e(mTag, "RemoteException", e); + Slog.e(TAG, "RemoteException", e); } mCurrentSession = null; } @@ -308,7 +336,7 @@ public class Sensor { public void onBinderDied() { final BaseClientMonitor client = mScheduler.getCurrentClient(); if (client != null && client.isInterruptable()) { - Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client); + Slog.e(TAG, "Sending face hardware unavailable error for client: " + client); final ErrorConsumer errorConsumer = (ErrorConsumer) client; errorConsumer.onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 46ce0b62e6d5..53376669b387 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -120,7 +120,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull private final FaceSensorPropertiesInternal mSensorProperties; @NonNull private final BiometricStateCallback mBiometricStateCallback; @NonNull private final Context mContext; - @NonNull private final BiometricScheduler mScheduler; + @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler; @NonNull private final Handler mHandler; @NonNull private final Supplier<IBiometricsFace> mLazyDaemon; @NonNull private final LockoutHalImpl mLockoutTracker; @@ -163,14 +163,15 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { private final int mSensorId; @NonNull private final Context mContext; @NonNull private final Handler mHandler; - @NonNull private final BiometricScheduler mScheduler; + @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler; @Nullable private Callback mCallback; @NonNull private final LockoutHalImpl mLockoutTracker; @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher; HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler, - @NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker, + @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler, + @NonNull LockoutHalImpl lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher) { mSensorId = sensorId; mContext = context; @@ -352,7 +353,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull FaceSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull Handler handler, - @NonNull BiometricScheduler scheduler, + @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler, @NonNull BiometricContext biometricContext) { mSensorProperties = sensorProps; mContext = context; @@ -395,7 +396,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull LockoutResetDispatcher lockoutResetDispatcher) { final Handler handler = new Handler(Looper.getMainLooper()); return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher, - handler, new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE, + handler, new BiometricScheduler<>( + BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityTracker */), BiometricContext.getInstance(context)); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java index 6355cb57a752..a004cae4ae26 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.UserInfo; +import android.hardware.biometrics.face.ISession; import android.hardware.biometrics.face.SensorProps; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.os.Handler; @@ -67,8 +68,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe }; private LockoutHalImpl mLockoutTracker; - public HidlToAidlSensorAdapter(@NonNull String tag, - @NonNull FaceProvider provider, + public HidlToAidlSensorAdapter(@NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull SensorProps prop, @@ -76,15 +76,14 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe @NonNull BiometricContext biometricContext, boolean resetLockoutRequiresChallenge, @NonNull Runnable internalCleanupAndGetFeatureRunnable) { - this(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext, + this(provider, context, handler, prop, lockoutResetDispatcher, biometricContext, resetLockoutRequiresChallenge, internalCleanupAndGetFeatureRunnable, new AuthSessionCoordinator(), null /* daemon */, null /* onEnrollSuccessCallback */); } @VisibleForTesting - HidlToAidlSensorAdapter(@NonNull String tag, - @NonNull FaceProvider provider, + HidlToAidlSensorAdapter(@NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull SensorProps prop, @@ -95,7 +94,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe @NonNull AuthSessionCoordinator authSessionCoordinator, @Nullable IBiometricsFace daemon, @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) { - super(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext, + super(provider, context, handler, prop, biometricContext, resetLockoutRequiresChallenge); mInternalCleanupAndGetFeatureRunnable = internalCleanupAndGetFeatureRunnable; mFaceProvider = provider; @@ -124,7 +123,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe @Override public void serviceDied(long cookie) { - Slog.d(TAG, "HAL died."); + Slog.d(TAG, "Face HAL died."); mDaemon = null; } @@ -140,10 +139,12 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe } @Override - public void init(LockoutResetDispatcher lockoutResetDispatcher, - FaceProvider provider) { - setScheduler(new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE, - null /* gestureAvailabilityTracker */)); + public void init(@NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull FaceProvider provider) { + setScheduler(new BiometricScheduler<ISession, AidlSession>(getHandler(), + BiometricScheduler.SENSOR_TYPE_FACE, + null /* gestureAvailabilityTracker */, () -> mCurrentUserId, + null /* userSwitchProvider */)); setLazySession(this::getSession); mLockoutTracker = new LockoutHalImpl(); } @@ -188,7 +189,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe return mDaemon; } - Slog.d(TAG, "Daemon was null, reconnecting, current operation: " + Slog.d(TAG, "Face daemon was null, reconnecting, current operation: " + getScheduler().getCurrentClient()); try { @@ -213,7 +214,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe } @VisibleForTesting void handleUserChanged(int newUserId) { - Slog.d(TAG, "User changed. Current user is " + newUserId); + Slog.d(TAG, "User changed. Current user for face sensor is " + newUserId); mSession = null; mCurrentUserId = newUserId; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java index 5daf2d4fbcf4..fa953615a1d6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java @@ -282,7 +282,7 @@ public class HidlToAidlSessionAdapter implements ISession { @Override public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) { - //Unsupported in HIDL + Slog.e(TAG, "enrollWithOptions unsupported in HIDL"); return null; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java index 8ff105baa981..0d4dac089907 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java @@ -51,12 +51,10 @@ public class AidlSession { mAidlResponseHandler = aidlResponseHandler; } - /** The underlying {@link ISession}. */ @NonNull public ISession getSession() { return mSession; } - /** The user id associated with the session. */ public int getUserId() { return mUserId; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 88a11d9c0ceb..c0388d1c4f21 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -46,6 +46,7 @@ import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Binder; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; @@ -102,6 +103,8 @@ import java.util.stream.Collectors; @SuppressWarnings("deprecation") public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvider { + private static final String TAG = "FingerprintProvider"; + private boolean mTestHalEnabled; @NonNull @@ -172,7 +175,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi boolean resetLockoutRequiresHardwareAuthToken) { this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName, lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, - null /* daemon */, resetLockoutRequiresHardwareAuthToken, + null /* daemon */, getHandler(), resetLockoutRequiresHardwareAuthToken, false /* testHalEnabled */); } @@ -184,6 +187,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull BiometricContext biometricContext, @Nullable IFingerprint daemon, + @NonNull Handler handler, boolean resetLockoutRequiresHardwareAuthToken, boolean testHalEnabled) { mContext = context; @@ -191,7 +195,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mAuthenticationStateListeners = authenticationStateListeners; mHalInstanceName = halInstanceName; mFingerprintSensors = new SensorList<>(ActivityManager.getService()); - mHandler = new Handler(Looper.getMainLooper()); + if (Flags.deHidl()) { + mHandler = handler; + } else { + mHandler = new Handler(Looper.getMainLooper()); + } mLockoutResetDispatcher = lockoutResetDispatcher; mActivityTaskManager = ActivityTaskManager.getInstance(); mTaskStackListener = new BiometricTaskStackListener(); @@ -204,6 +212,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher); } + @NonNull + private static Handler getHandler() { + HandlerThread handlerThread = new HandlerThread(TAG); + handlerThread.start(); + return new Handler(handlerThread.getLooper()); + } + private void initAuthenticationBroadcastReceiver() { new AuthenticationStatsBroadcastReceiver( mContext, @@ -262,11 +277,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi location.sensorLocationY, location.sensorRadius)) .collect(Collectors.toList())); - final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, - mHandler, internalProp, mLockoutResetDispatcher, - gestureAvailabilityDispatcher, mBiometricContext); - sensor.init(gestureAvailabilityDispatcher, - mLockoutResetDispatcher); + final Sensor sensor = new Sensor(this, mContext, mHandler, internalProp, + mBiometricContext); + sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher); final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL : sensor.getLazySession().get().getUserId(); @@ -286,10 +299,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, boolean resetLockoutRequiresHardwareAuthToken) { final int sensorId = prop.commonProps.sensorId; - final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/" - + sensorId, this, mContext, mHandler, - prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher, - mBiometricContext, resetLockoutRequiresHardwareAuthToken, + final Sensor sensor = new HidlToAidlSensorAdapter(this, mContext, mHandler, prop, + mLockoutResetDispatcher, mBiometricContext, resetLockoutRequiresHardwareAuthToken, () -> scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(), null /* callback */)); sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher); @@ -307,14 +318,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi private void addAidlSensors(@NonNull SensorProps prop, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, - List<SensorLocationInternal> workaroundLocations, + @NonNull List<SensorLocationInternal> workaroundLocations, boolean resetLockoutRequiresHardwareAuthToken) { final int sensorId = prop.commonProps.sensorId; - final Sensor sensor = new Sensor(getTag() + "/" + sensorId, - this, mContext, mHandler, - prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher, - mBiometricContext, workaroundLocations, - resetLockoutRequiresHardwareAuthToken); + final Sensor sensor = new Sensor(this, mContext, mHandler, prop, mBiometricContext, + workaroundLocations, resetLockoutRequiresHardwareAuthToken); sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher); final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL : sensor.getLazySession().get().getUserId(); @@ -329,7 +337,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } private String getTag() { - return "FingerprintProvider/" + mHalInstanceName; + return TAG + "/" + mHalInstanceName; } boolean hasHalInstance() { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java index 2cc1879c0851..394f04520531 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -30,11 +31,11 @@ import com.android.server.biometrics.sensors.StopUserClient; import java.util.function.Supplier; -public class FingerprintStopUserClient extends StopUserClient<AidlSession> { +public class FingerprintStopUserClient extends StopUserClient<ISession> { private static final String TAG = "FingerprintStopUserClient"; public FingerprintStopUserClient(@NonNull Context context, - @NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId, + @NonNull Supplier<ISession> lazyDaemon, @Nullable IBinder token, int userId, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull UserStoppedCallback callback) { @@ -50,7 +51,7 @@ public class FingerprintStopUserClient extends StopUserClient<AidlSession> { @Override protected void startHalOperation() { try { - getFreshDaemon().getSession().close(); + getFreshDaemon().close(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); getCallback().onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index dd887bb05c12..af88c62904fc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -59,6 +59,7 @@ import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.StartUserClient; import com.android.server.biometrics.sensors.StopUserClient; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; +import com.android.server.biometrics.sensors.UserSwitchProvider; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; @@ -77,15 +78,16 @@ import java.util.stream.Collectors; @SuppressWarnings("deprecation") public class Sensor { + private static final String TAG = "Sensor"; + private boolean mTestHalEnabled; - @NonNull private final String mTag; @NonNull private final FingerprintProvider mProvider; @NonNull private final Context mContext; @NonNull private final IBinder mToken; @NonNull private final Handler mHandler; @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; - @NonNull private BiometricScheduler mScheduler; + @NonNull private BiometricScheduler<IFingerprint, ISession> mScheduler; @NonNull private LockoutTracker mLockoutTracker; @NonNull private final Map<Integer, Long> mAuthenticatorIds; @NonNull private final BiometricContext mBiometricContext; @@ -93,13 +95,10 @@ public class Sensor { @Nullable AidlSession mCurrentSession; @NonNull private Supplier<AidlSession> mLazySession; - public Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, + public Sensor(@NonNull FingerprintProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull BiometricContext biometricContext, AidlSession session) { - mTag = tag; mProvider = provider; mContext = context; mToken = new Binder(); @@ -110,41 +109,52 @@ public class Sensor { mCurrentSession = session; } - Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context, + Sensor(@NonNull FingerprintProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull BiometricContext biometricContext) { - this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher, - gestureAvailabilityDispatcher, biometricContext, null); + this(provider, context, handler, sensorProperties, + biometricContext, null); } - Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context, + Sensor(@NonNull FingerprintProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull SensorProps sensorProp, - @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull BiometricContext biometricContext, @NonNull List<SensorLocationInternal> workaroundLocation, boolean resetLockoutRequiresHardwareAuthToken) { - this(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp, + this(provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp, workaroundLocation, resetLockoutRequiresHardwareAuthToken), - lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, null); + biometricContext, null); } /** * Initialize biometric scheduler, lockout tracker and session for the sensor. */ - public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher, - LockoutResetDispatcher lockoutResetDispatcher) { - mScheduler = new UserAwareBiometricScheduler(mTag, + public void init(@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull LockoutResetDispatcher lockoutResetDispatcher) { + if (Flags.deHidl()) { + setScheduler(getBiometricSchedulerForInit(gestureAvailabilityDispatcher, + lockoutResetDispatcher)); + } else { + setScheduler(getUserAwareBiometricSchedulerForInit(gestureAvailabilityDispatcher, + lockoutResetDispatcher)); + } + mLockoutTracker = new LockoutCache(); + mLazySession = () -> mCurrentSession != null ? mCurrentSession : null; + } + + private BiometricScheduler<IFingerprint, ISession> getBiometricSchedulerForInit( + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull LockoutResetDispatcher lockoutResetDispatcher) { + return new BiometricScheduler<>(mHandler, BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties), gestureAvailabilityDispatcher, () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL, - new UserAwareBiometricScheduler.UserSwitchCallback() { + new UserSwitchProvider<IFingerprint, ISession>() { @NonNull @Override - public StopUserClient<?> getStopUserClient(int userId) { - return new FingerprintStopUserClient(mContext, mLazySession, mToken, + public StopUserClient<ISession> getStopUserClient(int userId) { + return new FingerprintStopUserClient(mContext, + () -> mLazySession.get().getSession(), mToken, userId, mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, () -> mCurrentSession = null); @@ -152,69 +162,100 @@ public class Sensor { @NonNull @Override - public StartUserClient<?, ?> getStartUserClient(int newUserId) { + public StartUserClient<IFingerprint, ISession> getStartUserClient( + int newUserId) { final int sensorId = mSensorProperties.sensorId; - - final AidlResponseHandler resultController; - - if (Flags.deHidl()) { - resultController = new AidlResponseHandler( - mContext, mScheduler, sensorId, newUserId, - mLockoutTracker, lockoutResetDispatcher, - mBiometricContext.getAuthSessionCoordinator(), () -> {}, - new AidlResponseHandler.AidlResponseHandlerCallback() { - @Override - public void onEnrollSuccess() { - mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId, - newUserId); - mProvider.scheduleInvalidationRequest(sensorId, - newUserId); - } - - @Override - public void onHardwareUnavailable() { - Slog.e(mTag, - "Fingerprint sensor hardware unavailable."); - mCurrentSession = null; - } - }); - } else { - resultController = new AidlResponseHandler( - mContext, mScheduler, sensorId, newUserId, - mLockoutTracker, lockoutResetDispatcher, - mBiometricContext.getAuthSessionCoordinator(), () -> { - Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); - mCurrentSession = null; - }); - } - - final StartUserClient.UserStartedCallback<ISession> userStartedCallback = - (userIdStarted, newSession, halInterfaceVersion) -> { - Slog.d(mTag, "New session created for user: " - + userIdStarted + " with hal version: " - + halInterfaceVersion); - mCurrentSession = new AidlSession(halInterfaceVersion, - newSession, userIdStarted, resultController); - if (FingerprintUtils.getInstance(sensorId) - .isInvalidationInProgress(mContext, userIdStarted)) { - Slog.w(mTag, - "Scheduling unfinished invalidation request for " - + "sensor: " - + sensorId - + ", user: " + userIdStarted); + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutTracker, lockoutResetDispatcher, + mBiometricContext.getAuthSessionCoordinator(), () -> {}, + new AidlResponseHandler.AidlResponseHandlerCallback() { + @Override + public void onEnrollSuccess() { + mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId, + newUserId); mProvider.scheduleInvalidationRequest(sensorId, - userIdStarted); + newUserId); + } + + @Override + public void onHardwareUnavailable() { + Slog.e(TAG, + "Fingerprint sensor hardware unavailable."); + mCurrentSession = null; } - }; + }); - return new FingerprintStartUserClient(mContext, mProvider::getHalInstance, - mToken, newUserId, mSensorProperties.sensorId, + return Sensor.this.getStartUserClient(resultController, sensorId, + newUserId); + } + }); + } + + private UserAwareBiometricScheduler<ISession, AidlSession> + getUserAwareBiometricSchedulerForInit( + GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + LockoutResetDispatcher lockoutResetDispatcher) { + return new UserAwareBiometricScheduler<>(TAG, + BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties), + gestureAvailabilityDispatcher, + () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL, + new UserAwareBiometricScheduler.UserSwitchCallback() { + @NonNull + @Override + public StopUserClient<ISession> getStopUserClient(int userId) { + return new FingerprintStopUserClient(mContext, + () -> mLazySession.get().getSession(), mToken, + userId, mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, - resultController, userStartedCallback); + () -> mCurrentSession = null); + } + + @NonNull + @Override + public StartUserClient<IFingerprint, ISession> getStartUserClient( + int newUserId) { + final int sensorId = mSensorProperties.sensorId; + + final AidlResponseHandler resultController = new AidlResponseHandler( + mContext, mScheduler, sensorId, newUserId, + mLockoutTracker, lockoutResetDispatcher, + mBiometricContext.getAuthSessionCoordinator(), () -> { + Slog.e(TAG, "Fingerprint hardware unavailable."); + mCurrentSession = null; + }); + + return Sensor.this.getStartUserClient(resultController, sensorId, + newUserId); } }); - mLockoutTracker = new LockoutCache(); - mLazySession = () -> mCurrentSession != null ? mCurrentSession : null; + } + + private FingerprintStartUserClient getStartUserClient(AidlResponseHandler resultController, + int sensorId, int newUserId) { + final StartUserClient.UserStartedCallback<ISession> userStartedCallback = + (userIdStarted, newSession, halInterfaceVersion) -> { + Slog.d(TAG, "New fingerprint session created for user: " + + userIdStarted + " with hal version: " + + halInterfaceVersion); + mCurrentSession = new AidlSession(halInterfaceVersion, + newSession, userIdStarted, resultController); + if (FingerprintUtils.getInstance(sensorId) + .isInvalidationInProgress(mContext, userIdStarted)) { + Slog.w(TAG, + "Scheduling unfinished invalidation request for " + + "fingerprint sensor: " + + sensorId + + ", user: " + userIdStarted); + mProvider.scheduleInvalidationRequest(sensorId, + userIdStarted); + } + }; + + return new FingerprintStartUserClient(mContext, mProvider::getHalInstance, + mToken, newUserId, mSensorProperties.sensorId, + BiometricLogger.ofUnknown(mContext), mBiometricContext, + resultController, userStartedCallback); } protected static FingerprintSensorPropertiesInternal getFingerprintSensorPropertiesInternal( @@ -267,7 +308,7 @@ public class Sensor { biometricStateCallback, mProvider, this); } - @NonNull public BiometricScheduler getScheduler() { + @NonNull public BiometricScheduler<IFingerprint, ISession> getScheduler() { return mScheduler; } @@ -283,17 +324,17 @@ public class Sensor { } void setTestHalEnabled(boolean enabled) { - Slog.w(mTag, "setTestHalEnabled: " + enabled); + Slog.w(TAG, "Fingerprint setTestHalEnabled: " + enabled); if (enabled != mTestHalEnabled) { // The framework should retrieve a new session from the HAL. try { if (mCurrentSession != null) { // TODO(181984005): This should be scheduled instead of directly invoked - Slog.d(mTag, "Closing old session"); + Slog.d(TAG, "Closing old fingerprint session"); mCurrentSession.getSession().close(); } } catch (RemoteException e) { - Slog.e(mTag, "RemoteException", e); + Slog.e(TAG, "RemoteException", e); } mCurrentSession = null; } @@ -335,7 +376,7 @@ public class Sensor { public void onBinderDied() { final BaseClientMonitor client = mScheduler.getCurrentClient(); if (client instanceof ErrorConsumer) { - Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client); + Slog.e(TAG, "Sending fingerprint hardware unavailable error for client: " + client); final ErrorConsumer errorConsumer = (ErrorConsumer) client; errorConsumer.onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index d3cecd0e34c7..4accf8f7ff30 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -119,7 +119,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners; private final ActivityTaskManager mActivityTaskManager; @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; - private final BiometricScheduler mScheduler; + private final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler; private final Handler mHandler; private final LockoutResetDispatcher mLockoutResetDispatcher; private final LockoutFrameworkImpl mLockoutTracker; @@ -198,11 +198,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider private final int mSensorId; @NonNull private final Context mContext; @NonNull final Handler mHandler; - @NonNull final BiometricScheduler mScheduler; + @NonNull final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler; @Nullable private Callback mCallback; HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler, - @NonNull BiometricScheduler scheduler) { + @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler) { mSensorId = sensorId; mContext = context; mHandler = handler; @@ -336,7 +336,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @NonNull BiometricStateCallback biometricStateCallback, @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FingerprintSensorPropertiesInternal sensorProps, - @NonNull BiometricScheduler scheduler, + @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull HalResultController controller, @@ -389,8 +389,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - final BiometricScheduler scheduler = - new BiometricScheduler(TAG, + final BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler = + new BiometricScheduler<>( BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps), gestureAvailabilityDispatcher); final HalResultController controller = new HalResultController(sensorProps.sensorId, @@ -533,8 +533,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider private void scheduleUpdateActiveUserWithoutHandler(int targetUserId, boolean force) { final boolean hasEnrolled = !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty(); - final FingerprintUpdateActiveUserClient client = - new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId, + final FingerprintUpdateActiveUserClientLegacy client = + new FingerprintUpdateActiveUserClientLegacy(mContext, mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorProperties.sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 88dae6fcc453..9232e11a05ee 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -140,9 +140,9 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage private static class TestableBiometricScheduler extends BiometricScheduler { @NonNull private Fingerprint21UdfpsMock mFingerprint21; - TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler, + TestableBiometricScheduler( @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher); + super(BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher); } void init(@NonNull Fingerprint21UdfpsMock fingerprint21) { @@ -258,7 +258,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage final Handler handler = new Handler(Looper.getMainLooper()); final TestableBiometricScheduler scheduler = - new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher); + new TestableBiometricScheduler(gestureAvailabilityDispatcher); final MockHalResultController controller = new MockHalResultController(sensorProps.sensorId, context, handler, scheduler); return new Fingerprint21UdfpsMock(context, biometricStateCallback, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java index 5c5b9928f57a..59e64cd06667 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java @@ -18,7 +18,7 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.biometrics.fingerprint.ISession; import android.os.Build; import android.os.Environment; import android.os.RemoteException; @@ -39,8 +39,8 @@ import java.util.function.Supplier; /** * Sets the HAL's current active user, and updates the framework's authenticatorId cache. */ -public class FingerprintUpdateActiveUserClient extends - StartUserClient<IBiometricsFingerprint, AidlSession> { +public class FingerprintUpdateActiveUserClient extends StartUserClient<ISession, + AidlSession> { private static final String TAG = "FingerprintUpdateActiveUserClient"; private static final String FP_DATA_DIR = "fpdata"; @@ -52,19 +52,7 @@ public class FingerprintUpdateActiveUserClient extends private File mDirectory; FingerprintUpdateActiveUserClient(@NonNull Context context, - @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId, - @NonNull String owner, int sensorId, - @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, - @NonNull Supplier<Integer> currentUserId, - boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds, - boolean forceUpdateAuthenticatorId) { - this(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, currentUserId, - hasEnrolledBiometrics, authenticatorIds, forceUpdateAuthenticatorId, - (newUserId, newUser, halInterfaceVersion) -> {}); - } - - FingerprintUpdateActiveUserClient(@NonNull Context context, - @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId, + @NonNull Supplier<ISession> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull Supplier<Integer> currentUserId, @@ -132,9 +120,10 @@ public class FingerprintUpdateActiveUserClient extends try { final int targetId = getTargetUserId(); Slog.d(TAG, "Setting active user: " + targetId); - getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath()); + HidlToAidlSessionAdapter sessionAdapter = (HidlToAidlSessionAdapter) getFreshDaemon(); + sessionAdapter.setActiveGroup(targetId, mDirectory.getAbsolutePath()); mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics - ? getFreshDaemon().getAuthenticatorId() : 0L); + ? sessionAdapter.getAuthenticatorIdForUpdateClient() : 0L); mUserStartedCallback.onUserStarted(targetId, null, 0); mCallback.onClientFinished(this, true /* success */); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java new file mode 100644 index 000000000000..fc85402edef4 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2024 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.fingerprint.hidl; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.os.Build; +import android.os.Environment; +import android.os.RemoteException; +import android.os.SELinux; +import android.util.Slog; + +import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.HalClientMonitor; + +import java.io.File; +import java.util.Map; +import java.util.function.Supplier; + +/** + * TODO(b/304604965): Delete this class once Flags.DE_HIDL is ready for release. + */ +public class FingerprintUpdateActiveUserClientLegacy extends + HalClientMonitor<IBiometricsFingerprint> { + private static final String TAG = "FingerprintUpdateActiveUserClient"; + private static final String FP_DATA_DIR = "fpdata"; + + private final Supplier<Integer> mCurrentUserId; + private final boolean mForceUpdateAuthenticatorId; + private final boolean mHasEnrolledBiometrics; + private final Map<Integer, Long> mAuthenticatorIds; + private File mDirectory; + + FingerprintUpdateActiveUserClientLegacy(@NonNull Context context, + @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId, + @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull Supplier<Integer> currentUserId, + boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds, + boolean forceUpdateAuthenticatorId) { + super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, + 0 /* cookie */, sensorId, logger, biometricContext); + mCurrentUserId = currentUserId; + mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId; + mHasEnrolledBiometrics = hasEnrolledBiometrics; + mAuthenticatorIds = authenticatorIds; + } + + @Override + public void start(@NonNull ClientMonitorCallback callback) { + super.start(callback); + + if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) { + Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning"); + callback.onClientFinished(this, true /* success */); + return; + } + + int firstSdkInt = Build.VERSION.DEVICE_INITIAL_SDK_INT; + if (firstSdkInt < Build.VERSION_CODES.BASE) { + Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be " + + "at least VERSION_CODES.BASE"); + } + File baseDir; + if (firstSdkInt <= Build.VERSION_CODES.O_MR1) { + baseDir = Environment.getUserSystemDirectory(getTargetUserId()); + } else { + baseDir = Environment.getDataVendorDeDirectory(getTargetUserId()); + } + + mDirectory = new File(baseDir, FP_DATA_DIR); + if (!mDirectory.exists()) { + if (!mDirectory.mkdir()) { + Slog.e(TAG, "Cannot make directory: " + mDirectory.getAbsolutePath()); + callback.onClientFinished(this, false /* success */); + return; + } + // Calling mkdir() from this process will create a directory with our + // permissions (inherited from the containing dir). This command fixes + // the label. + if (!SELinux.restorecon(mDirectory)) { + Slog.e(TAG, "Restorecons failed. Directory will have wrong label."); + callback.onClientFinished(this, false /* success */); + return; + } + } + + startHalOperation(); + } + + @Override + public void unableToStart() { + // Nothing to do here + } + + @Override + protected void startHalOperation() { + try { + final int targetId = getTargetUserId(); + Slog.d(TAG, "Setting active user: " + targetId); + getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath()); + mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics + ? getFreshDaemon().getAuthenticatorId() : 0L); + mCallback.onClientFinished(this, true /* success */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to setActiveGroup: " + e); + mCallback.onClientFinished(this, false /* success */); + } + } + + @Override + public int getProtoEnum() { + return BiometricsProto.CM_UPDATE_ACTIVE_USER; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java index 90da74ccaa1c..47fdcdb92a99 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.UserInfo; +import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.os.Handler; @@ -39,7 +40,7 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.StartUserClient; import com.android.server.biometrics.sensors.StopUserClient; -import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; +import com.android.server.biometrics.sensors.UserSwitchProvider; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; @@ -71,37 +72,33 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe } }; - public HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider, - @NonNull Context context, @NonNull Handler handler, + public HidlToAidlSensorAdapter(@NonNull FingerprintProvider provider, + @NonNull Context context, + @NonNull Handler handler, @NonNull SensorProps prop, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull BiometricContext biometricContext, boolean resetLockoutRequiresHardwareAuthToken, @NonNull Runnable internalCleanupRunnable) { - this(tag, provider, context, handler, prop, lockoutResetDispatcher, - gestureAvailabilityDispatcher, biometricContext, + this(provider, context, handler, prop, lockoutResetDispatcher, biometricContext, resetLockoutRequiresHardwareAuthToken, internalCleanupRunnable, new AuthSessionCoordinator(), null /* daemon */, null /* onEnrollSuccessCallback */); } @VisibleForTesting - HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider, + HidlToAidlSensorAdapter(@NonNull FingerprintProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull SensorProps prop, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull BiometricContext biometricContext, boolean resetLockoutRequiresHardwareAuthToken, @NonNull Runnable internalCleanupRunnable, @NonNull AuthSessionCoordinator authSessionCoordinator, @Nullable IBiometricsFingerprint daemon, @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) { - super(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(prop, + super(provider, context, handler, getFingerprintSensorPropertiesInternal(prop, new ArrayList<>(), resetLockoutRequiresHardwareAuthToken), - lockoutResetDispatcher, - gestureAvailabilityDispatcher, biometricContext, null /* session */); mLockoutResetDispatcher = lockoutResetDispatcher; mInternalCleanupRunnable = internalCleanupRunnable; @@ -127,7 +124,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe @Override public void serviceDied(long cookie) { - Slog.d(TAG, "HAL died."); + Slog.d(TAG, "Fingerprint HAL died."); mSession = null; mDaemon = null; } @@ -139,12 +136,12 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe } @Override - public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher, - LockoutResetDispatcher lockoutResetDispatcher) { + public void init(@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull LockoutResetDispatcher lockoutResetDispatcher) { setLazySession(this::getSession); - setScheduler(new UserAwareBiometricScheduler(TAG, + setScheduler(new BiometricScheduler<ISession, AidlSession>(getHandler(), BiometricScheduler.sensorTypeFromFingerprintProperties(getSensorProperties()), - gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchCallback())); + gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchProvider())); mLockoutTracker = new LockoutFrameworkImpl(getContext(), userId -> mLockoutResetDispatcher.notifyLockoutResetCallbacks( getSensorProperties().sensorId), getHandler()); @@ -152,6 +149,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe @Override @Nullable + @VisibleForTesting protected AidlSession getSessionForUser(int userId) { if (mSession != null && mSession.getUserId() == userId) { return mSession; @@ -217,21 +215,18 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe } mDaemon.asBinder().linkToDeath(this, 0 /* flags */); - - Slog.d(TAG, "Fingerprint HAL ready"); - scheduleLoadAuthenticatorIds(); mInternalCleanupRunnable.run(); return mDaemon; } - private UserAwareBiometricScheduler.UserSwitchCallback getUserSwitchCallback() { - return new UserAwareBiometricScheduler.UserSwitchCallback() { + private UserSwitchProvider<ISession, AidlSession> getUserSwitchProvider() { + return new UserSwitchProvider<>() { @NonNull @Override - public StopUserClient<?> getStopUserClient(int userId) { - return new StopUserClient<IBiometricsFingerprint>(getContext(), - HidlToAidlSensorAdapter.this::getIBiometricsFingerprint, + public StopUserClient<AidlSession> getStopUserClient(int userId) { + return new StopUserClient<>(getContext(), + HidlToAidlSensorAdapter.this::getSession, null /* token */, userId, getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()), getBiometricContext(), () -> { @@ -258,7 +253,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe @NonNull @Override - public StartUserClient<?, ?> getStartUserClient(int newUserId) { + public StartUserClient<ISession, AidlSession> getStartUserClient(int newUserId) { return getFingerprintUpdateActiveUserClient(newUserId, false /* forceUpdateAuthenticatorId */); } @@ -268,7 +263,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe private FingerprintUpdateActiveUserClient getFingerprintUpdateActiveUserClient(int newUserId, boolean forceUpdateAuthenticatorIds) { return new FingerprintUpdateActiveUserClient(getContext(), - this::getIBiometricsFingerprint, newUserId, TAG, + () -> getSession().getSession(), newUserId, TAG, getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()), getBiometricContext(), () -> mCurrentUserId, !FingerprintUtils.getInstance(getSensorProperties().sensorId) @@ -290,7 +285,7 @@ public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRe } @VisibleForTesting void handleUserChanged(int newUserId) { - Slog.d(TAG, "User changed. Current user is " + newUserId); + Slog.d(TAG, "User changed. Current user for fingerprint sensor is " + newUserId); mSession = null; mCurrentUserId = newUserId; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java index 2fc00e126354..b469752d49cf 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java @@ -209,6 +209,14 @@ public class HidlToAidlSessionAdapter implements ISession { return null; } + public long getAuthenticatorIdForUpdateClient() throws RemoteException { + return mSession.get().getAuthenticatorId(); + } + + public void setActiveGroup(int userId, String absolutePath) throws RemoteException { + mSession.get().setActiveGroup(userId, absolutePath); + } + private void setCallback(AidlResponseHandler aidlResponseHandler) { mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler); try { diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index bd22e1d21dea..4c4cf6080965 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -16,6 +16,7 @@ package com.android.server.display; +import static com.android.server.display.BrightnessMappingStrategy.INVALID_NITS; import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat; import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat; @@ -567,7 +568,8 @@ public class DisplayDeviceConfig { public static final int DEFAULT_LOW_REFRESH_RATE = 60; - private static final float BRIGHTNESS_DEFAULT = 0.5f; + @VisibleForTesting + static final float BRIGHTNESS_DEFAULT = 0.5f; private static final String ETC_DIR = "etc"; private static final String DISPLAY_CONFIG_DIR = "displayconfig"; private static final String CONFIG_FILE_FORMAT = "display_%s.xml"; @@ -597,8 +599,6 @@ public class DisplayDeviceConfig { // so -2 is used instead private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f; - static final float NITS_INVALID = -1; - // Length of the ambient light horizon used to calculate the long term estimate of ambient // light. private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000; @@ -1031,11 +1031,12 @@ public class DisplayDeviceConfig { /** * Calculates the nits value for the specified backlight value if a mapping exists. * - * @return The mapped nits or {@link #NITS_INVALID} if no mapping exits. + * @return The mapped nits or {@link BrightnessMappingStrategy.INVALID_NITS} if no mapping + * exits. */ public float getNitsFromBacklight(float backlight) { if (mBacklightToNitsSpline == null) { - return NITS_INVALID; + return INVALID_NITS; } backlight = Math.max(backlight, mBacklightMinimum); return mBacklightToNitsSpline.interpolate(backlight); @@ -1061,7 +1062,7 @@ public class DisplayDeviceConfig { float backlight = getBacklightFromBrightness(brightness); float nits = getNitsFromBacklight(backlight); - if (nits == NITS_INVALID) { + if (nits == INVALID_NITS) { return PowerManager.BRIGHTNESS_INVALID; } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 25576ce9efd6..3a6333099b77 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -19,6 +19,8 @@ package com.android.server.display; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.Mode.INVALID_MODE_ID; +import static com.android.server.display.BrightnessMappingStrategy.INVALID_NITS; + import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; @@ -956,8 +958,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { void handleHdrSdrNitsChanged(float displayNits, float sdrNits) { final float newHdrSdrRatio; - if (displayNits != DisplayDeviceConfig.NITS_INVALID - && sdrNits != DisplayDeviceConfig.NITS_INVALID) { + if (displayNits != INVALID_NITS && sdrNits != INVALID_NITS) { // Ensure the ratio stays >= 1.0f as values below that are nonsensical newHdrSdrRatio = Math.max(1.f, displayNits / sdrNits); } else { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 0c2eee5aebbe..ad090829a2f6 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -2118,11 +2118,10 @@ public class LockSettingsService extends ILockSettings.Stub { Slogf.d(TAG, "CE storage for user %d is already unlocked", userId); return; } - final UserInfo userInfo = mUserManager.getUserInfo(userId); final String userType = isUserSecure(userId) ? "secured" : "unsecured"; final byte[] secret = sp.deriveFileBasedEncryptionKey(); try { - mStorageManager.unlockCeStorage(userId, userInfo.serialNumber, secret); + mStorageManager.unlockCeStorage(userId, secret); Slogf.i(TAG, "Unlocked CE storage for %s user %d", userType, userId); } catch (RemoteException e) { Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId); diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index a6f71c29b380..85c4ffe6ac67 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -22,6 +22,7 @@ import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; +import static android.media.audio.Flags.focusExclusiveWithRecording; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; @@ -588,30 +589,41 @@ public final class NotificationAttentionHelper { } private boolean playSound(final NotificationRecord record, Uri soundUri) { + final boolean shouldPlay; + if (focusExclusiveWithRecording()) { + // flagged path + shouldPlay = mAudioManager.shouldNotificationSoundPlay(record.getAudioAttributes()); + } else { + // legacy path + // play notifications if there is no user of exclusive audio focus + // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or + // VIBRATE ringer mode) + shouldPlay = !mAudioManager.isAudioFocusExclusive() + && (mAudioManager.getStreamVolume( + AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0); + } + if (!shouldPlay) { + if (DEBUG) Slog.v(TAG, "Not playing sound " + soundUri + " due to focus/volume"); + return false; + } + boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0; - // play notifications if there is no user of exclusive audio focus - // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or - // VIBRATE ringer mode) - if (!mAudioManager.isAudioFocusExclusive() - && (mAudioManager.getStreamVolume( - AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) { - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); - if (player != null) { - if (DEBUG) { - Slog.v(TAG, "Playing sound " + soundUri + " with attributes " - + record.getAudioAttributes()); - } - player.playAsync(soundUri, record.getSbn().getUser(), looping, - record.getAudioAttributes(), getSoundVolume(record)); - return true; + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + if (DEBUG) { + Slog.v(TAG, "Playing sound " + soundUri + " with attributes " + + record.getAudioAttributes()); } - } catch (RemoteException e) { - Log.e(TAG, "Failed playSound: " + e); - } finally { - Binder.restoreCallingIdentity(identity); + player.playAsync(soundUri, record.getSbn().getUser(), looping, + record.getAudioAttributes(), getSoundVolume(record)); + return true; } + } catch (RemoteException e) { + Log.e(TAG, "Failed playSound: " + e); + } finally { + Binder.restoreCallingIdentity(identity); } return false; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 9ed3559c1389..7aa7b7e1bfc1 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -83,6 +83,7 @@ import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.media.audio.Flags.focusExclusiveWithRecording; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.os.Flags.allowPrivateProfile; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; @@ -9104,27 +9105,40 @@ public class NotificationManagerService extends SystemService { } private boolean playSound(final NotificationRecord record, Uri soundUri) { + final boolean shouldPlay; + if (focusExclusiveWithRecording()) { + // flagged path + shouldPlay = mAudioManager.shouldNotificationSoundPlay(record.getAudioAttributes()); + } else { + // legacy path + // play notifications if there is no user of exclusive audio focus + // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or + // VIBRATE ringer mode) + shouldPlay = !mAudioManager.isAudioFocusExclusive() + && (mAudioManager.getStreamVolume( + AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0); + } + if (!shouldPlay) { + if (DBG) Slog.v(TAG, "Not playing sound " + soundUri + " due to focus/volume"); + return false; + } + boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0; - // play notifications if there is no user of exclusive audio focus - // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or - // VIBRATE ringer mode) - if (!mAudioManager.isAudioFocusExclusive() - && (mAudioManager.getStreamVolume( - AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) { - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); - if (player != null) { - if (DBG) Slog.v(TAG, "Playing sound " + soundUri + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + if (DBG) { + Slog.v(TAG, "Playing sound " + soundUri + " with attributes " + record.getAudioAttributes()); - player.playAsync(soundUri, record.getSbn().getUser(), looping, - record.getAudioAttributes(), 1.0f); - return true; } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); + player.playAsync(soundUri, record.getSbn().getUser(), looping, + record.getAudioAttributes(), 1.0f); + return true; } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); } return false; } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 64d3a20e9281..1786ac53eeab 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -25,6 +25,7 @@ import static android.service.notification.NotificationListenerService.Ranking.U import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Flags; import android.app.KeyguardManager; @@ -167,7 +168,7 @@ public final class NotificationRecord { private boolean mPreChannelsNotification = true; private Uri mSound; private VibrationEffect mVibration; - private AudioAttributes mAttributes; + private @NonNull AudioAttributes mAttributes; private NotificationChannel mChannel; private ArrayList<String> mPeopleOverride; private ArrayList<SnoozeCriterion> mSnoozeCriteria; @@ -334,7 +335,7 @@ public final class NotificationRecord { return vibration; } - private AudioAttributes calculateAttributes() { + private @NonNull AudioAttributes calculateAttributes() { final Notification n = getSbn().getNotification(); AudioAttributes attributes = getChannel().getAudioAttributes(); if (attributes == null) { @@ -1003,7 +1004,7 @@ public final class NotificationRecord { } public boolean isAudioAttributesUsage(int usage) { - return mAttributes != null && mAttributes.getUsage() == usage; + return mAttributes.getUsage() == usage; } /** @@ -1172,7 +1173,7 @@ public final class NotificationRecord { return mVibration; } - public AudioAttributes getAudioAttributes() { + public @NonNull AudioAttributes getAudioAttributes() { return mAttributes; } diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 376b06105b8c..a4af5e71000c 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -27,6 +27,7 @@ import static android.content.pm.ArchivedActivityInfo.drawableToBitmap; import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS; import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION; import static android.content.pm.PackageInstaller.UNARCHIVAL_OK; +import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET; import static android.content.pm.PackageManager.DELETE_ARCHIVE; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; @@ -100,6 +101,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CompletableFuture; /** @@ -210,7 +212,6 @@ public class PackageArchiver { return; } - // TODO(b/278553670) Add special strings for the delete dialog mPm.mInstallerService.uninstall( new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), @@ -264,7 +265,7 @@ public class PackageArchiver { try { // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options. requestUnarchive(packageName, callerPackageName, - getOrCreateUnarchiveIntentSender(userId, packageName), + getOrCreateLauncherListener(userId, packageName), UserHandle.of(userId), false /* showUnarchivalConfirmation= */); } catch (Throwable t) { @@ -329,7 +330,7 @@ public class PackageArchiver { return true; } - private IntentSender getOrCreateUnarchiveIntentSender(int userId, String packageName) { + private IntentSender getOrCreateLauncherListener(int userId, String packageName) { Pair<Integer, String> key = Pair.create(userId, packageName); synchronized (mLauncherIntentSenders) { IntentSender intentSender = mLauncherIntentSenders.get(key); @@ -515,7 +516,6 @@ public class PackageArchiver { /** * Returns true if the app is archivable. */ - // TODO(b/299299569) Exclude system apps public boolean isAppArchivable(@NonNull String packageName, @NonNull UserHandle user) { Objects.requireNonNull(packageName); Objects.requireNonNull(user); @@ -685,15 +685,14 @@ public class PackageArchiver { PackageInstaller.SessionParams.MODE_FULL_INSTALL); sessionParams.setAppPackageName(packageName); sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT; - sessionParams.unarchiveIntentSender = statusReceiver; int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId); // Handles case of repeated unarchival calls for the same package. - // TODO(b/316881759) Allow attaching multiple intentSenders to one session. int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid, sessionParams, userId); if (existingSessionId != PackageInstaller.SessionInfo.INVALID_ID) { + attachListenerToSession(statusReceiver, existingSessionId, userId); return existingSessionId; } @@ -702,12 +701,34 @@ public class PackageArchiver { installerPackage, mContext.getAttributionTag(), installerUid, userId); + attachListenerToSession(statusReceiver, sessionId, userId); + // TODO(b/297358628) Also cleanup sessions upon device restart. mPm.mHandler.postDelayed(() -> mPm.mInstallerService.cleanupDraftIfUnclaimed(sessionId), getUnarchiveForegroundTimeout()); return sessionId; } + private void attachListenerToSession(IntentSender statusReceiver, int existingSessionId, + int userId) { + PackageInstallerSession session = mPm.mInstallerService.getSession(existingSessionId); + int status = session.getUnarchivalStatus(); + // Here we handle a race condition that might happen when an installer reports UNARCHIVAL_OK + // but hasn't created a session yet. Without this the listener would never receive a success + // response. + if (status == UNARCHIVAL_OK) { + notifyUnarchivalListener(UNARCHIVAL_OK, session.getInstallerPackageName(), + session.params.appPackageName, /* requiredStorageBytes= */ 0, + /* userActionIntent= */ null, Set.of(statusReceiver), userId); + return; + } else if (status != UNARCHIVAL_STATUS_UNSET) { + throw new IllegalStateException(TextUtils.formatSimple("Session %s has unarchive status" + + "%s but is still active.", session.sessionId, status)); + } + + session.registerUnarchivalListener(statusReceiver); + } + /** * Returns the icon of an archived app. This is the icon of the main activity of the app. * @@ -883,13 +904,7 @@ public class PackageArchiver { void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName, long requiredStorageBytes, @Nullable PendingIntent userActionIntent, - @Nullable IntentSender unarchiveIntentSender, int userId) { - if (unarchiveIntentSender == null) { - // Maybe this can happen if the installer calls reportUnarchivalStatus twice in quick - // succession. - return; - } - + Set<IntentSender> unarchiveIntentSenders, int userId) { final Intent broadcastIntent = new Intent(); broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName); broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status); @@ -909,15 +924,16 @@ public class PackageArchiver { final BroadcastOptions options = BroadcastOptions.makeBasic(); options.setPendingIntentBackgroundActivityStartMode( MODE_BACKGROUND_ACTIVITY_START_DENIED); - try { - unarchiveIntentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null, - /* handler= */ null, /* requiredPermission= */ null, - options.toBundle()); - } catch (IntentSender.SendIntentException e) { - Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e); - } finally { - synchronized (mLauncherIntentSenders) { - mLauncherIntentSenders.remove(Pair.create(userId, appPackageName)); + for (IntentSender intentSender : unarchiveIntentSenders) { + try { + intentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null, + /* handler= */ null, /* requiredPermission= */ null, options.toBundle()); + } catch (IntentSender.SendIntentException e) { + Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e); + } finally { + synchronized (mLauncherIntentSenders) { + mLauncherIntentSenders.remove(Pair.create(userId, appPackageName)); + } } } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 0a23dfb80f3b..a9118d46b4ba 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1759,26 +1759,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements binderUid, unarchiveId)); } - IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender; - if (unarchiveIntentSender == null) { - throw new IllegalStateException( - TextUtils.formatSimple( - "Unarchival status for ID %s has already been set or a " - + "session has been created for it already by the " - + "caller.", - unarchiveId)); - } - - // Execute expensive calls outside the sync block. - mPm.mHandler.post( - () -> mPackageArchiver.notifyUnarchivalListener(status, - session.getInstallerPackageName(), - session.params.appPackageName, requiredStorageBytes, userActionIntent, - unarchiveIntentSender, userId)); - session.params.unarchiveIntentSender = null; - if (status != UNARCHIVAL_OK) { - Binder.withCleanCallingIdentity(session::abandon); - } + session.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes, + userActionIntent); } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 4adb60c34c52..117d03fd059b 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -22,6 +22,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDAT import static android.content.pm.DataLoaderType.INCREMENTAL; import static android.content.pm.DataLoaderType.STREAMING; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; +import static android.content.pm.PackageInstaller.UNARCHIVAL_OK; +import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET; import static android.content.pm.PackageItemInfo.MAX_SAFE_LABEL_LENGTH; import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE; @@ -65,6 +67,7 @@ import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; @@ -97,6 +100,7 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.PreapprovalDetails; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageInstaller.UnarchivalStatus; import android.content.pm.PackageInstaller.UserActionReason; import android.content.pm.PackageManager; import android.content.pm.PackageManager.PackageInfoFlags; @@ -771,6 +775,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final List<String> mResolvedInstructionSets = new ArrayList<>(); @GuardedBy("mLock") private final List<String> mResolvedNativeLibPaths = new ArrayList<>(); + + @GuardedBy("mLock") + private final Set<IntentSender> mUnarchivalListeners = new ArraySet<>(); + @GuardedBy("mLock") private File mInheritedFilesBase; @GuardedBy("mLock") @@ -796,6 +804,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private int mValidatedTargetSdk = INVALID_TARGET_SDK_VERSION; + @UnarchivalStatus + private int mUnarchivalStatus = UNARCHIVAL_STATUS_UNSET; + private static final FileFilter sAddedApkFilter = new FileFilter() { @Override public boolean accept(File file) { @@ -5088,6 +5099,44 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + void registerUnarchivalListener(IntentSender intentSender) { + synchronized (mLock) { + this.mUnarchivalListeners.add(intentSender); + } + } + + Set<IntentSender> getUnarchivalListeners() { + synchronized (mLock) { + return new ArraySet<>(mUnarchivalListeners); + } + } + + void reportUnarchivalStatus(@UnarchivalStatus int status, int unarchiveId, + long requiredStorageBytes, PendingIntent userActionIntent) { + if (getUnarchivalStatus() != UNARCHIVAL_STATUS_UNSET) { + throw new IllegalStateException( + TextUtils.formatSimple( + "Unarchival status for ID %s has already been set or a session has " + + "been created for it already by the caller.", + unarchiveId)); + } + mUnarchivalStatus = status; + + // Execute expensive calls outside the sync block. + mPm.mHandler.post( + () -> mPm.mInstallerService.mPackageArchiver.notifyUnarchivalListener(status, + getInstallerPackageName(), params.appPackageName, requiredStorageBytes, + userActionIntent, getUnarchivalListeners(), userId)); + if (status != UNARCHIVAL_OK) { + Binder.withCleanCallingIdentity(this::abandon); + } + } + + @UnarchivalStatus + int getUnarchivalStatus() { + return this.mUnarchivalStatus; + } + /** * Free up storage used by this session and its children. * Must not be called on a child session. diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index 7d87d1b27da0..cef3244c9068 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -193,7 +193,7 @@ public final class StorageEventHelper extends StorageEventListener { } try { - sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber, flags); + sm.prepareUserStorage(volumeUuid, user.id, flags); synchronized (mPm.mInstallLock) { appDataHelper.reconcileAppsDataLI(volumeUuid, user.id, flags, true /* migrateAppData */); diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java index 8adb5661ad1d..4c42c2dd0850 100644 --- a/services/core/java/com/android/server/pm/UserDataPreparer.java +++ b/services/core/java/com/android/server/pm/UserDataPreparer.java @@ -92,7 +92,7 @@ class UserDataPreparer { volumeUuid, userId, flags, isNewUser); try { // Prepare CE and/or DE storage. - storage.prepareUserStorage(volumeUuid, userId, userSerial, flags); + storage.prepareUserStorage(volumeUuid, userId, flags); // Ensure that the data directories of a removed user with the same ID are not being // reused. New users must get fresh data directories, to avoid leaking data. diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 75b453184db8..49af4fedb643 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -5136,7 +5136,7 @@ public class UserManagerService extends IUserManager.Stub { t.traceBegin("createUserStorageKeys"); final StorageManager storage = mContext.getSystemService(StorageManager.class); - storage.createUserStorageKeys(userId, userInfo.serialNumber, userInfo.isEphemeral()); + storage.createUserStorageKeys(userId, userInfo.isEphemeral()); t.traceEnd(); // Only prepare DE storage here. CE storage will be prepared later, when the user is diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 5d716fc46f7a..e3eb5b52bc2e 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -1891,7 +1891,8 @@ public class TrustManagerService extends SystemService { }; } - private final PackageMonitor mPackageMonitor = new PackageMonitor() { + @VisibleForTesting + final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override public void onSomePackagesChanged() { refreshAgentList(UserHandle.USER_ALL); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 56bef3335b8b..e1bf8f8aa34a 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2596,6 +2596,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255)); } + if (activityRecord != null) { + change.setActivityComponent(activityRecord.mActivityComponent); + } + change.setRotation(info.mRotation, endRotation); if (info.mSnapshot != null) { change.setSnapshot(info.mSnapshot, info.mSnapshotLuma); diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index dfb5a5758448..fb0729f806b1 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -517,6 +517,8 @@ public final class CredentialManagerService .map(CredentialOption::getType) .collect(Collectors.toList())); + finalizeAndEmitInitialPhaseMetric(session); + if (providerSessions.isEmpty()) { try { callback.onError( @@ -776,6 +778,13 @@ public final class CredentialManagerService providerSessions.forEach(ProviderSession::invokeSession); } + private void finalizeAndEmitInitialPhaseMetric(GetCandidateRequestSession session) { + var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric(); + initMetric.setAutofillSessionId(session.getAutofillSessionId()); + initMetric.setAutofillRequestId(session.getAutofillRequestId()); + finalizeAndEmitInitialPhaseMetric((RequestSession) session); + } + private void finalizeAndEmitInitialPhaseMetric(RequestSession session) { try { var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric(); diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java index d1651713fe03..0f914c32346d 100644 --- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java @@ -49,7 +49,12 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> { private static final String TAG = "GetCandidateRequestSession"; + private static final String SESSION_ID_KEY = "autofill_session_id"; + private static final String REQUEST_ID_KEY = "autofill_request_id"; + private final IAutoFillManagerClient mAutoFillCallback; + private final int mAutofillSessionId; + private final int mAutofillRequestId; public GetCandidateRequestSession( Context context, SessionLifetime sessionCallback, @@ -62,6 +67,8 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ RequestInfo.TYPE_GET, callingAppInfo, enabledProviders, cancellationSignal, 0L); mAutoFillCallback = autoFillCallback; + mAutofillSessionId = request.getData().getInt(SESSION_ID_KEY, -1); + mAutofillRequestId = request.getData().getInt(REQUEST_ID_KEY, -1); } /** @@ -177,4 +184,19 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ Slog.d(TAG, "onFinalResponseReceived"); respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(response)); } + + /** + * Returns autofill session id. Returns -1 if unavailable. + */ + public int getAutofillSessionId() { + return mAutofillSessionId; + } + + /** + * Returns autofill request id. Returns -1 if unavailable. + */ + public int getAutofillRequestId() { + return mAutofillRequestId; + + } } diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index b36de0b03fa8..23aa3742175a 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -426,7 +426,11 @@ public class MetricUtilities { /* per_classtype_counts */ initialPhaseMetric.getUniqueRequestCounts(), /* origin_specified */ - initialPhaseMetric.isOriginSpecified() + initialPhaseMetric.isOriginSpecified(), + /* autofill_session_id */ + initialPhaseMetric.getAutofillSessionId(), + /* autofill_request_id */ + initialPhaseMetric.getAutofillRequestId() ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during initial metric emit: " + e); diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java index 8e965e3e5ba5..8a4e86c440b3 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java @@ -49,6 +49,12 @@ public class InitialPhaseMetric { // Stores the deduped request information, particularly {"req":5} private Map<String, Integer> mRequestCounts = new LinkedHashMap<>(); + // The session id of autofill if the request is from autofill, defaults to -1 + private int mAutofillSessionId = -1; + + // The request id of autofill if the request is from autofill, defaults to -1 + private int mAutofillRequestId = -1; + public InitialPhaseMetric(int sessionIdTrackOne) { mSessionIdCaller = sessionIdTrackOne; @@ -126,6 +132,24 @@ public class InitialPhaseMetric { return mOriginSpecified; } + /* ------ Autofill Integration ------ */ + + public void setAutofillSessionId(int autofillSessionId) { + mAutofillSessionId = autofillSessionId; + } + + public int getAutofillSessionId() { + return mAutofillSessionId; + } + + public void setAutofillRequestId(int autofillRequestId) { + mAutofillRequestId = autofillRequestId; + } + + public int getAutofillRequestId() { + return mAutofillRequestId; + } + /* ------ Unique Request Counts Map Information ------ */ public void setRequestCounts(Map<String, Integer> requestCounts) { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java index e5be4d9aa755..9e11fa2f0bdf 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java @@ -50,7 +50,7 @@ import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; -// atest PackageManagerServiceTest:com.android.server.pm.UserDataPreparerTest +// atest PackageManagerServiceServerTests:com.android.server.pm.UserDataPreparerTest @RunWith(AndroidJUnit4.class) @Presubmit @SmallTest @@ -99,7 +99,7 @@ public class UserDataPreparerTest { systemDeDir.mkdirs(); mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_DE); verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), - eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE)); + eq(StorageManager.FLAG_STORAGE_DE)); verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE)); int serialNumber = UserDataPreparer.getSerialNumber(userDeDir); @@ -116,7 +116,7 @@ public class UserDataPreparerTest { systemCeDir.mkdirs(); mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE); verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), - eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE)); + eq(StorageManager.FLAG_STORAGE_CE)); verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE)); int serialNumber = UserDataPreparer.getSerialNumber(userCeDir); @@ -129,7 +129,7 @@ public class UserDataPreparerTest { public void testPrepareUserData_forNewUser_destroysOnFailure() throws Exception { TEST_USER.lastLoggedInTime = 0; doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock) - .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL), + .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(StorageManager.FLAG_STORAGE_CE)); mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE); verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID), @@ -140,7 +140,7 @@ public class UserDataPreparerTest { public void testPrepareUserData_forExistingUser_doesNotDestroyOnFailure() throws Exception { TEST_USER.lastLoggedInTime = System.currentTimeMillis(); doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock) - .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL), + .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(StorageManager.FLAG_STORAGE_CE)); mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE); verify(mStorageManagerMock, never()).destroyUserStorage(isNull(String.class), diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index e370f5501865..c67e7c5ae61e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -41,6 +41,7 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; import android.os.Temperature; import android.provider.Settings; import android.util.SparseArray; @@ -109,6 +110,43 @@ public final class DisplayDeviceConfigTest { } @Test + public void testDefaultValues() { + when(mResources.getString(com.android.internal.R.string.config_displayLightSensorType)) + .thenReturn("test_light_sensor"); + when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(true); + + mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false, + mFlags); + + assertEquals(DisplayDeviceConfig.BRIGHTNESS_DEFAULT, + mDisplayDeviceConfig.getBrightnessDefault(), ZERO_DELTA); + assertEquals(PowerManager.BRIGHTNESS_MAX, + mDisplayDeviceConfig.getBrightnessRampFastDecrease(), ZERO_DELTA); + assertEquals(PowerManager.BRIGHTNESS_MAX, + mDisplayDeviceConfig.getBrightnessRampFastIncrease(), ZERO_DELTA); + assertEquals(PowerManager.BRIGHTNESS_MAX, + mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), ZERO_DELTA); + assertEquals(PowerManager.BRIGHTNESS_MAX, + mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), ZERO_DELTA); + assertEquals(PowerManager.BRIGHTNESS_MAX, + mDisplayDeviceConfig.getBrightnessRampSlowDecreaseIdle(), ZERO_DELTA); + assertEquals(PowerManager.BRIGHTNESS_MAX, + mDisplayDeviceConfig.getBrightnessRampSlowIncreaseIdle(), ZERO_DELTA); + assertEquals(0, mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis()); + assertEquals(0, mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis()); + assertEquals(0, mDisplayDeviceConfig.getBrightnessRampDecreaseMaxIdleMillis()); + assertEquals(0, mDisplayDeviceConfig.getBrightnessRampIncreaseMaxIdleMillis()); + assertNull(mDisplayDeviceConfig.getNits()); + assertNull(mDisplayDeviceConfig.getBacklight()); + assertEquals(0.3f, mDisplayDeviceConfig.getBacklightFromBrightness(0.3f), ZERO_DELTA); + assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type); + assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name); + assertNull(mDisplayDeviceConfig.getProximitySensor().type); + assertNull(mDisplayDeviceConfig.getProximitySensor().name); + assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable()); + } + + @Test public void testConfigValuesFromDisplayConfig() throws IOException { setupDisplayDeviceConfigFromDisplayConfigFile(); @@ -681,6 +719,7 @@ public final class DisplayDeviceConfigTest { assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type); assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name); + assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable()); assertEquals(brightnessIntToFloat(35), mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA); @@ -807,6 +846,24 @@ public final class DisplayDeviceConfigTest { mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA); } + @Test + public void testIsAutoBrightnessAvailable_EnabledInConfigResource() throws IOException { + when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(true); + + setupDisplayDeviceConfigFromDisplayConfigFile(); + + assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable()); + } + + @Test + public void testIsAutoBrightnessAvailable_DisabledInConfigResource() throws IOException { + when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(false); + + setupDisplayDeviceConfigFromDisplayConfigFile(); + + assertFalse(mDisplayDeviceConfig.isAutoBrightnessAvailable()); + } + private String getValidLuxThrottling() { return "<luxThrottling>\n" + " <brightnessLimitMap>\n" @@ -1176,7 +1233,7 @@ public final class DisplayDeviceConfigTest { + "<nits>" + NITS[2] + "</nits>\n" + "</point>\n" + "</screenBrightnessMap>\n" - + "<autoBrightness>\n" + + "<autoBrightness enabled=\"true\">\n" + "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n" + "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n" + (includeIdleMode ? getRampSpeedsIdle() : "") @@ -1593,6 +1650,7 @@ public final class DisplayDeviceConfigTest { when(mResources.getString(com.android.internal.R.string.config_displayLightSensorType)) .thenReturn("test_light_sensor"); + when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(true); when(mResources.getInteger( R.integer.config_autoBrightnessBrighteningLightDebounce)) diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index f386c3bf6a15..d876dae29798 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -80,10 +80,13 @@ import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.ApplicationExitInfo; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.content.ComponentName; @@ -94,9 +97,11 @@ import android.content.pm.ServiceInfo; import android.os.Build; import android.os.IBinder; import android.os.PowerManagerInternal; +import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.SparseArray; @@ -107,7 +112,9 @@ import com.android.server.wm.WindowProcessController; import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; import java.io.File; @@ -148,12 +155,18 @@ public class MockingOomAdjusterTests { private static final String MOCKAPP5_PROCESSNAME = "test #5"; private static final String MOCKAPP5_PACKAGENAME = "com.android.test.test5"; private static final int MOCKAPP2_UID_OTHER = MOCKAPP2_UID + UserHandle.PER_USER_RANGE; + private static final int MOCKAPP_ISOLATED_UID = Process.FIRST_ISOLATED_UID + 321; + private static final String MOCKAPP_ISOLATED_PROCESSNAME = "isolated test #1"; + private static int sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS; private static Context sContext; private static PackageManagerInternal sPackageManagerInternal; private static ActivityManagerService sService; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @SuppressWarnings("GuardedBy") @BeforeClass public static void setUpOnce() { @@ -220,6 +233,11 @@ public class MockingOomAdjusterTests { } } + @Before + public void setUp() { + mSetFlagsRule.enableFlags(Flags.FLAG_NEW_FGS_RESTRICTION_LOGIC); + } + @AfterClass public static void tearDownOnce() { LocalServices.removeServiceForTest(PackageManagerInternal.class); @@ -286,6 +304,20 @@ public class MockingOomAdjusterTests { } /** + * Run updateOomAdjPendingTargetsLocked(). + * - enqueues all provided processes to the pending list and lru before running + */ + @SuppressWarnings("GuardedBy") + private void updateOomAdjPending(ProcessRecord... apps) { + setProcessesToLru(apps); + for (ProcessRecord app : apps) { + sService.mOomAdjuster.enqueueOomAdjTargetLocked(app); + } + sService.mOomAdjuster.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_NONE); + sService.mProcessList.getLruProcessesLOSP().clear(); + } + + /** * Fix up the pointers in the {@link ProcessRecordNode#mApp}: * because we used the mokito spy objects all over the tests here, but the internal * pointers in the {@link ProcessRecordNode#mApp} actually point to the real object. @@ -519,6 +551,7 @@ public class MockingOomAdjusterTests { sService.mConstants.mShortFgsProcStateExtraWaitDuration = 200_000; ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService); + s.appInfo = new ApplicationInfo(); s.startRequested = true; s.isForeground = true; s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE; @@ -561,6 +594,7 @@ public class MockingOomAdjusterTests { // SHORT_SERVICE, timed out already. s = ServiceRecord.newEmptyInstanceForTest(sService); + s.appInfo = new ApplicationInfo(); s.startRequested = true; s.isForeground = true; s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE; @@ -1079,6 +1113,7 @@ public class MockingOomAdjusterTests { // In order to trick OomAdjuster to think it has a short-service, we need this logic. ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService); + s.appInfo = new ApplicationInfo(); s.startRequested = true; s.isForeground = true; s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE; @@ -1109,6 +1144,7 @@ public class MockingOomAdjusterTests { // In order to trick OomAdjuster to think it has a short-service, we need this logic. ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService); + s.appInfo = new ApplicationInfo(); s.startRequested = true; s.isForeground = true; s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE; @@ -1400,6 +1436,7 @@ public class MockingOomAdjusterTests { // In order to trick OomAdjuster to think it has a short-service, we need this logic. ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService); + s.appInfo = new ApplicationInfo(); s.startRequested = true; s.isForeground = true; s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE; @@ -2651,6 +2688,90 @@ public class MockingOomAdjusterTests { assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); } + @SuppressWarnings("GuardedBy") + @Test + public void testUpdateOomAdj_DoAll_Isolated_stopService() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_ISOLATED_UID, + MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + + setProcessesToLru(app); + ServiceRecord s = makeServiceRecord(app); + s.startRequested = true; + s.lastActivity = SystemClock.uptimeMillis(); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + updateOomAdj(); + assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND); + + app.mServices.stopService(s); + updateOomAdj(); + // isolated process should be killed immediately after service stop. + verify(app).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true); + } + + @SuppressWarnings("GuardedBy") + @Test + public void testUpdateOomAdj_DoPending_Isolated_stopService() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_ISOLATED_UID, + MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + + ServiceRecord s = makeServiceRecord(app); + s.startRequested = true; + s.lastActivity = SystemClock.uptimeMillis(); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + updateOomAdjPending(app); + assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND); + + app.mServices.stopService(s); + updateOomAdjPending(app); + // isolated process should be killed immediately after service stop. + verify(app).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true); + } + + @SuppressWarnings("GuardedBy") + @Test + public void testUpdateOomAdj_DoAll_Isolated_stopServiceWithEntryPoint() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_ISOLATED_UID, + MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + app.setIsolatedEntryPoint("test"); + + setProcessesToLru(app); + ServiceRecord s = makeServiceRecord(app); + s.startRequested = true; + s.lastActivity = SystemClock.uptimeMillis(); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + updateOomAdj(); + assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND); + + app.mServices.stopService(s); + updateOomAdj(); + // isolated process with entry point should not be killed + verify(app, never()).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true); + } + + @SuppressWarnings("GuardedBy") + @Test + public void testUpdateOomAdj_DoPending_Isolated_stopServiceWithEntryPoint() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_ISOLATED_UID, + MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + app.setIsolatedEntryPoint("test"); + + ServiceRecord s = makeServiceRecord(app); + s.startRequested = true; + s.lastActivity = SystemClock.uptimeMillis(); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + updateOomAdjPending(app); + assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND); + + app.mServices.stopService(s); + updateOomAdjPending(app); + // isolated process with entry point should not be killed + verify(app, never()).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true); + } + private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName, String packageName, boolean hasShownUi) { long now = SystemClock.uptimeMillis(); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index e5ecdc478df7..0403c64fc624 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -210,6 +210,9 @@ public class PackageArchiverTest { anyInt())).thenReturn(1); when(mInstallerService.getExistingDraftSessionId(anyInt(), any(), anyInt())).thenReturn( PackageInstaller.SessionInfo.INVALID_ID); + PackageInstallerSession session = mock(PackageInstallerSession.class); + when(mInstallerService.getSession(anyInt())).thenReturn(session); + when(session.getUnarchivalStatus()).thenReturn(PackageInstaller.UNARCHIVAL_STATUS_UNSET); doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class)))) .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(), eq(mUserId)); diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java index 97e94e3c6fc9..37ca09d9fa27 100644 --- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java @@ -47,7 +47,6 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricManager; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; @@ -280,7 +279,7 @@ public class TrustManagerServiceTest { "com.android/.SystemTrustAgent"); addTrustAgent(newAgentComponentName, /* isSystemApp= */ true); - mMockContext.sendPackageChangedBroadcast(newAgentComponentName); + notifyPackageChanged(newAgentComponentName); assertThat(mEnabledTrustAgents).containsExactly(newAgentComponentName); assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName); @@ -299,7 +298,7 @@ public class TrustManagerServiceTest { "com.android/.SystemTrustAgent"); addTrustAgent(newAgentComponentName, /* isSystemApp= */ true); - mMockContext.sendPackageChangedBroadcast(newAgentComponentName); + notifyPackageChanged(newAgentComponentName); assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent); assertThat(mKnownTrustAgents).containsExactly(defaultTrustAgent, newAgentComponentName); @@ -312,7 +311,7 @@ public class TrustManagerServiceTest { "com.user/.UserTrustAgent"); addTrustAgent(newAgentComponentName, /* isSystemApp= */ false); - mMockContext.sendPackageChangedBroadcast(newAgentComponentName); + notifyPackageChanged(newAgentComponentName); assertThat(mEnabledTrustAgents).isEmpty(); assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName); @@ -330,7 +329,7 @@ public class TrustManagerServiceTest { // Simulate user turning off systemTrustAgent2 mLockPatternUtils.setEnabledTrustAgents(List.of(systemTrustAgent1), TEST_USER_ID); - mMockContext.sendPackageChangedBroadcast(systemTrustAgent2); + notifyPackageChanged(systemTrustAgent2); assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1); } @@ -440,11 +439,16 @@ public class TrustManagerServiceTest { permission, PackageManager.PERMISSION_GRANTED); } + private void notifyPackageChanged(ComponentName changedComponent) { + mService.mPackageMonitor.onPackageChanged( + changedComponent.getPackageName(), + UserHandle.of(TEST_USER_ID).getUid(1234), + new String[] { changedComponent.getClassName() }); + } + /** A mock Context that allows the test process to send protected broadcasts. */ private static final class MockContext extends TestableContext { - private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers = - new ArrayList<>(); private final ArrayList<BroadcastReceiver> mUserStartedBroadcastReceivers = new ArrayList<>(); @@ -458,9 +462,6 @@ public class TrustManagerServiceTest { UserHandle user, IntentFilter filter, @Nullable String broadcastPermission, @Nullable Handler scheduler) { - if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) { - mPackageChangedBroadcastReceivers.add(receiver); - } if (filter.hasAction(Intent.ACTION_USER_STARTED)) { mUserStartedBroadcastReceivers.add(receiver); } @@ -473,20 +474,6 @@ public class TrustManagerServiceTest { @Nullable String receiverPermission, @Nullable Bundle options) { } - void sendPackageChangedBroadcast(ComponentName changedComponent) { - Intent intent = new Intent( - Intent.ACTION_PACKAGE_CHANGED, - Uri.fromParts(URI_SCHEME_PACKAGE, - changedComponent.getPackageName(), /* fragment= */ null)) - .putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, - new String[]{changedComponent.getClassName()}) - .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID) - .putExtra(Intent.EXTRA_UID, UserHandle.of(TEST_USER_ID).getUid(1234)); - for (BroadcastReceiver receiver : mPackageChangedBroadcastReceivers) { - receiver.onReceive(this, intent); - } - } - void sendUserStartedBroadcast() { Intent intent = new Intent(Intent.ACTION_USER_STARTED) .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 9b8419021c5c..00450267ee79 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -215,6 +215,16 @@ java_library { } java_library { + name: "mockito-test-utils", + srcs: [ + "utils-mockito/**/*.kt", + ], + static_libs: [ + "mockito-target-minus-junit4", + ], +} + +java_library { name: "servicestests-utils-mockito-extended", srcs: [ "utils/**/*.java", diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 77b1455a2ecc..26934d8e5d6e 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -763,8 +763,7 @@ public class UserControllerTest { mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND); - verify(mInjector.mStorageManagerMock, never()) - .unlockCeStorage(eq(TEST_USER_ID), anyInt(), any()); + verify(mInjector.mStorageManagerMock, never()).unlockCeStorage(eq(TEST_USER_ID), any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index 89299002a227..f7480dea780b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -44,12 +44,18 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricService; +import android.hardware.biometrics.fingerprint.IFingerprint; +import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.Fingerprint; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; @@ -60,6 +66,7 @@ import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.nano.BiometricSchedulerProto; @@ -95,14 +102,39 @@ public class BiometricSchedulerTest { @Rule public final TestableContext mContext = new TestableContext( InstrumentationRegistry.getContext(), null); - private BiometricScheduler mScheduler; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + private BiometricScheduler<IFingerprint, ISession> mScheduler; private IBinder mToken; + private int mCurrentUserId = UserHandle.USER_SYSTEM; + private boolean mShouldFailStopUser = false; + private final List<Integer> mStartedUsers = new ArrayList<>(); + private final StartUserClient.UserStartedCallback<ISession> mUserStartedCallback = + (newUserId, newUser, halInterfaceVersion) -> { + mStartedUsers.add(newUserId); + mCurrentUserId = newUserId; + }; + private int mUsersStoppedCount = 0; + private final StopUserClient.UserStoppedCallback mUserStoppedCallback = + () -> { + mUsersStoppedCount++; + mCurrentUserId = UserHandle.USER_NULL; + }; + private boolean mStartOperationsFinish = true; + private int mStartUserClientCount = 0; @Mock private IBiometricService mBiometricService; @Mock private BiometricContext mBiometricContext; @Mock private AuthSessionCoordinator mAuthSessionCoordinator; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private ISession mSession; + @Mock + private IFingerprint mFingerprint; @Before public void setUp() { @@ -111,9 +143,39 @@ public class BiometricSchedulerTest { when(mAuthSessionCoordinator.getLockoutStateFor(anyInt(), anyInt())).thenReturn( BIOMETRIC_SUCCESS); when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); - mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()), - BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */, - mBiometricService, LOG_NUM_RECENT_OPERATIONS); + if (Flags.deHidl()) { + mScheduler = new BiometricScheduler<>( + new Handler(TestableLooper.get(this).getLooper()), + BiometricScheduler.SENSOR_TYPE_UNKNOWN, + null /* gestureAvailabilityDispatcher */, + mBiometricService, + LOG_NUM_RECENT_OPERATIONS, + () -> mCurrentUserId, + new UserSwitchProvider<IFingerprint, ISession>() { + @NonNull + @Override + public StopUserClient<ISession> getStopUserClient(int userId) { + return new TestStopUserClient(mContext, () -> mSession, mToken, userId, + TEST_SENSOR_ID, mBiometricLogger, mBiometricContext, + mUserStoppedCallback, () -> mShouldFailStopUser); + } + + @NonNull + @Override + public StartUserClient<IFingerprint, ISession> getStartUserClient( + int newUserId) { + mStartUserClientCount++; + return new TestStartUserClient(mContext, () -> mFingerprint, mToken, + newUserId, TEST_SENSOR_ID, mBiometricLogger, mBiometricContext, + mUserStartedCallback, mStartOperationsFinish); + } + }); + } else { + mScheduler = new BiometricScheduler<>( + new Handler(TestableLooper.get(this).getLooper()), + BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */, + mBiometricService, LOG_NUM_RECENT_OPERATIONS); + } } @Test @@ -479,6 +541,7 @@ public class BiometricSchedulerTest { final boolean isEnroll = client instanceof TestEnrollClient; mScheduler.scheduleClientMonitor(client); + waitForIdle(); if (started) { mScheduler.startPreparedClient(client.getCookie()); } @@ -789,6 +852,172 @@ public class BiometricSchedulerTest { assertEquals(1, client1.getFingerprints().size()); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testScheduleOperation_whenNoUser() { + mCurrentUserId = UserHandle.USER_NULL; + + final BaseClientMonitor nextClient = mock(BaseClientMonitor.class); + when(nextClient.getTargetUserId()).thenReturn(0); + + mScheduler.scheduleClientMonitor(nextClient); + waitForIdle(); + + assertThat(mUsersStoppedCount).isEqualTo(0); + assertThat(mStartedUsers).containsExactly(0); + verify(nextClient).start(any()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testScheduleOperation_whenNoUser_notStarted() { + mCurrentUserId = UserHandle.USER_NULL; + mStartOperationsFinish = false; + + final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{ + mock(BaseClientMonitor.class), + mock(BaseClientMonitor.class), + mock(BaseClientMonitor.class) + }; + for (BaseClientMonitor client : nextClients) { + when(client.getTargetUserId()).thenReturn(5); + mScheduler.scheduleClientMonitor(client); + waitForIdle(); + } + + assertThat(mUsersStoppedCount).isEqualTo(0); + assertThat(mStartedUsers).isEmpty(); + assertThat(mStartUserClientCount).isEqualTo(1); + for (BaseClientMonitor client : nextClients) { + verify(client, never()).start(any()); + } + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testScheduleOperation_whenNoUser_notStarted_andReset() { + mCurrentUserId = UserHandle.USER_NULL; + mStartOperationsFinish = false; + + final BaseClientMonitor client = mock(BaseClientMonitor.class); + + when(client.getTargetUserId()).thenReturn(5); + + mScheduler.scheduleClientMonitor(client); + waitForIdle(); + + final TestStartUserClient startUserClient = + (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor(); + mScheduler.reset(); + + assertThat(mScheduler.mCurrentOperation).isNull(); + + final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation( + mock(BaseClientMonitor.class), new ClientMonitorCallback() {}); + mScheduler.mCurrentOperation = fakeOperation; + startUserClient.mCallback.onClientFinished(startUserClient, true); + + assertThat(fakeOperation).isSameInstanceAs(mScheduler.mCurrentOperation); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testScheduleOperation_whenSameUser() { + mCurrentUserId = 10; + + BaseClientMonitor nextClient = mock(BaseClientMonitor.class); + when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId); + + mScheduler.scheduleClientMonitor(nextClient); + + waitForIdle(); + + verify(nextClient).start(any()); + assertThat(mUsersStoppedCount).isEqualTo(0); + assertThat(mStartedUsers).isEmpty(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testScheduleOperation_whenDifferentUser() { + mCurrentUserId = 10; + + final int nextUserId = 11; + BaseClientMonitor nextClient = mock(BaseClientMonitor.class); + when(nextClient.getTargetUserId()).thenReturn(nextUserId); + + mScheduler.scheduleClientMonitor(nextClient); + + waitForIdle(); + assertThat(mUsersStoppedCount).isEqualTo(1); + + waitForIdle(); + assertThat(mStartedUsers).containsExactly(nextUserId); + + waitForIdle(); + verify(nextClient).start(any()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testStartUser_alwaysStartsNextOperation() { + mCurrentUserId = UserHandle.USER_NULL; + + BaseClientMonitor nextClient = mock(BaseClientMonitor.class); + when(nextClient.getTargetUserId()).thenReturn(10); + + mScheduler.scheduleClientMonitor(nextClient); + + waitForIdle(); + verify(nextClient).start(any()); + + // finish first operation + mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */); + waitForIdle(); + + // schedule second operation but swap out the current operation + // before it runs so that it's not current when it's completion callback runs + nextClient = mock(BaseClientMonitor.class); + when(nextClient.getTargetUserId()).thenReturn(11); + mScheduler.scheduleClientMonitor(nextClient); + + waitForIdle(); + verify(nextClient).start(any()); + assertThat(mStartedUsers).containsExactly(10, 11).inOrder(); + assertThat(mUsersStoppedCount).isEqualTo(1); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) + public void testStartUser_failsClearsStopUserClient() { + mCurrentUserId = UserHandle.USER_NULL; + + // When a stop user client fails, check that mStopUserClient + // is set to null to prevent the scheduler from getting stuck. + BaseClientMonitor nextClient = mock(BaseClientMonitor.class); + when(nextClient.getTargetUserId()).thenReturn(10); + + mScheduler.scheduleClientMonitor(nextClient); + + waitForIdle(); + verify(nextClient).start(any()); + + // finish first operation + mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */); + waitForIdle(); + + // schedule second operation but swap out the current operation + // before it runs so that it's not current when it's completion callback runs + nextClient = mock(BaseClientMonitor.class); + when(nextClient.getTargetUserId()).thenReturn(11); + mShouldFailStopUser = true; + mScheduler.scheduleClientMonitor(nextClient); + + waitForIdle(); + assertThat(mStartedUsers).containsExactly(10, 11).inOrder(); + assertThat(mUsersStoppedCount).isEqualTo(0); + assertThat(mScheduler.getStopUserClient()).isEqualTo(null); + } private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception { return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer)); @@ -1069,4 +1298,82 @@ public class BiometricSchedulerTest { return mFingerprints; } } + + private interface StopUserClientShouldFail { + boolean shouldFail(); + } + + private class TestStopUserClient extends StopUserClient<ISession> { + private StopUserClientShouldFail mShouldFailClient; + TestStopUserClient(@NonNull Context context, + @NonNull Supplier<ISession> lazyDaemon, @Nullable IBinder token, int userId, + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, + @NonNull UserStoppedCallback callback, StopUserClientShouldFail shouldFail) { + super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback); + mShouldFailClient = shouldFail; + } + + @Override + protected void startHalOperation() { + + } + + @Override + public void start(@NonNull ClientMonitorCallback callback) { + super.start(callback); + if (mShouldFailClient.shouldFail()) { + getCallback().onClientFinished(this, false /* success */); + // When the above fails, it means that the HAL has died, in this case we + // need to ensure the UserSwitchCallback correctly returns the NULL user handle. + mCurrentUserId = UserHandle.USER_NULL; + } else { + onUserStopped(); + } + } + + @Override + public void unableToStart() { + + } + } + + private static class TestStartUserClient extends StartUserClient<IFingerprint, ISession> { + + @Mock + private ISession mSession; + private final boolean mShouldFinish; + ClientMonitorCallback mCallback; + + TestStartUserClient(@NonNull Context context, + @NonNull Supplier<IFingerprint> lazyDaemon, @Nullable IBinder token, int userId, + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, + @NonNull UserStartedCallback<ISession> callback, boolean shouldFinish) { + super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback); + mShouldFinish = shouldFinish; + } + + @Override + protected void startHalOperation() { + + } + + @Override + public void start(@NonNull ClientMonitorCallback callback) { + super.start(callback); + + mCallback = callback; + if (mShouldFinish) { + mUserStartedCallback.onUserStarted( + getTargetUserId(), mSession, 1 /* halInterfaceVersion */); + callback.onClientFinished(this, true /* success */); + } + } + + @Override + public void unableToStart() { + + } + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java index 8b1a2915820a..772ec8b73393 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java @@ -36,8 +36,10 @@ import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; import android.hardware.biometrics.face.SensorProps; import android.hardware.face.HidlFaceSensorConfig; +import android.os.Handler; import android.os.RemoteException; import android.os.UserManager; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -88,14 +90,11 @@ public class FaceProviderTest { @Mock private BiometricStateCallback mBiometricStateCallback; + private final TestLooper mLooper = new TestLooper(); private SensorProps[] mSensorProps; private LockoutResetDispatcher mLockoutResetDispatcher; private FaceProvider mFaceProvider; - private static void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - } - @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); @@ -121,7 +120,8 @@ public class FaceProviderTest { mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext, - mDaemon, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */); + mDaemon, new Handler(mLooper.getLooper()), + false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */); } @Test @@ -156,6 +156,7 @@ public class FaceProviderTest { mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback, hidlFaceSensorConfig, TAG, mLockoutResetDispatcher, mBiometricContext, mDaemon, + new Handler(mLooper.getLooper()), true /* resetLockoutRequiresChallenge */, true /* testHalEnabled */); @@ -210,4 +211,12 @@ public class FaceProviderTest { assertEquals(0, scheduler.getCurrentPendingCount()); } } + + private void waitForIdle() { + if (Flags.deHidl()) { + mLooper.dispatchAll(); + } else { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index e7f7195588ff..fe9cd4353603 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.common.CommonProps; +import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; import android.hardware.biometrics.face.SensorProps; import android.hardware.face.FaceSensorPropertiesInternal; @@ -40,6 +41,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; @@ -49,6 +51,7 @@ import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; +import com.android.server.biometrics.sensors.UserSwitchProvider; import org.junit.Before; import org.junit.Test; @@ -74,6 +77,8 @@ public class SensorTest { @Mock private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback; @Mock + private UserSwitchProvider<IFace, ISession> mUserSwitchProvider; + @Mock private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; @@ -84,16 +89,16 @@ public class SensorTest { @Mock private AuthSessionCoordinator mAuthSessionCoordinator; @Mock - FaceProvider mFaceProvider; + private FaceProvider mFaceProvider; @Mock - BaseClientMonitor mClientMonitor; + private BaseClientMonitor mClientMonitor; @Mock - AidlSession mCurrentSession; + private AidlSession mCurrentSession; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); - private UserAwareBiometricScheduler mScheduler; + private BiometricScheduler<IFace, ISession> mScheduler; private AidlResponseHandler mHalCallback; @Before @@ -101,16 +106,26 @@ public class SensorTest { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); - when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); - mScheduler = new UserAwareBiometricScheduler(TAG, - new Handler(mLooper.getLooper()), - BiometricScheduler.SENSOR_TYPE_FACE, - null /* gestureAvailabilityDispatcher */, - mBiometricService, - () -> USER_ID, - mUserSwitchCallback); + if (Flags.deHidl()) { + mScheduler = new BiometricScheduler<>( + new Handler(mLooper.getLooper()), + BiometricScheduler.SENSOR_TYPE_FACE, + null /* gestureAvailabilityDispatcher */, + mBiometricService, + 2 /* recentOperationsLimit */, + () -> USER_ID, + mUserSwitchProvider); + } else { + mScheduler = new UserAwareBiometricScheduler<>(TAG, + new Handler(mLooper.getLooper()), + BiometricScheduler.SENSOR_TYPE_FACE, + null /* gestureAvailabilityDispatcher */, + mBiometricService, + () -> USER_ID, + mUserSwitchCallback); + } mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, mHardwareUnavailableCallback); @@ -146,18 +161,8 @@ public class SensorTest { @Test public void onBinderDied_noErrorOnNullClient() { mLooper.dispatchAll(); - - final SensorProps sensorProps = new SensorProps(); - sensorProps.commonProps = new CommonProps(); - sensorProps.commonProps.sensorId = 1; - final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( - sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, - sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */, - sensorProps.sensorType, sensorProps.supportsDetectInteraction, - sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); - final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, - null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext); - sensor.init(mLockoutResetDispatcher, mFaceProvider); + final Sensor sensor = getSensor(); + mScheduler = sensor.getScheduler(); mScheduler.reset(); assertNull(mScheduler.getCurrentClient()); @@ -175,18 +180,8 @@ public class SensorTest { when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID); when(mClientMonitor.isInterruptable()).thenReturn(false); - final SensorProps sensorProps = new SensorProps(); - sensorProps.commonProps = new CommonProps(); - sensorProps.commonProps.sensorId = 1; - final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( - sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, - sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */, - sensorProps.sensorType, sensorProps.supportsDetectInteraction, - sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); - final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, - internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession); - sensor.init(mLockoutResetDispatcher, mFaceProvider); - mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler(); + final Sensor sensor = getSensor(); + mScheduler = sensor.getScheduler(); sensor.mCurrentSession = new AidlSession(0, mock(ISession.class), USER_ID, mHalCallback); @@ -206,4 +201,20 @@ public class SensorTest { verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID)); verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong()); } + + private Sensor getSensor() { + final SensorProps sensorProps = new SensorProps(); + sensorProps.commonProps = new CommonProps(); + sensorProps.commonProps.sensorId = 1; + final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( + sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, + sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */, + sensorProps.sensorType, sensorProps.supportsDetectInteraction, + sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); + final Sensor sensor = new Sensor(mFaceProvider, mContext, + null /* handler */, internalProp, mBiometricContext); + sensor.init(mLockoutResetDispatcher, mFaceProvider); + + return sensor; + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java index 4e43332ab52c..940fe69925b5 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java @@ -45,7 +45,6 @@ import androidx.test.core.app.ApplicationProvider; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; -import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -88,9 +87,9 @@ public class HidlToAidlSensorAdapterTest { @Mock private IBiometricsFace mDaemon; @Mock - AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback; + private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback; @Mock - BiometricUtils<Face> mBiometricUtils; + private BiometricUtils<Face> mBiometricUtils; private final TestLooper mLooper = new TestLooper(); private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter; @@ -118,20 +117,14 @@ public class HidlToAidlSensorAdapterTest { mContext.getOrCreateTestableResources(); final String config = String.format("%d:8:15", SENSOR_ID); - final BiometricScheduler scheduler = new BiometricScheduler(TAG, - new Handler(mLooper.getLooper()), - BiometricScheduler.SENSOR_TYPE_FACE, - null /* gestureAvailabilityTracker */, - mBiometricService, 10 /* recentOperationsLimit */); final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig(); faceSensorConfig.parse(config, mContext); - mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG, mFaceProvider, + mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(mFaceProvider, mContext, new Handler(mLooper.getLooper()), faceSensorConfig, mLockoutResetDispatcherForSensor, mBiometricContext, false /* resetLockoutRequiresChallenge */, mInternalCleanupAndGetFeatureRunnable, mAuthSessionCoordinator, mDaemon, mAidlResponseHandlerCallback); mHidlToAidlSensorAdapter.init(mLockoutResetDispatcherForSensor, mFaceProvider); - mHidlToAidlSensorAdapter.setScheduler(scheduler); mHidlToAidlSensorAdapter.handleUserChanged(USER_ID); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java index bf5986c1d0f3..258be573d005 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java @@ -38,8 +38,10 @@ import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.SensorLocation; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.fingerprint.HidlFingerprintSensorConfig; +import android.os.Handler; import android.os.RemoteException; import android.os.UserManager; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -92,14 +94,12 @@ public class FingerprintProviderTest { @Mock private BiometricContext mBiometricContext; + private final TestLooper mLooper = new TestLooper(); + private SensorProps[] mSensorProps; private LockoutResetDispatcher mLockoutResetDispatcher; private FingerprintProvider mFingerprintProvider; - private static void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - } - @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); @@ -126,7 +126,8 @@ public class FingerprintProviderTest { mFingerprintProvider = new FingerprintProvider(mContext, mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext, - mDaemon, false /* resetLockoutRequiresHardwareAuthToken */, + mDaemon, new Handler(mLooper.getLooper()), + false /* resetLockoutRequiresHardwareAuthToken */, true /* testHalEnabled */); } @@ -159,6 +160,7 @@ public class FingerprintProviderTest { mBiometricStateCallback, mAuthenticationStateListeners, hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext, mDaemon, + new Handler(mLooper.getLooper()), false /* resetLockoutRequiresHardwareAuthToken */, true /* testHalEnabled */); @@ -215,4 +217,12 @@ public class FingerprintProviderTest { assertEquals(0, scheduler.getCurrentPendingCount()); } } + + private void waitForIdle() { + if (Flags.deHidl()) { + mLooper.dispatchAll(); + } else { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 126a05e12628..b4c2ee8f89bf 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -32,14 +32,17 @@ import android.content.Context; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.common.CommonProps; import android.hardware.biometrics.face.SensorProps; +import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; +import android.os.HandlerThread; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; @@ -49,6 +52,7 @@ import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; +import com.android.server.biometrics.sensors.UserSwitchProvider; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import org.junit.Before; @@ -75,6 +79,8 @@ public class SensorTest { @Mock private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback; @Mock + private UserSwitchProvider<IFingerprint, ISession> mUserSwitchProvider; + @Mock private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; @@ -92,11 +98,13 @@ public class SensorTest { private AidlSession mCurrentSession; @Mock private BaseClientMonitor mClientMonitor; + @Mock + private HandlerThread mThread; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); - private BiometricScheduler mScheduler; + private BiometricScheduler<IFingerprint, ISession> mScheduler; private AidlResponseHandler mHalCallback; @Before @@ -105,14 +113,26 @@ public class SensorTest { when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); - - mScheduler = new UserAwareBiometricScheduler(TAG, - new Handler(mLooper.getLooper()), - BiometricScheduler.SENSOR_TYPE_FP_OTHER, - null /* gestureAvailabilityDispatcher */, - mBiometricService, - () -> USER_ID, - mUserSwitchCallback); + when(mThread.getLooper()).thenReturn(mLooper.getLooper()); + + if (Flags.deHidl()) { + mScheduler = new BiometricScheduler<>( + new Handler(mLooper.getLooper()), + BiometricScheduler.SENSOR_TYPE_FP_OTHER, + null /* gestureAvailabilityDispatcher */, + mBiometricService, + 2 /* recentOperationsLimit */, + () -> USER_ID, + mUserSwitchProvider); + } else { + mScheduler = new UserAwareBiometricScheduler<>(TAG, + new Handler(mLooper.getLooper()), + BiometricScheduler.SENSOR_TYPE_FP_OTHER, + null /* gestureAvailabilityDispatcher */, + mBiometricService, + () -> USER_ID, + mUserSwitchCallback); + } mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, mHardwareUnavailableCallback); @@ -153,18 +173,7 @@ public class SensorTest { when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID); when(mClientMonitor.isInterruptable()).thenReturn(false); - final SensorProps sensorProps = new SensorProps(); - sensorProps.commonProps = new CommonProps(); - sensorProps.commonProps.sensorId = 1; - final FingerprintSensorPropertiesInternal internalProp = new - FingerprintSensorPropertiesInternal( - sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, - sensorProps.commonProps.maxEnrollmentsPerUser, null, - sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */); - final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext, - null /* handler */, internalProp, mLockoutResetDispatcher, - mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession); - sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher); + final Sensor sensor = getSensor(); mScheduler = sensor.getScheduler(); sensor.mCurrentSession = new AidlSession(0, mock(ISession.class), USER_ID, mHalCallback); @@ -185,4 +194,21 @@ public class SensorTest { verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID)); verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong()); } + + private Sensor getSensor() { + final SensorProps sensorProps = new SensorProps(); + sensorProps.commonProps = new CommonProps(); + sensorProps.commonProps.sensorId = 1; + final FingerprintSensorPropertiesInternal internalProp = new + FingerprintSensorPropertiesInternal( + sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, + sensorProps.commonProps.maxEnrollmentsPerUser, null, + sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */); + final Sensor sensor = new Sensor(mFingerprintProvider, mContext, + null /* handler */, internalProp, + mBiometricContext, mCurrentSession); + sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher); + + return sensor; + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java index 89a49615dbe1..cbbc54547bf6 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java @@ -36,12 +36,12 @@ import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.HidlFingerprintSensorConfig; import android.os.Handler; +import android.os.HandlerThread; import android.os.RemoteException; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.testing.TestableContext; -import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; @@ -49,17 +49,11 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationStateListeners; -import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricUtils; -import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; -import com.android.server.biometrics.sensors.StartUserClient; -import com.android.server.biometrics.sensors.StopUserClient; -import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; -import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession; import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient; import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider; import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient; @@ -111,60 +105,18 @@ public class HidlToAidlSensorAdapterTest { private BiometricUtils<Fingerprint> mBiometricUtils; @Mock private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock + private HandlerThread mThread; private final TestLooper mLooper = new TestLooper(); private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter; private final TestableContext mContext = new TestableContext( ApplicationProvider.getApplicationContext()); - private final UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback = - new UserAwareBiometricScheduler.UserSwitchCallback() { - @NonNull - @Override - public StopUserClient<?> getStopUserClient(int userId) { - return new StopUserClient<IBiometricsFingerprint>(mContext, - mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null, USER_ID, - SENSOR_ID, mLogger, mBiometricContext, () -> {}) { - @Override - protected void startHalOperation() { - getCallback().onClientFinished(this, true /* success */); - } - - @Override - public void unableToStart() {} - }; - } - - @NonNull - @Override - public StartUserClient<?, ?> getStartUserClient(int newUserId) { - return new StartUserClient<IBiometricsFingerprint, AidlSession>(mContext, - mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null, - USER_ID, SENSOR_ID, - mLogger, mBiometricContext, - (newUserId1, newUser, halInterfaceVersion) -> - mHidlToAidlSensorAdapter.handleUserChanged(newUserId1)) { - @Override - public void start(@NonNull ClientMonitorCallback callback) { - super.start(callback); - startHalOperation(); - } - - @Override - protected void startHalOperation() { - mUserStartedCallback.onUserStarted(USER_ID, null, 0); - getCallback().onClientFinished(this, true /* success */); - } - - @Override - public void unableToStart() {} - }; - } - };; - @Before public void setUp() throws RemoteException { when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); + when(mThread.getLooper()).thenReturn(mLooper.getLooper()); doAnswer((answer) -> { mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback() .onEnrollmentProgress(1 /* enrollmentId */, 0 /* remaining */); @@ -175,26 +127,18 @@ public class HidlToAidlSensorAdapterTest { mContext.getOrCreateTestableResources(); final String config = String.format("%d:2:15", SENSOR_ID); - final UserAwareBiometricScheduler scheduler = new UserAwareBiometricScheduler(TAG, - new Handler(mLooper.getLooper()), - BiometricScheduler.SENSOR_TYPE_FP_OTHER, - null /* gestureAvailabilityDispatcher */, - mBiometricService, - () -> USER_ID, - mUserSwitchCallback); final HidlFingerprintSensorConfig fingerprintSensorConfig = new HidlFingerprintSensorConfig(); fingerprintSensorConfig.parse(config, mContext); - mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG, + mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter( mFingerprintProvider, mContext, new Handler(mLooper.getLooper()), fingerprintSensorConfig, mLockoutResetDispatcherForSensor, - mGestureAvailabilityDispatcher, mBiometricContext, - false /* resetLockoutRequiresHardwareAuthToken */, + mBiometricContext, false /* resetLockoutRequiresHardwareAuthToken */, mInternalCleanupRunnable, mAuthSessionCoordinator, mDaemon, mAidlResponseHandlerCallback); mHidlToAidlSensorAdapter.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcherForSensor); - mHidlToAidlSensorAdapter.setScheduler(scheduler); + mHidlToAidlSensorAdapter.handleUserChanged(USER_ID); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index f5d50d173466..6986cab72f56 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -305,9 +305,9 @@ public abstract class BaseLockSettingsServiceTests { doAnswer(invocation -> { Object[] args = invocation.getArguments(); mStorageManager.unlockCeStorage(/* userId= */ (int) args[0], - /* secret= */ (byte[]) args[2]); + /* secret= */ (byte[]) args[1]); return null; - }).when(sm).unlockCeStorage(anyInt(), anyInt(), any()); + }).when(sm).unlockCeStorage(anyInt(), any()); doAnswer(invocation -> { Object[] args = invocation.getArguments(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 42ad73a23f0e..8622488f820e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -158,6 +158,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); + // consistent with focus not exclusive and volume not muted + when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class))) + .thenReturn(true); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); @@ -869,6 +872,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); @@ -886,6 +890,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(1); + // all streams at 1 means no muting from audio framework + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(true); mService.buzzBeepBlinkLocked(r); @@ -904,6 +910,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); @@ -924,6 +931,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); @@ -1195,6 +1203,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); verifyDelayedVibrate(mService.getVibratorHelper().createFallbackVibration(false)); @@ -1923,6 +1932,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { NotificationRecord r = getBuzzyBeepyNotification(); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mService.buzzBeepBlinkLocked(r); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 1b77b99e7d3e..bfd2df2d2b7d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -182,6 +182,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50); + when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class))) + .thenReturn(true); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); when(mVibrator.hasFrequencyControl()).thenReturn(false); when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); @@ -930,6 +932,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class))) + .thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); @@ -947,6 +951,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(1); + // all streams at 1 means no muting from audio framework + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(true); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); @@ -965,6 +971,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); @@ -986,6 +993,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); @@ -1258,6 +1266,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // the phone is quiet when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); verifyDelayedVibrate(mAttentionHelper.getVibratorHelper().createFallbackVibration(false)); @@ -1988,6 +1997,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { NotificationRecord r = getBuzzyBeepyNotification(); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); 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 0514943a83c5..51df1d4cb14d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -195,6 +195,36 @@ public class TransitionTests extends WindowTestsBase { } @Test + public void testCreateInfo_Activity() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + ArraySet<WindowContainer> participants = transition.mParticipants; + + final Task theTask = createTask(mDisplayContent); + final ActivityRecord closing = createActivityRecord(theTask); + final ActivityRecord opening = createActivityRecord(theTask); + // Start states. + changes.put(theTask, new Transition.ChangeInfo(theTask, true /* vis */, false /* exChg */)); + changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */)); + changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */)); + fillChangeMap(changes, theTask); + // End states. + closing.setVisibleRequested(false); + opening.setVisibleRequested(true); + + final int transit = transition.mType; + int flags = 0; + + participants.add(opening); + participants.add(closing); + ArrayList<Transition.ChangeInfo> targets = + Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT); + assertEquals(2, info.getChanges().size()); + assertEquals(info.getChanges().get(1).getActivityComponent(), closing.mActivityComponent); + } + + @Test public void testCreateInfo_NestedTasks() { final Transition transition = createTestTransition(TRANSIT_OPEN); ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; |