Merge "Delete the built-in "Sleeping" rule when a TYPE_BEDTIME rule is added" into main
diff --git a/Android.bp b/Android.bp
index 82b844b..f3b2ebb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -665,6 +665,7 @@
lint: {
baseline_filename: "lint-baseline.xml",
},
+ apex_available: ["com.android.wifi"],
}
filegroup {
diff --git a/WEAR_OWNERS b/WEAR_OWNERS
index 4f3bc27..4127f99 100644
--- a/WEAR_OWNERS
+++ b/WEAR_OWNERS
@@ -4,3 +4,9 @@
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 b7460cd..fa4387b 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 @@
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 @@
}
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 @@
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 @@
createOrOpenTestDatabase(journalMode, syncMode);
}
}
-
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9077d02..506d203 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5747,7 +5747,7 @@
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 @@
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 @@
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 @@
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 d2af9db..2e22071 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1882,6 +1882,7 @@
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 @@
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 b4a6955..845a346 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1311,8 +1311,9 @@
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 ee1d117b..d5eee63f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -8099,7 +8099,7 @@
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 @@
} 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 @@
+ 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 f0a8996..a8dba51 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -382,10 +382,10 @@
* 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 0e131b4..43322641 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -672,6 +672,13 @@
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 @@
* @hide
*/
@IntDef(value = {
+ UNARCHIVAL_STATUS_UNSET,
UNARCHIVAL_OK,
UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
@@ -2696,8 +2704,6 @@
public int developmentInstallFlags = 0;
/** {@hide} */
public int unarchiveId = -1;
- /** {@hide} */
- public IntentSender unarchiveIntentSender;
private final ArrayMap<String, Integer> mPermissionStates;
@@ -2750,7 +2756,6 @@
applicationEnabledSettingPersistent = source.readBoolean();
developmentInstallFlags = source.readInt();
unarchiveId = source.readInt();
- unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
}
/** {@hide} */
@@ -2785,7 +2790,6 @@
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
ret.developmentInstallFlags = developmentInstallFlags;
ret.unarchiveId = unarchiveId;
- ret.unarchiveIntentSender = unarchiveIntentSender;
return ret;
}
@@ -3495,7 +3499,6 @@
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
pw.printPair("unarchiveId", unarchiveId);
- pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
pw.println();
}
@@ -3540,7 +3543,6 @@
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 0b60977..a2cd3e1 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -138,3 +138,11 @@
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 b96d832..ecffe9e 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -121,8 +121,12 @@
// 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 @@
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 @@
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 @@
/**
* 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 62a5123..92ef9c2 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -7,3 +7,11 @@
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 c5167db..a3a2a2e 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,11 +304,7 @@
*
* @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 7e5c141..4c95e02 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,20 +312,14 @@
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 f0f7e8a..41f21ef 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,12 +166,7 @@
* 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 70de477..05a3e18 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -21,6 +21,7 @@
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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 5a40e42..8219d2f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -1802,7 +1802,7 @@
* 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 @@
* 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 @@
}
}
}
- 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 e005910..37bde3d 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -149,13 +149,47 @@
@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 c14810b..f3496e7 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -425,6 +425,8 @@
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 @@
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 3ecf74e..54ed73c 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -134,16 +134,16 @@
@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 78a12f7..9587db1 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1602,14 +1602,13 @@
* 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 @@
}
/** {@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 144e64f..e8da0d9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3603,12 +3603,17 @@
+ " 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 b4ac9a2..d8fa415 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1260,7 +1260,7 @@
* <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 bb49679..dbeffc8 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1469,7 +1469,8 @@
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 @@
* @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 @@
}
}
- 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 e62d5c9..64fe66e 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -212,7 +212,9 @@
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 @@
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 @@
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 7c9340e..bceb872 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -44,6 +44,7 @@
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 @@
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 @@
mBackgroundColor = in.readInt();
mSnapshot = in.readTypedObject(SurfaceControl.CREATOR);
mSnapshotLuma = in.readFloat();
+ mActivityComponent = in.readTypedObject(ComponentName.CREATOR);
}
private Change localRemoteCopy() {
@@ -685,6 +688,7 @@
out.mBackgroundColor = mBackgroundColor;
out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
out.mSnapshotLuma = mSnapshotLuma;
+ out.mActivityComponent = mActivityComponent;
return out;
}
@@ -780,6 +784,11 @@
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 @@
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 @@
dest.writeInt(mBackgroundColor);
dest.writeTypedObject(mSnapshot, flags);
dest.writeFloat(mSnapshotLuma);
+ dest.writeTypedObject(mActivityComponent, flags);
}
@NonNull
@@ -994,6 +1010,10 @@
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 893cc98..6f1c763 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -82,10 +82,16 @@
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 @@
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 @@
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 @@
(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 43266a5..cb3f99c 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 @@
}
@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 3fc08ee..bd5f809 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.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 org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,6 +57,10 @@
@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 @@
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 681a52b..e04ab81 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 d073f1d..66c0c96 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 @@
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 @@
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 7dec12a..bc1a575 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 @@
IBinder startResizeTransition(WindowContainerTransaction wct,
Transitions.TransitionHandler handler,
+ @Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishCallback) {
if (mPendingResize != null) {
mPendingResize.cancel(null);
@@ -396,13 +397,14 @@
}
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 96e57e7..0781a9e 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 @@
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 e22bf3d..e9da258 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 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 @@
}
@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 8343858..b8939e6f 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 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 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 a5a69f9..4918289 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -21,6 +21,7 @@
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 @@
}
}
+ /**
+ * @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 5c268d4..2eec9b3 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -775,4 +775,8 @@
@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 8ac364e7..b2c23a4 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 @@
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 @@
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 @@
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 170cb45..9ad3e3c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -91,7 +91,8 @@
// 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 @@
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 0e40db2..f44b161 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 d201544..d3d8e4e 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 @@
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 1b40de4..1138221 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.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 @@
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
- beforeContentPadding: ContentPaddingInPx
+ beforeContentPadding: ContentPaddingInPx,
+ viewModel: BaseCommunalViewModel,
): Modifier {
return pointerInput(dragDropState, beforeContentPadding) {
detectDragGesturesAfterLongPress(
@@ -220,9 +222,16 @@
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 449ee6f..4079f12 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 @@
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.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.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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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,6 +190,71 @@
}
@Test
+ fun addWidget_configurationFails_doNotAddWidgetToDb() =
+ testScope.runTest {
+ userUnlocked(true)
+ val repository = initCommunalWidgetRepository()
+ runCurrent()
+
+ 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) { false }
+ runCurrent()
+
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider)
+ verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+ verify(appWidgetHost).deleteAppWidgetId(id)
+ }
+
+ @Test
+ fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
+ testScope.runTest {
+ userUnlocked(true)
+ val repository = initCommunalWidgetRepository()
+ runCurrent()
+
+ 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(communalWidgetHost).allocateIdAndBindWidget(provider)
+ verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+ verify(appWidgetHost).deleteAppWidgetId(id)
+ }
+
+ @Test
+ fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
+ testScope.runTest {
+ userUnlocked(true)
+ val repository = initCommunalWidgetRepository()
+ 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 deleteWidget_removeWidgetId_andDeleteFromDb() =
testScope.runTest {
userUnlocked(true)
@@ -225,17 +288,9 @@
testScope.runTest {
communalEnabled(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
- verifyBroadcastReceiverNeverRegistered()
- }
-
- @Test
- fun broadcastReceiver_featureEnabledAndUserUnlocked_doNotRegisterBroadcastReceiver() =
- testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
- verifyBroadcastReceiverNeverRegistered()
+ repository.communalWidgets.launchIn(backgroundScope)
+ runCurrent()
+ assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
}
@Test
@@ -243,24 +298,9 @@
testScope.runTest {
userUnlocked(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
- verifyBroadcastReceiverRegistered()
- }
-
- @Test
- fun broadcastReceiver_whenFlowFinishes_unregisterBroadcastReceiver() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
-
- val job = launch { repository.communalWidgets.collect() }
+ repository.communalWidgets.launchIn(backgroundScope)
runCurrent()
- val receiver = broadcastReceiverUpdate()
-
- job.cancel()
- runCurrent()
-
- verify(broadcastDispatcher).unregisterReceiver(receiver)
+ assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
}
@Test
@@ -268,12 +308,16 @@
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 @@
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 @@
appWidgetHost,
testScope.backgroundScope,
testDispatcher,
- broadcastDispatcher,
+ fakeBroadcastDispatcher,
communalRepository,
communalWidgetHost,
communalWidgetDao,
@@ -315,45 +366,6 @@
)
}
- 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 4a935d0..ff6fd43 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.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.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 @@
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 @@
underTest =
CommunalEditModeViewModel(
withDeps.communalInteractor,
+ appWidgetHost,
Provider { shadeViewController },
powerManager,
mediaHost,
+ uiEventLogger,
)
}
@@ -145,4 +159,71 @@
)
.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 7c3dc97..5b88ebe6 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.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 @@
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 @@
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true)
+ notificationsKeyguardInteractor.setPulseExpanding(true)
deviceEntryRepository.setBypassEnabled(false)
runCurrent()
@@ -130,9 +129,9 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..d26cbd5
--- /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
similarity index 100%
rename from packages/SystemUI/res/color/notification_overlay_color.xml
rename to 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 0000000..3e8bcf3
--- /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 355e75d..3f903ae 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 64a1d24..17719d1 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 3f026a4..7d7c050 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 cab8adf..e6816e9 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 @@
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.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 @@
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 @@
// Whether the [AppWidgetHost] is listening for updates.
private var isHostListening = false
+ private suspend fun isUserUnlockingOrUnlocked(): Boolean =
+ withContext(bgDispatcher) { userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) }
+
private val isUserUnlocked: Flow<Boolean> =
- callbackFlow {
- if (!communalRepository.isCommunalEnabled) {
- awaitClose()
- }
-
- fun isUserUnlockingOrUnlocked(): Boolean {
- return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
- }
-
- fun send() {
- trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
- }
-
- if (isUserUnlockingOrUnlocked()) {
- send()
- awaitClose()
+ 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 @@
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 @@
}
}
+ @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 8271238..24d4c6c 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 @@
/** 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 155de32..41f9cb4 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 @@
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 @@
}
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 4fabd97..4cb83a3 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 @@
/**
* 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 @@
/** 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 d8e831c..0cbf3f13 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 @@
@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 @@
// 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 0f94a92..bfc6f2b 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 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 @@
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 @@
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 @@
}
)
}
+
+ 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 e660b97..0d641ac 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 @@
}
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 a5c1cf1..6f4a1e7 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 @@
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
if (!isKeyguardShowing()) {
- mNotificationStackScrollLayoutController.generateHeadsUpAnimation(
- entry.getHeadsUpAnimationView(), true);
+ mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true);
}
}
@@ -4375,8 +4374,7 @@
// 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 0c67279..3f2c818 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.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 @@
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController,
private val logger: NotificationWakeUpCoordinatorLogger,
+ private val notifsKeyguardInteractor: NotificationsKeyguardInteractor,
) :
OnHeadsUpChangedListener,
StatusBarStateController.StateListener,
@@ -144,6 +146,7 @@
for (listener in wakeUpListeners) {
listener.onFullyHiddenChanged(value)
}
+ notifsKeyguardInteractor.setNotificationsFullyHidden(value)
}
}
@@ -216,6 +219,7 @@
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 5435fb5..2cac000 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 2cc1403..bd6ea30 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 73341db..a6361cb 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 f6431a2..7ea9b14 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 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 @@
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 @@
}
/**
+ * 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 @@
setCustomBackground(d);
}
- 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[]{}},
+ private Drawable getBaseBackgroundLayer() {
+ return ((LayerDrawable) mBackground).getDrawable(0);
+ }
- new int[]{tintColor, 0, tintColor}
- );
- mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP);
- mBackground.setTintList(stateList);
- } else {
- mBackground.setTintList(null);
- }
+ private Drawable getStatefulBackgroundLayer() {
+ return ((LayerDrawable) mBackground).getDrawable(1);
+ }
+
+ public void setTint(int tintColor) {
+ 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 805b44c..ea414d2 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 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 2e54512..abc04b8 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 @@
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 ac04d31..4f7dce3 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 @@
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 @@
}
}
}
+
+/**
+ * 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 df5162a..3d724e1 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.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 @@
});
}
+ 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 6e19bed..9b72eb7 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 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 @@
* {@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 @@
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 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 @@
mMainExecutor = mainExecutor;
mConnection = serviceConnection;
mObserver = observer;
+ mDumpManager = dumpManager;
+ mDumpsysName = TAG + "#" + dumpsysName;
mMaxReconnectAttempts = maxReconnectAttempts;
mBaseReconnectDelayMs = baseReconnectDelayMs;
@@ -108,6 +121,7 @@
* 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 @@
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 bcf34f8..c52c524 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 @@
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 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 3132767..a206581 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.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 @@
when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
+ NotificationsKeyguardViewStateRepository notifsKeyguardViewStateRepository =
+ new NotificationsKeyguardViewStateRepository();
+ NotificationsKeyguardInteractor notifsKeyguardInteractor =
+ new NotificationsKeyguardInteractor(notifsKeyguardViewStateRepository);
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
mDumpManager,
@@ -596,7 +602,8 @@
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 438b33d..039fef9 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 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 @@
@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 @@
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 170f651..0000000
--- 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 bb3113a..3593f5b 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.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 @@
@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 @@
@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 @@
@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 47feccf..7faf562 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.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 @@
interface TestComponent : SysUITestComponent<NotificationIconsInteractor> {
val activeNotificationListRepository: ActiveNotificationListRepository
- val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
@Component.Factory
interface Factory {
@@ -136,7 +136,7 @@
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 @@
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 @@
val activeNotificationListRepository: ActiveNotificationListRepository
val deviceEntryRepository: FakeDeviceEntryRepository
- val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
@Component.Factory
interface Factory {
@@ -222,7 +222,7 @@
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 @@
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 @@
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 @@
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 @@
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 db0139c..55c49ee 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 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 @@
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 @@
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 @@
mConnectionManager = new PersistentConnectionManager<>(
mFakeClock,
mFakeExecutor,
+ mDumpManager,
+ DUMPSYS_NAME,
mConnection,
MAX_RETRIES,
RETRY_DELAY_MS,
@@ -154,4 +164,16 @@
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 c6f12e2..397dc1a 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 faacce6..eb287ee 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 @@
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 788e3aa..1ffc9f4 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 5d3cb4d..0000000
--- 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 f2b9da4..df7fd94 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 @@
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 432464e..61a38b8 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 @@
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 513c095..d967874 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 @@
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 085c186..7abfecf 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 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 553ba12..7fc1738 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 @@
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 @@
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 007be05..6a81425 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 @@
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 @@
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 @@
}
}
- 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 @@
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 19a9239..7a4ac6a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1356,8 +1356,8 @@
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 @@
@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 @@
/* 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 @@
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 72e62c3..8ad60e6 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 @@
/**
* 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 f49e25a..ef7a0e0 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 @@
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 7cc7c51..5a3fbe9 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 @@
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 08b129e..2771572 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.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.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 @@
* (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 @@
* 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 a1b6f29..91d533c 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 @@
}
}
+
+ /**
+ * @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 0df0006..1376bde 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 @@
}
/**
+ * 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 c72632f..c2bc1e4 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 @@
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 89b638b..89e08c1 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.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.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.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 @@
*
* 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 @@
@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 @@
}
}
- @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 @@
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 @@
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 @@
};
@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 @@
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 startNextOperationIfIdle() {
+ protected void startNextOperation() {
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;
+ }
+
+ 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 startNextOperationIfIdleLegacy() {
+ if (mCurrentOperation != null) {
+ Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
+ return;
+ }
+ if (mPendingOperations.isEmpty()) {
+ 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 @@
// 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 @@
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 @@
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 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 @@
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 @@
&& 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 @@
* @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 @@
* @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 @@
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 @@
* HAL dies.
*/
public void reset() {
- Slog.d(getTag(), "Resetting scheduler");
+ Slog.d(TAG, "Resetting scheduler");
mPendingOperations.clear();
mCurrentOperation = null;
}
@@ -562,11 +674,11 @@
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 @@
/**
* 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 e8654dc..e01c4ec 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 @@
/**
* 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 3753bbd..7ca10e3 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 @@
/**
* 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 @@
@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 @@
} 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 @@
@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 @@
}
@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 @@
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 @@
@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 0000000..bc5c55b
--- /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 af46f44..3d61f99 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 @@
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 9fa15b8..e4ecf1a 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.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 @@
* 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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
}
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 0110ae9..e5ae8e3 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 @@
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 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 @@
@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 3e5c599..635e79a 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.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 @@
*/
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 @@
@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 @@
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,
+ 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 UserSwitchProvider<IFace, ISession>() {
+ @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(), () -> {
+ },
+ new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+ newUserId);
+ mProvider.scheduleInvalidationRequest(sensorId,
+ newUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ Slog.e(TAG, "Face sensor hardware unavailable.");
+ mCurrentSession = null;
+ }
+ });
+
+ return Sensor.this.getStartUserClient(resultController, sensorId,
+ newUserId, provider);
+ }
+ });
+ }
+
+ private UserAwareBiometricScheduler<IFace, ISession> getUserAwareBiometricSchedulerForInit(
+ LockoutResetDispatcher lockoutResetDispatcher,
FaceProvider provider) {
- mScheduler = new UserAwareBiometricScheduler(mTag,
+ return new UserAwareBiometricScheduler<>(TAG,
BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
() -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@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(), () -> {
+ Slog.e(TAG, "Face sensor hardware unavailable.");
+ mCurrentSession = null;
+ });
- 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);
- }
- };
-
- 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 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 @@
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 @@
mProvider, this);
}
- @NonNull public BiometricScheduler getScheduler() {
+ @NonNull public BiometricScheduler<IFace, ISession> getScheduler() {
return mScheduler;
}
@@ -259,17 +287,17 @@
}
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 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 46ce0b6..5337666 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 @@
@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 @@
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 @@
@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 @@
@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 6355cb5..a004cae4 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.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 @@
};
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 @@
@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 @@
@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 @@
@Override
public void serviceDied(long cookie) {
- Slog.d(TAG, "HAL died.");
+ Slog.d(TAG, "Face HAL died.");
mDaemon = null;
}
@@ -140,10 +139,12 @@
}
@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 @@
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 @@
}
@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 5daf2d4..fa95361 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 @@
@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 8ff105b..0d4dac0 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 @@
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 88a11d9..c0388d1 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.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 @@
@SuppressWarnings("deprecation")
public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvider {
+ private static final String TAG = "FingerprintProvider";
+
private boolean mTestHalEnabled;
@NonNull
@@ -172,7 +175,7 @@
boolean resetLockoutRequiresHardwareAuthToken) {
this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
- null /* daemon */, resetLockoutRequiresHardwareAuthToken,
+ null /* daemon */, getHandler(), resetLockoutRequiresHardwareAuthToken,
false /* testHalEnabled */);
}
@@ -184,6 +187,7 @@
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull BiometricContext biometricContext,
@Nullable IFingerprint daemon,
+ @NonNull Handler handler,
boolean resetLockoutRequiresHardwareAuthToken,
boolean testHalEnabled) {
mContext = context;
@@ -191,7 +195,11 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
}
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 2cc1879..394f045 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 @@
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 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 @@
@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 dd887bb..af88c62 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.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 @@
@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 @@
@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 @@
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 @@
@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);
}
- };
- return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
- mToken, newUserId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), mBiometricContext,
- resultController, userStartedCallback);
+ @Override
+ public void onHardwareUnavailable() {
+ Slog.e(TAG,
+ "Fingerprint sensor hardware unavailable.");
+ mCurrentSession = null;
+ }
+ });
+
+ return Sensor.this.getStartUserClient(resultController, sensorId,
+ newUserId);
}
});
- mLockoutTracker = new LockoutCache();
- mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+ }
+
+ 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,
+ () -> 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);
+ }
+ });
+ }
+
+ 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 @@
biometricStateCallback, mProvider, this);
}
- @NonNull public BiometricScheduler getScheduler() {
+ @NonNull public BiometricScheduler<IFingerprint, ISession> getScheduler() {
return mScheduler;
}
@@ -283,17 +324,17 @@
}
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 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 d3cecd0..4accf8f 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 @@
@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 @@
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 @@
@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 @@
@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 @@
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 88dae6f..9232e11 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 @@
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 @@
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 5c5b992..59e64cd 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 @@
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 @@
/**
* 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 @@
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 @@
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 0000000..fc85402
--- /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 90da74c..47fdcdb 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.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.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 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 @@
@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 @@
}
@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 @@
@Override
@Nullable
+ @VisibleForTesting
protected AidlSession getSessionForUser(int userId) {
if (mSession != null && mSession.getUserId() == userId) {
return mSession;
@@ -217,21 +215,18 @@
}
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 @@
@NonNull
@Override
- public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ public StartUserClient<ISession, AidlSession> getStartUserClient(int newUserId) {
return getFingerprintUpdateActiveUserClient(newUserId,
false /* forceUpdateAuthenticatorId */);
}
@@ -268,7 +263,7 @@
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 @@
}
@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 2fc00e1..b469752 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 @@
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 bd22e1d..4c4cf608 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 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 @@
// 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 @@
/**
* 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 @@
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 25576ce..3a63330 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 @@
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 @@
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 0c2eee5..ad09082 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 @@
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 a6f71c2..85c4ffe 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.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 @@
}
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 9ed3559..7aa7b7e 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_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 @@
}
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 64d3a20..1786ac5 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.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 @@
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 @@
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 boolean isAudioAttributesUsage(int usage) {
- return mAttributes != null && mAttributes.getUsage() == usage;
+ return mAttributes.getUsage() == usage;
}
/**
@@ -1172,7 +1173,7 @@
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 376b061..a4af5e7 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.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.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
@@ -210,7 +212,6 @@
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 @@
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 @@
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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
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 0a23dfb..a9118d4 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 @@
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 4adb60c..117d03f 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.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.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.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 @@
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 @@
@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 @@
}
}
+ 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 7d87d1b..cef3244 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 @@
}
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 8adb566..4c42c2d 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 @@
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 75b4531..49af4fe 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 @@
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 5d716fc..e3eb5b5 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 @@
};
}
- 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 56bef33..e1bf8f8 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 @@
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 dfb5a57..fb0729f 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 @@
.map(CredentialOption::getType)
.collect(Collectors.toList()));
+ finalizeAndEmitInitialPhaseMetric(session);
+
if (providerSessions.isEmpty()) {
try {
callback.onError(
@@ -776,6 +778,13 @@
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 d165171..0f914c3 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 @@
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 @@
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 @@
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 b36de0b..23aa374 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 @@
/* 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 8e965e3..8a4e86c 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 @@
// 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 @@
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 e5be4d9..9e11fa2 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.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 @@
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 @@
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 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 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 e370f55..c67e7c5 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.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 @@
}
@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 @@
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 @@
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 @@
+ "<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 @@
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 f386c3b..d876dae 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.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.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 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 @@
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 @@
}
}
+ @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 @@
}
/**
+ * 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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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 e5ecdc4..0403c64 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 @@
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 97e94e3..37ca09d 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.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 @@
"com.android/.SystemTrustAgent");
addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
- mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+ notifyPackageChanged(newAgentComponentName);
assertThat(mEnabledTrustAgents).containsExactly(newAgentComponentName);
assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
@@ -299,7 +298,7 @@
"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 @@
"com.user/.UserTrustAgent");
addTrustAgent(newAgentComponentName, /* isSystemApp= */ false);
- mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+ notifyPackageChanged(newAgentComponentName);
assertThat(mEnabledTrustAgents).isEmpty();
assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
@@ -330,7 +329,7 @@
// 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 @@
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 @@
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 @@
@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 9b84190..0045026 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -215,6 +215,16 @@
}
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 77b1455..26934d8 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 @@
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 8929900..f7480de 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.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.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 @@
@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 @@
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 @@
final boolean isEnroll = client instanceof TestEnrollClient;
mScheduler.scheduleClientMonitor(client);
+ waitForIdle();
if (started) {
mScheduler.startPreparedClient(client.getCookie());
}
@@ -789,6 +852,172 @@
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 @@
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 8b1a291..772ec8b 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.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 @@
@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 @@
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 @@
mFaceProvider = new FaceProvider(mContext,
mBiometricStateCallback, hidlFaceSensorConfig, TAG,
mLockoutResetDispatcher, mBiometricContext, mDaemon,
+ new Handler(mLooper.getLooper()),
true /* resetLockoutRequiresChallenge */,
true /* testHalEnabled */);
@@ -210,4 +211,12 @@
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 e7f7195..fe9cd43 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 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 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.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 @@
@Mock
private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
@Mock
+ private UserSwitchProvider<IFace, ISession> mUserSwitchProvider;
+ @Mock
private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -84,16 +89,16 @@
@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 @@
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 @@
@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 @@
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 @@
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 4e43332..940fe69 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 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 @@
@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 @@
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 bf5986c..258be57 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.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 @@
@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 @@
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 @@
mBiometricStateCallback, mAuthenticationStateListeners,
hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
+ new Handler(mLooper.getLooper()),
false /* resetLockoutRequiresHardwareAuthToken */,
true /* testHalEnabled */);
@@ -215,4 +217,12 @@
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 126a05e..b4c2ee8 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.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.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 @@
@Mock
private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
@Mock
+ private UserSwitchProvider<IFingerprint, ISession> mUserSwitchProvider;
+ @Mock
private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -92,11 +98,13 @@
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 @@
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ when(mThread.getLooper()).thenReturn(mLooper.getLooper());
- mScheduler = new UserAwareBiometricScheduler(TAG,
- new Handler(mLooper.getLooper()),
- BiometricScheduler.SENSOR_TYPE_FP_OTHER,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- () -> USER_ID,
- mUserSwitchCallback);
+ 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 @@
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 @@
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 89a4961..cbbc545 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.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.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 @@
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 @@
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 f5d50d1..6986cab 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 @@
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 42ad73a..8622488 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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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 1b77b99..bfd2df2d 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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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 0514943..51df1d4 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 @@
}
@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;