Merge "SlicePurchaseActivity handle user data based on contents type" into main
diff --git a/Android.bp b/Android.bp
index 040bb6e..93d6f53 100644
--- a/Android.bp
+++ b/Android.bp
@@ -212,7 +212,7 @@
"android.hardware.contexthub-V1.0-java",
"android.hardware.contexthub-V1.1-java",
"android.hardware.contexthub-V1.2-java",
- "android.hardware.contexthub-V2-java",
+ "android.hardware.contexthub-V3-java",
"android.hardware.gnss-V1.0-java",
"android.hardware.gnss-V2.1-java",
"android.hardware.health-V1.0-java-constants",
diff --git a/core/api/current.txt b/core/api/current.txt
index c2bc5a2..82b880a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14271,6 +14271,7 @@
method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
+ method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
method public int delete(String, String, String[]);
method public static boolean deleteDatabase(@NonNull java.io.File);
method public void disableWriteAheadLogging();
@@ -14281,6 +14282,7 @@
method public void execSQL(String, Object[]) throws android.database.SQLException;
method public static String findEditTable(String);
method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
+ method public long getLastInsertRowId();
method public long getMaximumSize();
method public long getPageSize();
method public String getPath();
@@ -14506,6 +14508,39 @@
method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
}
+ public final class SQLiteRawStatement implements java.io.Closeable {
+ method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
+ method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
+ method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
+ method public void bindInt(int, int) throws android.database.sqlite.SQLiteException;
+ method public void bindLong(int, long) throws android.database.sqlite.SQLiteException;
+ method public void bindNull(int) throws android.database.sqlite.SQLiteException;
+ method public void bindText(int, @NonNull String) throws android.database.sqlite.SQLiteException;
+ method public void clearBindings();
+ method public void close();
+ method @Nullable public byte[] getColumnBlob(int) throws android.database.sqlite.SQLiteException;
+ method public double getColumnDouble(int) throws android.database.sqlite.SQLiteException;
+ method public int getColumnInt(int) throws android.database.sqlite.SQLiteException;
+ method public int getColumnLength(int) throws android.database.sqlite.SQLiteException;
+ method public long getColumnLong(int) throws android.database.sqlite.SQLiteException;
+ method @NonNull public String getColumnName(int) throws android.database.sqlite.SQLiteException;
+ method @NonNull public String getColumnText(int) throws android.database.sqlite.SQLiteException;
+ method public int getColumnType(int) throws android.database.sqlite.SQLiteException;
+ method public int getParameterCount();
+ method public int getParameterIndex(@NonNull String);
+ method @Nullable public String getParameterName(int);
+ method public int getResultColumnCount();
+ method public boolean isOpen();
+ method public int readColumnBlob(int, @NonNull byte[], int, int, int) throws android.database.sqlite.SQLiteException;
+ method public void reset();
+ method public boolean step() throws android.database.sqlite.SQLiteException;
+ field public static final int SQLITE_DATA_TYPE_BLOB = 4; // 0x4
+ field public static final int SQLITE_DATA_TYPE_FLOAT = 2; // 0x2
+ field public static final int SQLITE_DATA_TYPE_INTEGER = 1; // 0x1
+ field public static final int SQLITE_DATA_TYPE_NULL = 5; // 0x5
+ field public static final int SQLITE_DATA_TYPE_TEXT = 3; // 0x3
+ }
+
public class SQLiteReadOnlyDatabaseException extends android.database.sqlite.SQLiteException {
ctor public SQLiteReadOnlyDatabaseException();
ctor public SQLiteReadOnlyDatabaseException(String);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index f467200..e447dc5 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -61,6 +61,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
import com.android.internal.widget.VerifyCredentialResponse;
import java.nio.charset.Charset;
@@ -885,12 +886,8 @@
}
Objects.requireNonNull(password, "Password cannot be null.");
complexity = PasswordMetrics.sanitizeComplexityLevel(complexity);
- // TODO: b/131755827 add devicePolicyManager support for Auto
- DevicePolicyManager devicePolicyManager =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
PasswordMetrics adminMetrics =
- devicePolicyManager.getPasswordMinimumMetrics(mContext.getUserId());
-
+ mLockPatternUtils.getRequestedPasswordMetrics(mContext.getUserId());
try (LockscreenCredential credential = createLockscreenCredential(lockType, password)) {
return PasswordMetrics.validateCredential(adminMetrics, complexity,
credential).size() == 0;
@@ -913,11 +910,8 @@
return -1;
}
complexity = PasswordMetrics.sanitizeComplexityLevel(complexity);
- // TODO: b/131755827 add devicePolicyManager support for Auto
- DevicePolicyManager devicePolicyManager =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
PasswordMetrics adminMetrics =
- devicePolicyManager.getPasswordMinimumMetrics(mContext.getUserId());
+ mLockPatternUtils.getRequestedPasswordMetrics(mContext.getUserId());
PasswordMetrics minMetrics =
PasswordMetrics.applyComplexity(adminMetrics, isPin, complexity);
return minMetrics.length;
@@ -1139,6 +1133,14 @@
currentLockType, currentPassword);
LockscreenCredential newCredential = createLockscreenCredential(
newLockType, newPassword);
+ PasswordMetrics adminMetrics =
+ mLockPatternUtils.getRequestedPasswordMetrics(mContext.getUserId());
+ List<PasswordValidationError> errors = PasswordMetrics.validateCredential(adminMetrics,
+ DevicePolicyManager.PASSWORD_COMPLEXITY_NONE, newCredential);
+ if (!errors.isEmpty()) {
+ Log.e(TAG, "New credential is not valid: " + errors.get(0));
+ return false;
+ }
return mLockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
}
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 251c5d8..93c0c8a 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -30,6 +30,7 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
+import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PATTERN_SIZE;
import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS;
@@ -134,17 +135,6 @@
}
}
- private static boolean hasInvalidCharacters(byte[] password) {
- // Allow non-control Latin-1 characters only.
- for (byte b : password) {
- char c = (char) b;
- if (c < 32 || c > 127) {
- return true;
- }
- }
- return false;
- }
-
@Override
public int describeContents() {
return 0;
@@ -201,7 +191,9 @@
return PasswordMetrics.computeForPasswordOrPin(credential.getCredential(),
credential.isPin());
} else if (credential.isPattern()) {
- return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ PasswordMetrics metrics = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ metrics.length = credential.size();
+ return metrics;
} else if (credential.isNone()) {
return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
} else {
@@ -529,39 +521,8 @@
return Collections.singletonList(
new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
}
- if (credential.isPassword() || credential.isPin()) {
- return validatePassword(adminMetrics, minComplexity, credential.isPin(),
- credential.getCredential());
- } else {
- return validatePasswordMetrics(adminMetrics, minComplexity,
- new PasswordMetrics(credential.getType()));
- }
- }
-
- /**
- * Validates a proposed lockscreen credential against minimum metrics and complexity.
- *
- * @param adminMetrics minimum metrics to satisfy admin requirements
- * @param minComplexity minimum complexity imposed by the requester
- * @param isPin whether to validate as a PIN (true) or password (false)
- * @param password the proposed lockscreen credential as a byte[]. Must be the value from
- * {@link LockscreenCredential#getCredential()}.
- *
- * @return a list of validation errors. An empty list means the credential is OK.
- *
- * TODO: merge this into validateCredential() and remove the redundant hasInvalidCharacters(),
- * once all external callers are removed
- */
- public static List<PasswordValidationError> validatePassword(
- PasswordMetrics adminMetrics, int minComplexity, boolean isPin, byte[] password) {
-
- if (hasInvalidCharacters(password)) {
- return Collections.singletonList(
- new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
- }
-
- final PasswordMetrics enteredMetrics = computeForPasswordOrPin(password, isPin);
- return validatePasswordMetrics(adminMetrics, minComplexity, enteredMetrics);
+ PasswordMetrics actualMetrics = computeForCredential(credential);
+ return validatePasswordMetrics(adminMetrics, minComplexity, actualMetrics);
}
/**
@@ -584,9 +545,18 @@
|| !bucket.allowsCredType(actualMetrics.credType)) {
return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0));
}
- if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD
- && actualMetrics.credType != CREDENTIAL_TYPE_PIN) {
- return Collections.emptyList(); // Nothing to check for pattern or none.
+ if (actualMetrics.credType == CREDENTIAL_TYPE_PATTERN) {
+ // For pattern, only need to check the length against the hardcoded minimum. If the
+ // pattern length is unavailable (e.g., PasswordMetrics that was stored on-disk before
+ // the pattern length started being included in it), assume it is okay.
+ if (actualMetrics.length != 0 && actualMetrics.length < MIN_LOCK_PATTERN_SIZE) {
+ return Collections.singletonList(new PasswordValidationError(TOO_SHORT,
+ MIN_LOCK_PATTERN_SIZE));
+ }
+ return Collections.emptyList();
+ }
+ if (actualMetrics.credType == CREDENTIAL_TYPE_NONE) {
+ return Collections.emptyList(); // Nothing to check for none.
}
if (actualMetrics.credType == CREDENTIAL_TYPE_PIN && actualMetrics.nonNumeric > 0) {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 026b8c2..eceec34 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2171,11 +2171,11 @@
* Return a {@link SQLiteRawStatement} connected to the database. A transaction must be in
* progress or an exception will be thrown. The resulting object will be closed automatically
* when the current transaction closes.
+ *
* @param sql The SQL string to be compiled into a prepared statement.
* @return A {@link SQLiteRawStatement} holding the compiled SQL.
* @throws IllegalStateException if a transaction is not in progress.
* @throws SQLiteException if the SQL cannot be compiled.
- * @hide
*/
@NonNull
public SQLiteRawStatement createRawStatement(@NonNull String sql) {
@@ -2184,16 +2184,19 @@
}
/**
- * Return the "rowid" of the last row to be inserted on the current connection. See the
- * SQLite documentation for the specific details. This method must only be called when inside
- * a transaction. {@link IllegalStateException} is thrown if the method is called outside a
- * transaction. If the function is called before any inserts in the current transaction, the
- * value returned will be from a previous transaction, which may be from a different thread.
+ * Return the "rowid" of the last row to be inserted on the current connection. This method must
+ * only be called when inside a transaction. {@link IllegalStateException} is thrown if the
+ * method is called outside a transaction. If the function is called before any inserts in the
+ * current transaction, the value returned will be from a previous transaction, which may be
+ * from a different thread. If no inserts have occurred on the current connection, the function
+ * returns 0. See the SQLite documentation for the specific details.
+ *
+ * @see <a href="https://sqlite.org/c3ref/last_insert_rowid.html">sqlite3_last_insert_rowid</a>
+ *
* @return The ROWID of the last row to be inserted under this connection.
* @throws IllegalStateException if there is no current transaction.
- * @hide
*/
- public long lastInsertRowId() {
+ public long getLastInsertRowId() {
return getThreadSession().lastInsertRowId();
}
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 6b43788..165f181 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -31,10 +31,10 @@
import java.util.Objects;
/**
- * Represents a SQLite statement. The methods correspond very closely to SQLite APIs that operate
- * on a sqlite_stmt object. See the SQLite API documentation for complete details. In general,
- * the APIs in this class correspond to the SQLite APIs with the same name, except that snake-case
- * is changed to camel-case.
+ * A {@link SQLiteRawStatement} represents a SQLite prepared statement. The methods correspond very
+ * closely to SQLite APIs that operate on a sqlite_stmt object. In general, each API in this class
+ * corresponds to a single SQLite API.
+
* <p>
* A {@link SQLiteRawStatement} must be created through a database, and there must be a
* transaction open at the time. Statements are implicitly closed when the outermost transaction
@@ -66,8 +66,9 @@
* database.endTransaction();
* }
* </pre></code>
- * Note that this class is unrelated to {@link SQLiteStatement}.
- * @hide
+ * Note that {@link SQLiteRawStatement} is unrelated to {@link SQLiteStatement}.
+ *
+ * @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
*/
public final class SQLiteRawStatement implements Closeable {
@@ -114,24 +115,46 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL})
+ @IntDef(value = {
+ SQLITE_DATA_TYPE_INTEGER,
+ SQLITE_DATA_TYPE_FLOAT,
+ SQLITE_DATA_TYPE_TEXT,
+ SQLITE_DATA_TYPE_BLOB,
+ SQLITE_DATA_TYPE_NULL})
public @interface SQLiteDataType {}
- public static final int SQLITE_INTEGER = 1;
- public static final int SQLITE_FLOAT = 2;
- public static final int SQLITE_TEXT = 3;
- public static final int SQLITE_BLOB = 4;
- public static final int SQLITE_NULL = 5;
+ /**
+ * The constant returned by {@link #getColumnType} when the column value is SQLITE_INTEGER.
+ */
+ public static final int SQLITE_DATA_TYPE_INTEGER = 1;
/**
- * SQLite error codes that are used by this class. Refer to the sqlite documentation for
- * other error codes.
+ * The constant returned by {@link #getColumnType} when the column value is SQLITE_FLOAT.
*/
- public static final int SQLITE_OK = 0;
- public static final int SQLITE_BUSY = 5;
- public static final int SQLITE_LOCKED = 6;
- public static final int SQLITE_ROW = 100;
- public static final int SQLITE_DONE = 101;
+ public static final int SQLITE_DATA_TYPE_FLOAT = 2;
+
+ /**
+ * The constant returned by {@link #getColumnType} when the column value is SQLITE_TEXT.
+ */
+ public static final int SQLITE_DATA_TYPE_TEXT = 3;
+
+ /**
+ * The constant returned by {@link #getColumnType} when the column value is SQLITE_BLOB.
+ */
+ public static final int SQLITE_DATA_TYPE_BLOB = 4;
+
+ /**
+ * The constant returned by {@link #getColumnType} when the column value is SQLITE_NULL.
+ */
+ public static final int SQLITE_DATA_TYPE_NULL = 5;
+
+ /**
+ * SQLite error codes that are used by this class.
+ */
+ private static final int SQLITE_BUSY = 5;
+ private static final int SQLITE_LOCKED = 6;
+ private static final int SQLITE_ROW = 100;
+ private static final int SQLITE_DONE = 101;
/**
* Create the statement with empty bindings. The construtor will throw
@@ -201,6 +224,7 @@
/**
* Return true if the statement is still open and false otherwise.
+ *
* @return True if the statement is open.
*/
public boolean isOpen() {
@@ -208,9 +232,12 @@
}
/**
- * Step to the next result. This returns true if the statement stepped to a new row, and
+ * Step to the next result row. This returns true if the statement stepped to a new row, and
* false if the statement is done. The method throws on any other result, including a busy or
* locked database. If WAL is enabled then the database should never be locked or busy.
+ *
+ * @see <a href="http://sqlite.org/c3ref/step.html">sqlite3_step</a>
+ *
* @return True if a row is available and false otherwise.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteDatabaseLockedException if the database is locked or busy.
@@ -239,11 +266,14 @@
}
/**
- * Step to the next result. This returns the raw error code code from the native method. The
+ * Step to the next result. This returns the raw result code code from the native method. The
* expected values are SQLITE_ROW and SQLITE_DONE. For other return values, clients must
- * decode the error and handle it themselves.
+ * decode the error and handle it themselves. http://sqlite.org/rescode.html for the current
+ * list of result codes.
+ *
* @return The native result code from the sqlite3_step() operation.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
+ * @hide
*/
public int stepNoThrow() {
throwIfInvalid();
@@ -255,8 +285,10 @@
}
/**
- * Reset the statement. The sqlite3 API returns an error code if the last call to step
- * generated an error; this function discards those error codes.
+ * Reset the statement.
+ *
+ * @see <a href="http://sqlite.org/c3ref/reset.html">sqlite3_reset</a>
+ *
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteException if a native error occurs.
*/
@@ -271,6 +303,9 @@
/**
* Clear all parameter bindings.
+ *
+ * @see <a href="http://sqlite.org/c3ref/clear_bindings.html">sqlite3_clear_bindings</a>
+ *
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteException if a native error occurs.
*/
@@ -285,10 +320,14 @@
/**
* Return the number of parameters in the statement.
+ *
+ * @see
+ * <a href="http://sqlite.org/c3ref/bind_parameter_count.html">sqlite3_bind_parameter_count</a>
+ *
* @return The number of parameters in the statement.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
*/
- public int bindParameterCount() {
+ public int getParameterCount() {
throwIfInvalid();
try {
return nativeBindParameterCount(mStatement);
@@ -300,11 +339,15 @@
/**
* Return the index of the parameter with specified name. If the name does not match any
* parameter, 0 is returned.
+ *
+ * @see
+ * <a href="http://sqlite.org/c3ref/bind_parameter_index.html">sqlite3_bind_parameter_index</a>
+ *
* @param name The name of a parameter.
* @return The index of the parameter or 0 if the name does not identify a parameter.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
*/
- public int bindParameterIndex(@NonNull String name) {
+ public int getParameterIndex(@NonNull String name) {
Objects.requireNonNull(name);
throwIfInvalid();
try {
@@ -317,16 +360,19 @@
/**
* Return the name of the parameter at the specified index. Null is returned if there is no
* such parameter or if the parameter does not have a name.
- * @param parameter The index of the parameter.
+ *
+ * @see
+ * <a href="http://sqlite.org/c3ref/bind_parameter_name.html">sqlite3_bind_parameter_name</a>
+ *
+ * @param parameterIndex The index of the parameter.
* @return The name of the parameter.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
- * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
*/
@Nullable
- public String bindParameterName(int parameter) {
+ public String getParameterName(int parameterIndex) {
throwIfInvalid();
try {
- return nativeBindParameterName(mStatement, parameter);
+ return nativeBindParameterName(mStatement, parameterIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -335,17 +381,20 @@
/**
* Bind a blob to a parameter. Parameter indices start at 1. The function throws if the
* parameter index is out of bounds.
- * @param parameter The index of the parameter in the query. It is one-based.
+ *
+ * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_blob</a>
+ *
+ * @param parameterIndex The index of the parameter in the query. It is one-based.
* @param value The value to be bound to the parameter.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public void bindBlob(int parameter, @NonNull byte[] value) throws SQLiteException {
+ public void bindBlob(int parameterIndex, @NonNull byte[] value) throws SQLiteException {
Objects.requireNonNull(value);
throwIfInvalid();
try {
- nativeBindBlob(mStatement, parameter, value, 0, value.length);
+ nativeBindBlob(mStatement, parameterIndex, value, 0, value.length);
} finally {
Reference.reachabilityFence(this);
}
@@ -355,7 +404,10 @@
* Bind a blob to a parameter. Parameter indices start at 1. The function throws if the
* parameter index is out of bounds. The sub-array value[offset] to value[offset+length-1] is
* bound.
- * @param parameter The index of the parameter in the query. It is one-based.
+ *
+ * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_blob</a>
+ *
+ * @param parameterIndex The index of the parameter in the query. It is one-based.
* @param value The value to be bound to the parameter.
* @param offset An offset into the value array
* @param length The number of bytes to bind from the value array.
@@ -364,13 +416,13 @@
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public void bindBlob(int parameter, @NonNull byte[] value, int offset, int length)
+ public void bindBlob(int parameterIndex, @NonNull byte[] value, int offset, int length)
throws SQLiteException {
Objects.requireNonNull(value);
throwIfInvalid();
throwIfInvalidBounds(value.length, offset, length);
try {
- nativeBindBlob(mStatement, parameter, value, offset, length);
+ nativeBindBlob(mStatement, parameterIndex, value, offset, length);
} finally {
Reference.reachabilityFence(this);
}
@@ -379,16 +431,19 @@
/**
* Bind a double to a parameter. Parameter indices start at 1. The function throws if the
* parameter index is out of bounds.
- * @param parameter The index of the parameter in the query. It is one-based.
+ *
+ * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_double</a>
+ *
+ * @param parameterIndex The index of the parameter in the query. It is one-based.
* @param value The value to be bound to the parameter.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public void bindDouble(int parameter, double value) throws SQLiteException {
+ public void bindDouble(int parameterIndex, double value) throws SQLiteException {
throwIfInvalid();
try {
- nativeBindDouble(mStatement, parameter, value);
+ nativeBindDouble(mStatement, parameterIndex, value);
} finally {
Reference.reachabilityFence(this);
}
@@ -397,15 +452,18 @@
/**
* Bind an int to a parameter. Parameter indices start at 1. The function throws if the
* parameter index is out of bounds.
- * @param parameter The index of the parameter in the query. It is one-based.
+ *
+ * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_int</a>
+ *
+ * @param parameterIndex The index of the parameter in the query. It is one-based.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public void bindInt(int parameter, int value) throws SQLiteException {
+ public void bindInt(int parameterIndex, int value) throws SQLiteException {
throwIfInvalid();
try {
- nativeBindInt(mStatement, parameter, value);
+ nativeBindInt(mStatement, parameterIndex, value);
} finally {
Reference.reachabilityFence(this);
}
@@ -414,15 +472,18 @@
/**
* Bind a long to the parameter. Parameter indices start at 1. The function throws if the
* parameter index is out of bounds.
+ *
+ * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_int64</a>
+ *
* @param value The value to be bound to the parameter.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public void bindLong(int parameter, long value) throws SQLiteException {
+ public void bindLong(int parameterIndex, long value) throws SQLiteException {
throwIfInvalid();
try {
- nativeBindLong(mStatement, parameter, value);
+ nativeBindLong(mStatement, parameterIndex, value);
} finally {
Reference.reachabilityFence(this);
}
@@ -431,15 +492,18 @@
/**
* Bind a null to the parameter. Parameter indices start at 1. The function throws if the
* parameter index is out of bounds.
- * @param parameter The index of the parameter in the query. It is one-based.
+ *
+ * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_null</a>
+ *
+ * @param parameterIndex The index of the parameter in the query. It is one-based.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public void bindNull(int parameter) throws SQLiteException {
+ public void bindNull(int parameterIndex) throws SQLiteException {
throwIfInvalid();
try {
- nativeBindNull(mStatement, parameter);
+ nativeBindNull(mStatement, parameterIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -448,17 +512,20 @@
/**
* Bind a string to the parameter. Parameter indices start at 1. The function throws if the
* parameter index is out of bounds. The string may not be null.
- * @param parameter The index of the parameter in the query. It is one-based.
+ *
+ * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_text16</a>
+ *
+ * @param parameterIndex The index of the parameter in the query. It is one-based.
* @param value The value to be bound to the parameter.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public void bindText(int parameter, @NonNull String value) throws SQLiteException {
+ public void bindText(int parameterIndex, @NonNull String value) throws SQLiteException {
Objects.requireNonNull(value);
throwIfInvalid();
try {
- nativeBindText(mStatement, parameter, value);
+ nativeBindText(mStatement, parameterIndex, value);
} finally {
Reference.reachabilityFence(this);
}
@@ -466,10 +533,13 @@
/**
* Return the number of columns in the current result row.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_count.html">sqlite3_column_count</a>
+ *
* @return The number of columns in the result row.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
*/
- public int getResultColumnsCount() {
+ public int getResultColumnCount() {
throwIfInvalid();
try {
return nativeColumnCount(mStatement);
@@ -480,17 +550,20 @@
/**
* Return the type of the column in the result row. Column indices start at 0.
- * @param column The index of a column in the result row. It is zero-based.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_type</a>
+ *
+ * @param columnIndex The index of a column in the result row. It is zero-based.
* @return The type of the value in the column of the result row.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
* @throws SQLiteException if a native error occurs.
*/
@SQLiteDataType
- public int getType(int column) throws SQLiteException {
+ public int getColumnType(int columnIndex) throws SQLiteException {
throwIfInvalid();
try {
- return nativeColumnType(mStatement, column);
+ return nativeColumnType(mStatement, columnIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -499,17 +572,20 @@
/**
* Return the name of the column in the result row. Column indices start at 0. This throws
* an exception if column is not in the result.
- * @param column The index of a column in the result row. It is zero-based.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_name.html">sqlite3_column_name</a>
+ *
+ * @param columnIndex The index of a column in the result row. It is zero-based.
* @return The name of the column in the result row.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
* @throws SQLiteOutOfMemoryException if the database cannot allocate memory for the name.
*/
@NonNull
- public String getName(int column) throws SQLiteException {
+ public String getColumnName(int columnIndex) throws SQLiteException {
throwIfInvalid();
try {
- return nativeColumnName(mStatement, column);
+ return nativeColumnName(mStatement, columnIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -517,20 +593,24 @@
/**
* Return the length of the column value in the result row. Column indices start at 0. This
- * returns 0 for a null and number of bytes for text or blob. Numeric values are converted to
- * a string and the length of the string is returned. Note that this cannot be used to
- * distinguish a null value from an empty text or blob. Note that this returns the number of
- * bytes in the text value, not the number of characters.
- * @param column The index of a column in the result row. It is zero-based.
+ * returns 0 for a null and number of bytes for text or blob. Numeric values are converted to a
+ * string and the length of the string is returned. See the sqlite documentation for
+ * details. Note that this cannot be used to distinguish a null value from an empty text or
+ * blob. Note that this returns the number of bytes in the text value, not the number of
+ * characters.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_bytes</a>
+ *
+ * @param columnIndex The index of a column in the result row. It is zero-based.
* @return The length, in bytes, of the value in the column.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public int getLength(int column) throws SQLiteException {
+ public int getColumnLength(int columnIndex) throws SQLiteException {
throwIfInvalid();
try {
- return nativeColumnBytes(mStatement, column);
+ return nativeColumnBytes(mStatement, columnIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -540,39 +620,23 @@
* Return the column value of the result row as a blob. Column indices start at 0. This
* throws an exception if column is not in the result. This returns null if the column value
* is null.
- * @param column The index of a column in the result row. It is zero-based.
+ *
+ * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_BLOB}; see
+ * the sqlite documentation for details.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_blob</a>
+ *
+ * @param columnIndex The index of a column in the result row. It is zero-based.
* @return The value of the column as a blob, or null if the column is NULL.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
* @throws SQLiteException if a native error occurs.
*/
@Nullable
- public byte[] getBlob(int column) throws SQLiteException {
+ public byte[] getColumnBlob(int columnIndex) throws SQLiteException {
throwIfInvalid();
try {
- return nativeColumnBlob(mStatement, column);
- } finally {
- Reference.reachabilityFence(this);
- }
- }
-
- /**
- * Copy the column value of the result row, interpreted as a blob, into the buffer. Column
- * indices start at 0. This throws an exception if column is not in the result row. Bytes are
- * copied into the buffer until the buffer is full or the end of the blob value is reached.
- * The function returns the number of bytes copied.
- * @param column The index of a column in the result row. It is zero-based.
- * @param buffer A pre-allocated array to be filled with the value of the column.
- * @return the number of bytes that were copied
- * @throws IllegalStateException if the statement is closed or this is a foreign thread.
- * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
- * @throws SQLiteException if a native error occurs.
- */
- public int getBlob(int column, @NonNull byte[] buffer) throws SQLiteException {
- Objects.requireNonNull(buffer);
- throwIfInvalid();
- try {
- return nativeColumnBuffer(mStatement, column, buffer, 0, buffer.length, 0);
+ return nativeColumnBlob(mStatement, columnIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -584,7 +648,13 @@
* copied into the buffer starting at the offset. Bytes are copied from the blob starting at
* srcOffset. Length bytes are copied unless the column value has fewer bytes available. The
* function returns the number of bytes copied.
- * @param column The index of a column in the result row. It is zero-based.
+ *
+ * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_BLOB}; see
+ * the sqlite documentation for details.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_blob</a>
+ *
+ * @param columnIndex The index of a column in the result row. It is zero-based.
* @param buffer A pre-allocated array to be filled with the value of the column.
* @param offset An offset into the buffer: copying starts here.
* @param length The number of bytes to copy.
@@ -595,13 +665,14 @@
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public int getBlob(int column, @NonNull byte[] buffer, int offset, int length, int srcOffset)
+ public int readColumnBlob(int columnIndex, @NonNull byte[] buffer, int offset,
+ int length, int srcOffset)
throws SQLiteException {
Objects.requireNonNull(buffer);
throwIfInvalid();
throwIfInvalidBounds(buffer.length, offset, length);
try {
- return nativeColumnBuffer(mStatement, column, buffer, offset, length, srcOffset);
+ return nativeColumnBuffer(mStatement, columnIndex, buffer, offset, length, srcOffset);
} finally {
Reference.reachabilityFence(this);
}
@@ -610,16 +681,22 @@
/**
* Return the column value as a double. Column indices start at 0. This throws an exception
* if column is not in the result.
- * @param column The index of a column in the result row. It is zero-based.
+ *
+ * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_FLOAT}; see
+ * the sqlite documentation for details.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_double</a>
+ *
+ * @param columnIndex The index of a column in the result row. It is zero-based.
* @return The value of a column as a double.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public double getDouble(int column) throws SQLiteException {
+ public double getColumnDouble(int columnIndex) throws SQLiteException {
throwIfInvalid();
try {
- return nativeColumnDouble(mStatement, column);
+ return nativeColumnDouble(mStatement, columnIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -628,16 +705,22 @@
/**
* Return the column value as a int. Column indices start at 0. This throws an exception if
* column is not in the result.
- * @param column The index of a column in the result row. It is zero-based.
+ *
+ * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_INTEGER};
+ * see the sqlite documentation for details.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_int</a>
+ *
+ * @param columnIndex The index of a column in the result row. It is zero-based.
* @return The value of the column as an int.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public int getInt(int column) throws SQLiteException {
+ public int getColumnInt(int columnIndex) throws SQLiteException {
throwIfInvalid();
try {
- return nativeColumnInt(mStatement, column);
+ return nativeColumnInt(mStatement, columnIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -646,16 +729,22 @@
/**
* Return the column value as a long. Column indices start at 0. This throws an exception if
* column is not in the result.
- * @param column The index of a column in the result row. It is zero-based.
+ *
+ * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_INTEGER};
+ * see the sqlite documentation for details.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_long</a>
+ *
+ * @param columnIndex The index of a column in the result row. It is zero-based.
* @return The value of the column as an long.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
* @throws SQLiteException if a native error occurs.
*/
- public long getLong(int column) throws SQLiteException {
+ public long getColumnLong(int columnIndex) throws SQLiteException {
throwIfInvalid();
try {
- return nativeColumnLong(mStatement, column);
+ return nativeColumnLong(mStatement, columnIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -664,17 +753,23 @@
/**
* Return the column value as a text. Column indices start at 0. This throws an exception if
* column is not in the result.
- * @param column The index of a column in the result row. It is zero-based.
+ *
+ * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_TEXT}; see
+ * the sqlite documentation for details.
+ *
+ * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_text16</a>
+ *
+ * @param columnIndex The index of a column in the result row. It is zero-based.
* @return The value of the column as a string.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
* @throws SQLiteException if a native error occurs.
*/
@NonNull
- public String getText(int column) throws SQLiteException {
+ public String getColumnText(int columnIndex) throws SQLiteException {
throwIfInvalid();
try {
- return nativeColumnText(mStatement, column);
+ return nativeColumnText(mStatement, columnIndex);
} finally {
Reference.reachabilityFence(this);
}
@@ -732,14 +827,16 @@
private static native void nativeBindText(long stmt, int param, String val);
/**
- * Methods that return information (including the values) of columns from the current result
- * row.
+ * Methods that return information about the columns int the current result row.
*/
@FastNative
private static native int nativeColumnType(long stmt, int col);
@FastNative
private static native String nativeColumnName(long stmt, int col);
+ /**
+ * Methods that return information about the value columns in the current result row.
+ */
@FastNative
private static native int nativeColumnBytes(long stmt, int col);
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 1fdb377..2e40f60 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -144,7 +144,6 @@
private Context mContext;
private IAuthService mService;
- // LINT.IfChange
/**
* Creates a builder for a {@link BiometricPrompt} dialog.
* @param context The {@link Context} that will be used to build the prompt.
@@ -418,7 +417,6 @@
* @hide
*/
@NonNull
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) {
mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager);
return this;
@@ -431,7 +429,6 @@
* @hide
*/
@NonNull
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
public Builder setReceiveSystemEvents(boolean set) {
mPromptInfo.setReceiveSystemEvents(set);
return this;
@@ -445,7 +442,6 @@
* @hide
*/
@NonNull
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState);
return this;
@@ -458,12 +454,10 @@
* @hide
*/
@NonNull
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
public Builder setIsForLegacyFingerprintManager(int sensorId) {
mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
return this;
}
- // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
/**
* Creates a {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 77df01b..02aad1d 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -126,7 +126,6 @@
return false;
}
- // LINT.IfChange
public boolean containsPrivateApiConfigurations() {
if (mDisallowBiometricsIfPolicyExists) {
return true;
@@ -142,14 +141,9 @@
return true;
} else if (mReceiveSystemEvents) {
return true;
- } else if (mIgnoreEnrollmentState) {
- return true;
- } else if (mIsForLegacyFingerprintManager) {
- return true;
}
return false;
}
- // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
// Setters
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 9d5073e..7080133 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -177,5 +177,5 @@
// Internal operation used to clear face biometric scheduler.
// Ensures that the scheduler is not stuck.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void scheduleWatchdog();
+ oneway void scheduleWatchdog();
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 9975852..e2840ec 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -213,5 +213,5 @@
// Internal operation used to clear fingerprint biometric scheduler.
// Ensures that the scheduler is not stuck.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void scheduleWatchdog();
+ oneway void scheduleWatchdog();
}
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index efe3fd4..a67d839 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -31,6 +31,7 @@
import android.view.ContextMenu.ContextMenuInfo;
import android.view.SoundEffectConstants;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ExpandableListConnector.PositionMetadata;
@@ -660,7 +661,11 @@
// Internally handle the item click
final int adjustedPosition = getFlatPositionForConnector(position);
- return handleItemClick(v, adjustedPosition, id);
+ final boolean clicked = handleItemClick(v, adjustedPosition, id);
+ if (v != null) {
+ v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+ }
+ return clicked;
}
/**
@@ -1150,13 +1155,16 @@
public void onInitializeAccessibilityNodeInfoForItem(
View view, int position, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
-
final PositionMetadata metadata = mConnector.getUnflattenedPos(position);
if (metadata.position.type == ExpandableListPosition.GROUP) {
- if (isGroupExpanded(metadata.position.groupPos)) {
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
- } else {
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
+ if (view != null && view.isEnabled()) {
+ info.setClickable(true);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+ if (isGroupExpanded(metadata.position.groupPos)) {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+ } else {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
+ }
}
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index b65c1a1..cb5dbe6 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1550,6 +1550,7 @@
float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR
* mEdgeGlowTop.onPullDistance(deltaDistance, 0.5f));
+ mEdgeGlowTop.onRelease();
if (consumed != unconsumed) {
mEdgeGlowTop.finish();
}
@@ -1560,6 +1561,7 @@
float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size;
int consumed = Math.round(size / FLING_DESTRETCH_FACTOR
* mEdgeGlowBottom.onPullDistance(deltaDistance, 0.5f));
+ mEdgeGlowBottom.onRelease();
if (consumed != unconsumed) {
mEdgeGlowBottom.finish();
}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index d78689e9..b7a2c71 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -95,7 +95,7 @@
@UnsupportedAppUsage
public void register(Context context, Looper thread, UserHandle user,
boolean externalStorage) {
- register(context, user, externalStorage,
+ register(context, user,
(thread == null) ? BackgroundThread.getHandler() : new Handler(thread));
}
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 65e5e29..c88763c 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -64,9 +64,9 @@
// is represented as a byte array of length 0.
private byte[] mCredential;
- // This indicates that the credential is a password that used characters outside ASCII 32–127.
+ // This indicates that the credential used characters outside ASCII 32–127.
//
- // Such passwords were never intended to be allowed. However, Android 10–14 had a bug where
+ // Such credentials were never intended to be allowed. However, Android 10–14 had a bug where
// conversion from the chars the user entered to the credential bytes used a simple truncation.
// Thus, any 'char' whose remainder mod 256 was in the range 32–127 was accepted and was
// equivalent to some ASCII character. For example, ™, which is U+2122, was truncated to ASCII
@@ -102,7 +102,6 @@
// LockscreenCredential object to be constructed so that the validation logic can run,
// even though the validation logic will ultimately reject the credential as too short.
}
- Preconditions.checkArgument(!hasInvalidChars || type == CREDENTIAL_TYPE_PASSWORD);
mType = type;
mCredential = credential;
mHasInvalidChars = hasInvalidChars;
@@ -262,6 +261,9 @@
* short
*/
public void validateBasicRequirements() {
+ if (mHasInvalidChars) {
+ throw new IllegalArgumentException("credential contains invalid characters");
+ }
switch (getType()) {
case CREDENTIAL_TYPE_PATTERN:
if (size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
@@ -276,9 +278,6 @@
}
break;
case CREDENTIAL_TYPE_PASSWORD:
- if (mHasInvalidChars) {
- throw new IllegalArgumentException("password contains invalid characters");
- }
if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
throw new IllegalArgumentException("password must be at least "
+ LockPatternUtils.MIN_LOCK_PASSWORD_SIZE + " characters long.");
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 9017d58..da308b2 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -292,21 +292,31 @@
}
public:
- static NativeLibrariesIterator* create(ZipFileRO* zipFile, bool debuggable) {
- void* cookie = nullptr;
+ static base::expected<std::unique_ptr<NativeLibrariesIterator>, int32_t> create(
+ ZipFileRO* zipFile, bool debuggable) {
// Do not specify a suffix to find both .so files and gdbserver.
- if (!zipFile->startIteration(&cookie, APK_LIB.data(), nullptr /* suffix */)) {
- return nullptr;
+ auto result = zipFile->startIterationOrError(APK_LIB.data(), nullptr /* suffix */);
+ if (!result.ok()) {
+ return base::unexpected(result.error());
}
- return new NativeLibrariesIterator(zipFile, debuggable, cookie);
+ return std::unique_ptr<NativeLibrariesIterator>(
+ new NativeLibrariesIterator(zipFile, debuggable, result.value()));
}
- ZipEntryRO next() {
- ZipEntryRO next = nullptr;
- while ((next = mZipFile->nextEntry(mCookie)) != nullptr) {
+ base::expected<ZipEntryRO, int32_t> next() {
+ ZipEntryRO nextEntry;
+ while (true) {
+ auto next = mZipFile->nextEntryOrError(mCookie);
+ if (!next.ok()) {
+ return base::unexpected(next.error());
+ }
+ nextEntry = next.value();
+ if (nextEntry == nullptr) {
+ break;
+ }
// Make sure this entry has a filename.
- if (mZipFile->getEntryFileName(next, fileName, sizeof(fileName))) {
+ if (mZipFile->getEntryFileName(nextEntry, fileName, sizeof(fileName))) {
continue;
}
@@ -317,7 +327,7 @@
}
}
- return next;
+ return nextEntry;
}
inline const char* currentEntry() const {
@@ -348,19 +358,28 @@
return INSTALL_FAILED_INVALID_APK;
}
- std::unique_ptr<NativeLibrariesIterator> it(
- NativeLibrariesIterator::create(zipFile, debuggable));
- if (it.get() == nullptr) {
+ auto result = NativeLibrariesIterator::create(zipFile, debuggable);
+ if (!result.ok()) {
return INSTALL_FAILED_INVALID_APK;
}
+ std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));
const ScopedUtfChars cpuAbi(env, javaCpuAbi);
if (cpuAbi.c_str() == nullptr) {
// This would've thrown, so this return code isn't observable by Java.
return INSTALL_FAILED_INVALID_APK;
}
- ZipEntryRO entry = nullptr;
- while ((entry = it->next()) != nullptr) {
+
+ while (true) {
+ auto next = it->next();
+ if (!next.ok()) {
+ return INSTALL_FAILED_INVALID_APK;
+ }
+ auto entry = next.value();
+ if (entry == nullptr) {
+ break;
+ }
+
const char* fileName = it->currentEntry();
const char* lastSlash = it->lastSlash();
@@ -388,11 +407,11 @@
return INSTALL_FAILED_INVALID_APK;
}
- std::unique_ptr<NativeLibrariesIterator> it(
- NativeLibrariesIterator::create(zipFile, debuggable));
- if (it.get() == nullptr) {
+ auto result = NativeLibrariesIterator::create(zipFile, debuggable);
+ if (!result.ok()) {
return INSTALL_FAILED_INVALID_APK;
}
+ std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));
const int numAbis = env->GetArrayLength(supportedAbisArray);
@@ -402,9 +421,17 @@
supportedAbis.emplace_back(env, (jstring)env->GetObjectArrayElement(supportedAbisArray, i));
}
- ZipEntryRO entry = nullptr;
int status = NO_NATIVE_LIBRARIES;
- while ((entry = it->next()) != nullptr) {
+ while (true) {
+ auto next = it->next();
+ if (!next.ok()) {
+ return INSTALL_FAILED_INVALID_APK;
+ }
+ auto entry = next.value();
+ if (entry == nullptr) {
+ break;
+ }
+
// We're currently in the lib/ directory of the APK, so it does have some native
// code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the
// libraries match.
diff --git a/core/res/res/values-watch/dimens.xml b/core/res/res/values-watch/dimens.xml
index c7caa39..cf75e7f 100644
--- a/core/res/res/values-watch/dimens.xml
+++ b/core/res/res/values-watch/dimens.xml
@@ -25,4 +25,9 @@
<dimen name="aerr_list_item_height">52dp</dimen>
<!-- Padding for contents in a view of BaseErrorDialog such as a title and buttons -->
<dimen name="base_error_dialog_contents_padding">14dp</dimen>
+
+ <!-- The width/height of the icon view on staring surface. -->
+ <dimen name="starting_surface_icon_size">48dp</dimen>
+ <!-- The default width/height of the icon on the spec of adaptive icon drawable. -->
+ <dimen name="starting_surface_default_icon_size">48dp</dimen>
</resources>
diff --git a/core/tests/coretests/src/android/app/KeyguardManagerTest.java b/core/tests/coretests/src/android/app/KeyguardManagerTest.java
index 958906c..ed8b288 100644
--- a/core/tests/coretests/src/android/app/KeyguardManagerTest.java
+++ b/core/tests/coretests/src/android/app/KeyguardManagerTest.java
@@ -174,6 +174,22 @@
}
@Test
+ public void setLock_validatesCredential() {
+ // setLock() should validate the credential before setting it. Test one example, which is
+ // that PINs must contain only ASCII digits 0-9, i.e. bytes 48-57. Using bytes 0-9 is
+ // incorrect and should *not* be accepted.
+ byte[] invalidPin = new byte[] { 1, 2, 3, 4 };
+ byte[] validPin = "1234".getBytes();
+
+ assertFalse(mKeyguardManager.setLock(KeyguardManager.PIN, invalidPin, -1, null));
+ assertFalse(mKeyguardManager.isDeviceSecure());
+
+ assertTrue(mKeyguardManager.setLock(KeyguardManager.PIN, validPin, -1, null));
+ assertTrue(mKeyguardManager.isDeviceSecure());
+ assertTrue(mKeyguardManager.setLock(-1, null, KeyguardManager.PIN, validPin));
+ }
+
+ @Test
public void checkLock_correctCredentials() {
// Set to `true` to behave as if SET_INITIAL_LOCK permission had been granted.
doReturn(true).when(mKeyguardManager).checkInitialLockMethodUsage();
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 84c5400..3c47630 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -42,6 +42,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.PasswordValidationError;
@@ -419,6 +420,21 @@
PasswordValidationError.TOO_SHORT, 6);
}
+ private LockscreenCredential createPattern(String patternString) {
+ return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+ patternString.getBytes()));
+ }
+
+ @Test
+ public void testValidateCredential_pattern() {
+ PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ assertValidationErrors(
+ validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, createPattern("123")),
+ PasswordValidationError.TOO_SHORT, 4);
+ assertValidationErrors(
+ validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, createPattern("1234")));
+ }
+
/**
* @param expected sequence of validation error codes followed by requirement values, must have
* even number of elements. Empty means no errors.
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 944e2b0..59a1643 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -187,7 +187,7 @@
for (int i = 0; s.step() && i < 5; i++) {
boolean r = t.step();
assertTrue(r);
- assertEquals(t.getInt(0), s.getInt(0));
+ assertEquals(t.getColumnInt(0), s.getColumnInt(0));
found++;
}
assertFalse(t.step());
@@ -240,7 +240,7 @@
try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
boolean r = s.step();
assertTrue(r);
- int rows = s.getInt(0);
+ int rows = s.getColumnInt(0);
assertEquals(10, rows);
}
mDatabase.setTransactionSuccessful();
@@ -254,10 +254,10 @@
final String query = "SELECT i, d, t FROM t1 WHERE t = 'text03value'";
try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
assertTrue(s.step());
- assertEquals(3, s.getResultColumnsCount());
- int vi = s.getInt(0);
- double vd = s.getDouble(1);
- String vt = s.getText(2);
+ assertEquals(3, s.getResultColumnCount());
+ int vi = s.getColumnInt(0);
+ double vd = s.getColumnDouble(1);
+ String vt = s.getColumnText(2);
assertEquals(3 * 3, vi);
assertEquals(2.5 * 3, vd, 0.1);
assertEquals("text03value", vt);
@@ -273,10 +273,10 @@
final String query = "SELECT i, d, t FROM t1 WHERE i == 20";
try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
assertTrue(s.step());
- assertEquals(3, s.getResultColumnsCount());
- assertEquals(20, s.getInt(0));
- assertEquals(0.0, s.getDouble(1), 0.01);
- assertEquals(null, s.getText(2));
+ assertEquals(3, s.getResultColumnCount());
+ assertEquals(20, s.getColumnInt(0));
+ assertEquals(0.0, s.getColumnDouble(1), 0.01);
+ assertEquals(null, s.getColumnText(2));
// No more rows.
assertFalse(s.step());
}
@@ -383,7 +383,7 @@
// Verify that a statement cannot be accessed once closed.
try {
- s.getResultColumnsCount();
+ s.getResultColumnCount();
fail("accessed closed statement");
} catch (AssertionError e) {
// Pass on the fail from the try-block before the generic catch below can see it.
@@ -495,7 +495,7 @@
// Fetch the entire reference array.
s.bindInt(1, 1);
assertTrue(s.step());
- byte[] a = s.getBlob(0);
+ byte[] a = s.getColumnBlob(0);
assertTrue(Arrays.equals(src, a));
s.reset();
@@ -503,21 +503,21 @@
s.bindInt(1, 2);
assertTrue(s.step());
byte[] c = new byte[src.length];
- assertEquals(8, s.getBlob(0, c, 0, c.length, 0));
+ assertEquals(8, s.readColumnBlob(0, c, 0, c.length, 0));
assertTrue(Arrays.equals(src, 4, 4+8, c, 0, 0+8));
s.reset();
// Fetch the null.
s.bindInt(1, 3);
assertTrue(s.step());
- assertEquals(null, s.getBlob(0));
+ assertEquals(null, s.getColumnBlob(0));
s.reset();
// Fetch the null and ensure the buffer is not modified.
for (int i = 0; i < c.length; i++) c[i] = 0;
s.bindInt(1, 3);
assertTrue(s.step());
- assertEquals(0, s.getBlob(0, c, 0, c.length, 0));
+ assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0));
for (int i = 0; i < c.length; i++) assertEquals(0, c[i]);
s.reset();
}
@@ -553,17 +553,17 @@
mDatabase.beginTransaction();
try {
try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
- assertEquals(3, s.bindParameterCount());
+ assertEquals(3, s.getParameterCount());
- assertEquals(1, s.bindParameterIndex(":1"));
- assertEquals(2, s.bindParameterIndex("?2"));
- assertEquals(3, s.bindParameterIndex("@FOO"));
- assertEquals(0, s.bindParameterIndex("@BAR"));
+ assertEquals(1, s.getParameterIndex(":1"));
+ assertEquals(2, s.getParameterIndex("?2"));
+ assertEquals(3, s.getParameterIndex("@FOO"));
+ assertEquals(0, s.getParameterIndex("@BAR"));
- assertEquals(":1", s.bindParameterName(1));
- assertEquals("?2", s.bindParameterName(2));
- assertEquals("@FOO", s.bindParameterName(3));
- assertEquals(null, s.bindParameterName(4));
+ assertEquals(":1", s.getParameterName(1));
+ assertEquals("?2", s.getParameterName(2));
+ assertEquals("@FOO", s.getParameterName(3));
+ assertEquals(null, s.getParameterName(4));
}
} finally {
mDatabase.endTransaction();
@@ -574,7 +574,7 @@
try {
try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
// Error case. The name is not supposed to be null.
- assertEquals(0, s.bindParameterIndex(null));
+ assertEquals(0, s.getParameterIndex(null));
fail("expected a NullPointerException");
}
} catch (NullPointerException e) {
@@ -631,7 +631,7 @@
int found = 0;
try (var s = mDatabase.createRawStatement(query)) {
for (int i = 0; s.step(); i++) {
- int vi = s.getInt(0);
+ int vi = s.getColumnInt(0);
int expected = i * 3;
assertEquals(expected, vi);
found = i;
@@ -705,7 +705,7 @@
for (int i = 0; i < loops; i++) {
try (var s = mDatabase.createRawStatement(query)) {
assertTrue(s.step());
- int vi = s.getInt(0);
+ int vi = s.getColumnInt(0);
int expected = 0;
assertEquals(expected, vi);
}
@@ -775,7 +775,7 @@
for (int i = 0; i < size; i++) {
assertTrue(s.step());
for (int j = 0; j < 12; j++) {
- assertEquals(s.getInt(j), wideVal(i, j));
+ assertEquals(s.getColumnInt(j), wideVal(i, j));
}
}
}
@@ -820,7 +820,7 @@
long start = SystemClock.uptimeMillis();
try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
while (s.step()) {
- s.getInt(0);
+ s.getColumnInt(0);
}
}
long elapsed = SystemClock.uptimeMillis() - start;
@@ -866,7 +866,7 @@
// No row is returned by this query.
assertFalse(r);
s.reset();
- assertEquals(i + 1, mDatabase.lastInsertRowId());
+ assertEquals(i + 1, mDatabase.getLastInsertRowId());
}
}
mDatabase.setTransactionSuccessful();
@@ -889,7 +889,7 @@
// No row is returned by this query.
assertFalse(r);
s.reset();
- assertEquals(size + i + 1, mDatabase.lastInsertRowId());
+ assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
}
}
mDatabase.setTransactionSuccessful();
@@ -920,12 +920,12 @@
mDatabase.beginTransactionReadOnly();
try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
- assertEquals(1, s.bindParameterIndex(head));
- assertEquals(head, s.bindParameterName(1));
+ assertEquals(1, s.getParameterIndex(head));
+ assertEquals(head, s.getParameterName(1));
s.bindInt(1, 20);
assertTrue(s.step());
- assertEquals(2, s.getInt(0));
- assertEquals(cat, s.getName(0));
+ assertEquals(2, s.getColumnInt(0));
+ assertEquals(cat, s.getColumnName(0));
} finally {
mDatabase.endTransaction();
}
diff --git a/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java
index 5290478..7ccbf1d 100644
--- a/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java
@@ -63,23 +63,20 @@
public void testPackageMonitorMultipleRegisterThrowsException() throws Exception {
PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
- spyPackageMonitor.register(mMockContext, UserHandle.ALL, false /* externalStorage */,
- mMockHandler);
+ spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
assertThat(spyPackageMonitor.getRegisteredHandler()).isEqualTo(mMockHandler);
verify(mMockContext, times(2)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
eq(null), eq(mMockHandler));
assertThrows(IllegalStateException.class,
- () -> spyPackageMonitor.register(mMockContext, UserHandle.ALL,
- false /* externalStorage */, mMockHandler));
+ () -> spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler));
}
@Test
public void testPackageMonitorRegisterMultipleUnRegisterThrowsException() throws Exception {
PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
- spyPackageMonitor.register(mMockContext, UserHandle.ALL, false /* externalStorage */,
- mMockHandler);
+ spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
spyPackageMonitor.unregister();
assertThrows(IllegalStateException.class, spyPackageMonitor::unregister);
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
index 12abaa9..5692742 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -158,6 +158,16 @@
}
@Test
+ public void testPinWithInvalidChars() {
+ LockscreenCredential pin = LockscreenCredential.createPin("\n\n\n\n");
+ assertTrue(pin.hasInvalidChars());
+ try {
+ pin.validateBasicRequirements();
+ fail("should not be able to set PIN with invalid chars");
+ } catch (IllegalArgumentException expected) { }
+ }
+
+ @Test
public void testSanitize() {
LockscreenCredential password = LockscreenCredential.createPassword("password");
password.zeroize();
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 14e8253..3d4b55a 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -228,12 +228,12 @@
<dimen name="bubble_user_education_stack_padding">16dp</dimen>
<!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
<dimen name="bubblebar_size">72dp</dimen>
- <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
- <dimen name="bubble_bar_expanded_view_handle_size">40dp</dimen>
- <!-- The width of the drag handle shown along with a bubble bar expanded view. -->
- <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen>
- <!-- The height of the drag handle shown along with a bubble bar expanded view. -->
- <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
+ <!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
+ <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+ <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
+ <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
+ <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
+ <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen>
<!-- Minimum width of the bubble bar manage menu. -->
<dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
<!-- Size of the dismiss icon in the bubble bar manage menu. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index f729d02..e97390d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -25,6 +25,8 @@
import android.util.Log;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.bubbles.BubbleOverflow;
@@ -111,7 +113,8 @@
/**
* Animates the provided bubble's expanded view to the expanded state.
*/
- public void animateExpansion(BubbleViewProvider expandedBubble) {
+ public void animateExpansion(BubbleViewProvider expandedBubble,
+ @Nullable Runnable afterAnimation) {
mExpandedBubble = expandedBubble;
if (mExpandedBubble == null) {
return;
@@ -160,6 +163,9 @@
bev.setAnimationMatrix(null);
updateExpandedView();
bev.setSurfaceZOrderedOnTop(false);
+ if (afterAnimation != null) {
+ afterAnimation.run();
+ }
})
.start();
}
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 396aa0e..6b6d6ba 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
@@ -16,12 +16,12 @@
package com.android.wm.shell.bubbles.bar;
-import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -63,7 +63,8 @@
private @Nullable TaskView mTaskView;
private @Nullable BubbleOverflowContainerView mOverflowView;
- private int mHandleHeight;
+ private int mCaptionHeight;
+
private int mBackgroundColor;
private float mCornerRadius = 0f;
@@ -97,8 +98,8 @@
super.onFinishInflate();
Context context = getContext();
setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
- mHandleHeight = context.getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_handle_size);
+ mCaptionHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_height);
addView(mHandleView);
applyThemeAttrs();
setClipToOutline(true);
@@ -136,6 +137,9 @@
addView(mTaskView);
mTaskView.setEnableSurfaceClipping(true);
mTaskView.setCornerRadius(mCornerRadius);
+
+ // Handle view needs to draw on top of task view.
+ bringChildToFront(mHandleView);
}
mMenuViewController = new BubbleBarMenuViewController(mContext, this);
mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
@@ -169,6 +173,10 @@
});
}
+ public BubbleBarHandleView getHandleView() {
+ return mHandleView;
+ }
+
// TODO (b/275087636): call this when theme/config changes
/** Updates the view based on the current theme. */
public void applyThemeAttrs() {
@@ -183,12 +191,12 @@
ta.recycle();
- mHandleHeight = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_handle_size);
+ mCaptionHeight = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_height);
if (mTaskView != null) {
mTaskView.setCornerRadius(mCornerRadius);
- updateHandleAndBackgroundColor(true /* animated */);
+ updateHandleColor(true /* animated */);
}
}
@@ -196,13 +204,12 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
- int menuViewHeight = Math.min(mHandleHeight, height);
+ int menuViewHeight = Math.min(mCaptionHeight, height);
measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
MeasureSpec.getMode(heightMeasureSpec)));
if (mTaskView != null) {
- int taskViewHeight = height - menuViewHeight;
- measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
+ measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
MeasureSpec.getMode(heightMeasureSpec)));
}
}
@@ -210,19 +217,20 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- // Drag handle above
- final int dragHandleBottom = t + mHandleView.getMeasuredHeight();
- mHandleView.layout(l, t, r, dragHandleBottom);
+ final int captionBottom = t + mCaptionHeight;
if (mTaskView != null) {
- mTaskView.layout(l, dragHandleBottom, r,
- dragHandleBottom + mTaskView.getMeasuredHeight());
+ mTaskView.layout(l, t, r,
+ t + mTaskView.getMeasuredHeight());
+ mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
}
+ // Handle draws on top of task view in the caption area.
+ mHandleView.layout(l, t, r, captionBottom);
}
@Override
public void onTaskCreated() {
setContentVisibility(true);
- updateHandleAndBackgroundColor(false /* animated */);
+ updateHandleColor(false /* animated */);
}
@Override
@@ -298,33 +306,20 @@
}
/**
- * Updates the background color to match with task view status/bg color, and sets handle color
- * to contrast with the background
+ * Updates the handle color based on the task view status bar or background color; if those
+ * are transparent it defaults to the background color pulled from system theme attributes.
*/
- private void updateHandleAndBackgroundColor(boolean animated) {
- if (mTaskView == null) return;
- final int color = getTaskViewColor();
- final boolean isRegionDark = Color.luminance(color) <= 0.5;
- mHandleView.updateHandleColor(isRegionDark, animated);
- setBackgroundColor(color);
- }
-
- /**
- * Retrieves task view status/nav bar color or background if available
- *
- * TODO (b/283075226): Update with color sampling when
- * RegionSamplingHelper or alternative is available
- */
- private @ColorInt int getTaskViewColor() {
- if (mTaskView == null || mTaskView.getTaskInfo() == null) return mBackgroundColor;
+ private void updateHandleColor(boolean animated) {
+ if (mTaskView == null || mTaskView.getTaskInfo() == null) return;
+ int color = mBackgroundColor;
ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
- return taskDescription.getStatusBarColor();
+ color = taskDescription.getStatusBarColor();
} else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
- return taskDescription.getBackgroundColor();
- } else {
- return mBackgroundColor;
+ color = taskDescription.getBackgroundColor();
}
+ final boolean isRegionDark = Color.luminance(color) <= 0.5;
+ mHandleView.updateHandleColor(isRegionDark, animated);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index ce26bc0..2b7a070 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -21,7 +21,8 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Outline;
-import android.graphics.Rect;
+import android.graphics.Path;
+import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
@@ -37,8 +38,12 @@
public class BubbleBarHandleView extends View {
private static final long COLOR_CHANGE_DURATION = 120;
- private int mHandleWidth;
- private int mHandleHeight;
+ // The handle view is currently rendered as 3 evenly spaced dots.
+ private int mDotSize;
+ private int mDotSpacing;
+ // Path used to draw the dots
+ private final Path mPath = new Path();
+
private @ColorInt int mHandleLightColor;
private @ColorInt int mHandleDarkColor;
private @Nullable ObjectAnimator mColorChangeAnim;
@@ -58,11 +63,10 @@
public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
-
- mHandleWidth = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_handle_width);
- mHandleHeight = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_handle_height);
+ mDotSize = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_dot_size);
+ mDotSpacing = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_dot_spacing);
mHandleLightColor = ContextCompat.getColor(getContext(),
R.color.bubble_bar_expanded_view_handle_light);
mHandleDarkColor = ContextCompat.getColor(getContext(),
@@ -74,13 +78,26 @@
public void getOutline(View view, Outline outline) {
final int handleCenterX = view.getWidth() / 2;
final int handleCenterY = view.getHeight() / 2;
- final float handleRadius = mHandleHeight / 2f;
- Rect handleBounds = new Rect(
- handleCenterX - mHandleWidth / 2,
- handleCenterY - mHandleHeight / 2,
- handleCenterX + mHandleWidth / 2,
- handleCenterY + mHandleHeight / 2);
- outline.setRoundRect(handleBounds, handleRadius);
+ final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2;
+ final int handleLeft = handleCenterX - handleTotalWidth / 2;
+ final int handleTop = handleCenterY - mDotSize / 2;
+ final int handleBottom = handleTop + mDotSize;
+ RectF dot1 = new RectF(
+ handleLeft, handleTop,
+ handleLeft + mDotSize, handleBottom);
+ RectF dot2 = new RectF(
+ dot1.right + mDotSpacing, handleTop,
+ dot1.right + mDotSpacing + mDotSize, handleBottom
+ );
+ RectF dot3 = new RectF(
+ dot2.right + mDotSpacing, handleTop,
+ dot2.right + mDotSpacing + mDotSize, handleBottom
+ );
+ mPath.reset();
+ mPath.addOval(dot1, Path.Direction.CW);
+ mPath.addOval(dot2, Path.Direction.CW);
+ mPath.addOval(dot3, Path.Direction.CW);
+ outline.setPath(mPath);
}
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index d20b33e..8ead18b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -24,6 +24,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
@@ -68,6 +69,10 @@
private final Region mTouchableRegion = new Region();
private final Rect mTempRect = new Rect();
+ // Used to ensure touch target size for the menu shown on a bubble expanded view
+ private TouchDelegate mHandleTouchDelegate;
+ private final Rect mHandleTouchBounds = new Rect();
+
public BubbleBarLayerView(Context context, BubbleController controller) {
super(context);
mBubbleController = controller;
@@ -164,7 +169,17 @@
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
- mAnimationHelper.animateExpansion(mExpandedBubble);
+ mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
+ if (mExpandedView == null) return;
+ // Touch delegate for the menu
+ BubbleBarHandleView view = mExpandedView.getHandleView();
+ view.getBoundsOnScreen(mHandleTouchBounds);
+ mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
+ mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
+ mExpandedView.getHandleView());
+ setTouchDelegate(mHandleTouchDelegate);
+ });
+
showScrim(true);
}
@@ -175,6 +190,7 @@
mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
+ setTouchDelegate(null);
showScrim(false);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 262d487..2dbc444 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -434,7 +434,7 @@
"Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
from);
mInteractive = interactive;
- if (!mInteractive && mMoving) {
+ if (!mInteractive && hideHandle && mMoving) {
final int position = mSplitLayout.getDividePosition();
mSplitLayout.flingDividePosition(
mLastDraggingPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index a9ccdf6..2b10377 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -323,7 +323,11 @@
}
}
if (mShown) {
- fadeOutDecor(()-> animFinishedCallback.accept(true));
+ fadeOutDecor(()-> {
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.accept(true);
+ }
+ });
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f70d3ae..e8fa638 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -593,9 +593,6 @@
void flingDividePosition(int from, int to, int duration,
@Nullable Runnable flingFinishedCallback) {
if (from == to) {
- // No animation run, still callback to stop resizing.
- mSplitLayoutHandler.onLayoutSizeChanged(this);
-
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
@@ -773,15 +770,13 @@
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
boolean boundsChanged = false;
if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
- wct.setBounds(task1.token, mBounds1);
- wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
+ setTaskBounds(wct, task1, mBounds1);
mWinBounds1.set(mBounds1);
mWinToken1 = task1.token;
boundsChanged = true;
}
if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
- wct.setBounds(task2.token, mBounds2);
- wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
+ setTaskBounds(wct, task2, mBounds2);
mWinBounds2.set(mBounds2);
mWinToken2 = task2.token;
boundsChanged = true;
@@ -789,6 +784,13 @@
return boundsChanged;
}
+ /** Set bounds to the {@link WindowContainerTransaction} for single task. */
+ public void setTaskBounds(WindowContainerTransaction wct,
+ ActivityManager.RunningTaskInfo task, Rect bounds) {
+ wct.setBounds(task.token, bounds);
+ wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds));
+ }
+
private int getSmallestWidthDp(Rect bounds) {
mTempRect.set(bounds);
mTempRect.inset(getDisplayStableInsets(mContext));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 40ea276..6d14440 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -63,6 +63,7 @@
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -204,7 +205,11 @@
* Moves a single task to freeform and sets the taskBounds to the passed in bounds,
* startBounds
*/
- fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) {
+ fun moveToFreeform(
+ taskInfo: RunningTaskInfo,
+ startBounds: Rect,
+ dragToDesktopValueAnimator: MoveToDesktopAnimator
+ ) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToFreeform with bounds taskId=%d",
@@ -216,8 +221,8 @@
wct.setBounds(taskInfo.token, startBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
+ dragToDesktopValueAnimator, mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -270,7 +275,7 @@
* Move a task to fullscreen after being dragged from fullscreen and released back into
* status bar area
*/
- fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) {
+ fun cancelMoveToFreeform(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: cancelMoveToFreeform taskId=%d",
@@ -280,8 +285,8 @@
wct.setBounds(task.token, null)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(
- wct, position) { t ->
+ enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
+ moveToDesktopAnimator) { t ->
val callbackWCT = WindowContainerTransaction()
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3e175f3..650cac5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -22,9 +22,10 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
-import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
+import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -35,6 +36,7 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
import java.util.ArrayList;
import java.util.List;
@@ -47,18 +49,17 @@
*/
public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "EnterDesktopTaskTransitionHandler";
private final Transitions mTransitions;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
- // The size of the screen during drag relative to the fullscreen size
- public static final float DRAG_FREEFORM_SCALE = 0.4f;
// The size of the screen after drag relative to the fullscreen size
public static final float FINAL_FREEFORM_SCALE = 0.6f;
public static final int FREEFORM_ANIMATION_DURATION = 336;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
- private Point mPosition;
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
+ private MoveToDesktopAnimator mMoveToDesktopAnimator;
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
@@ -87,15 +88,30 @@
}
/**
+ * Starts Transition of type TRANSIT_ENTER_FREEFORM
+ * @param wct WindowContainerTransaction for transition
+ * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+ * to desktop animation
+ * @param onAnimationEndCallback to be called after animation
+ */
+ public void startMoveToFreeformAnimation(@NonNull WindowContainerTransaction wct,
+ @NonNull MoveToDesktopAnimator moveToDesktopAnimator,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ mMoveToDesktopAnimator = moveToDesktopAnimator;
+ startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, onAnimationEndCallback);
+ }
+
+ /**
* Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
* @param wct WindowContainerTransaction for transition
- * @param position Position of task when transition is triggered
+ * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+ * to desktop animation
* @param onAnimationEndCallback to be called after animation
*/
public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
- Point position,
+ MoveToDesktopAnimator moveToDesktopAnimator,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
- mPosition = position;
+ mMoveToDesktopAnimator = moveToDesktopAnimator;
startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
onAnimationEndCallback);
}
@@ -145,9 +161,23 @@
// to null and we don't require an animation
final SurfaceControl sc = change.getLeash();
startT.setWindowCrop(sc, null);
+
+ if (mMoveToDesktopAnimator == null
+ || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+ Slog.e(TAG, "No animator available for this transition");
+ return false;
+ }
+
+ // Calculate and set position of the task
+ final PointF position = mMoveToDesktopAnimator.getPosition();
+ startT.setPosition(sc, position.x, position.y);
+ finishT.setPosition(sc, position.x, position.y);
+
startT.apply();
+
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null, null));
+
return true;
}
@@ -162,12 +192,18 @@
endBounds.height());
startT.apply();
+ // End the animation that shrinks the window when task is first dragged from fullscreen
+ if (mMoveToDesktopAnimator != null) {
+ mMoveToDesktopAnimator.endAnimator();
+ }
+
// We want to find the scale of the current bounds relative to the end bounds. The
// task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
// scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
// DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
final ValueAnimator animator =
- ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
+ ValueAnimator.ofFloat(
+ MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
animator.setDuration(FREEFORM_ANIMATION_DURATION);
final SurfaceControl.Transaction t = mTransactionSupplier.get();
animator.addUpdateListener(animation -> {
@@ -199,8 +235,7 @@
}
if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && mPosition != null) {
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
// This Transition animates a task to fullscreen after being dragged from the status
// bar and then released back into the status bar area
final SurfaceControl sc = change.getLeash();
@@ -210,13 +245,27 @@
.setWindowCrop(sc, endBounds.width(), endBounds.height())
.apply();
+ if (mMoveToDesktopAnimator == null
+ || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+ Slog.e(TAG, "No animator available for this transition");
+ return false;
+ }
+
+ // End the animation that shrinks the window when task is first dragged from fullscreen
+ mMoveToDesktopAnimator.endAnimator();
+
final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
+ animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
animator.setDuration(FREEFORM_ANIMATION_DURATION);
final SurfaceControl.Transaction t = mTransactionSupplier.get();
+
+ // Get position of the task
+ final float x = mMoveToDesktopAnimator.getPosition().x;
+ final float y = mMoveToDesktopAnimator.getPosition().y;
+
animator.addUpdateListener(animation -> {
final float scale = (float) animation.getAnimatedValue();
- t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale))
+ t.setPosition(sc, x * (1 - scale), y * (1 - scale))
.setScale(sc, scale, scale)
.show(sc)
.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index e1737ec..3f3951a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -81,6 +81,7 @@
super(context, pipSizeSpecHandler, pipDisplayLayoutState);
mContext = context;
updateDefaultGravity();
+ mTvPipGravity = mDefaultGravity;
mPreviousCollapsedGravity = mDefaultGravity;
mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 837f118..39b6675 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -573,10 +573,12 @@
}
final boolean isRootTask = taskInfo != null
&& TransitionInfo.isIndependent(change, info);
+ final boolean isRecentsTask = mRecentsTask != null
+ && mRecentsTask.equals(change.getContainer());
hasTaskChange = hasTaskChange || isRootTask;
final boolean isLeafTask = leafTaskFilter.test(change);
if (TransitionUtil.isOpeningType(change.getMode())) {
- if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+ if (isRecentsTask) {
recentsOpening = change;
} else if (isRootTask || isLeafTask) {
if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
@@ -591,7 +593,7 @@
openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
}
} else if (TransitionUtil.isClosingType(change.getMode())) {
- if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+ if (isRecentsTask) {
foundRecentsClosing = true;
} else if (isRootTask || isLeafTask) {
if (closingTasks == null) {
@@ -612,7 +614,7 @@
if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
hasChangingApp = true;
} else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
- && !mRecentsTask.equals(change.getContainer())) {
+ && !isRecentsTask ) {
// Unless it is a 3p launcher. This means that the 3p launcher was already
// visible (eg. the "pausing" task is translucent over the 3p launcher).
// Treat it as if we are "re-opening" the 3p launcher.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 3669bce..af8ef17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -624,8 +624,8 @@
Toast.LENGTH_SHORT).show();
}
}
- mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
- splitPosition, splitRatio, remoteTransition, instanceId);
+ mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId);
}
/**
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 05a9f48..6961dab 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
@@ -28,6 +28,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
@@ -1302,20 +1303,12 @@
final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
final boolean oneStageVisible =
mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
- if (oneStageVisible) {
+ if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
// Dismiss split because there's show-when-locked activity showing on top of keyguard.
// Also make sure the task contains show-when-locked activity remains on top after split
// dismissed.
- if (!ENABLE_SHELL_TRANSITIONS) {
- final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
- exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- } else {
- final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(dismissTop, wct);
- mSplitTransitions.startDismissTransition(wct, this, dismissTop,
- EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- }
+ final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+ exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
}
@@ -1570,6 +1563,8 @@
// split bounds.
wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+ mSplitLayout.getInvisibleBounds(mTempRect1);
+ mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1);
}
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
@@ -2385,6 +2380,15 @@
// so appends operations to exit split.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
}
+ } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null
+ && isSplitScreenVisible()) {
+ // Split include show when lock activity case, check the top activity under which
+ // stage and move it to the top.
+ int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity)
+ ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ prepareExitSplitScreen(top, out);
+ mSplitTransitions.setDismissTransition(transition, top,
+ EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
// When split in the background, it should be only opening/dismissing transition and
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 944acb9..daf8be6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -189,15 +189,10 @@
if (taskView == null) return null;
// Opening types should all be initiated by shell
if (!TransitionUtil.isClosingType(request.getType())) return null;
- PendingTransition pending = findPendingCloseTransition(taskView);
- if (pending == null) {
- pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */);
- }
- if (pending.mClaimed != null) {
- throw new IllegalStateException("Task is closing in 2 collecting transitions?"
- + " This state doesn't make sense");
- }
+ PendingTransition pending = new PendingTransition(request.getType(), null,
+ taskView, null /* cookie */);
pending.mClaimed = transition;
+ mPending.add(pending);
return new WindowContainerTransaction();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 7245bc9..14f2f9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -24,9 +24,9 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
+import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -112,9 +112,8 @@
private SplitScreenController mSplitScreenController;
- private ValueAnimator mDragToDesktopValueAnimator;
+ private MoveToDesktopAnimator mMoveToDesktopAnimator;
private final Rect mDragToDesktopAnimationStartBounds = new Rect();
- private boolean mDragToDesktopAnimationStarted;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -233,7 +232,6 @@
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
-
decoration.relayout(taskInfo);
}
@@ -599,7 +597,7 @@
}
case MotionEvent.ACTION_UP: {
if (relevantDecor == null) {
- mDragToDesktopAnimationStarted = false;
+ mMoveToDesktopAnimator = null;
mTransitionDragActive = false;
return;
}
@@ -613,14 +611,14 @@
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
- mDragToDesktopAnimationStarted = false;
+ mMoveToDesktopAnimator = null;
return;
- } else if (mDragToDesktopAnimationStarted) {
- Point position = new Point((int) ev.getX(), (int) ev.getY());
+ } else if (mMoveToDesktopAnimator != null) {
relevantDecor.incrementRelayoutBlock();
mDesktopTasksController.ifPresent(
- c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, position));
- mDragToDesktopAnimationStarted = false;
+ c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator));
+ mMoveToDesktopAnimator = null;
return;
}
}
@@ -640,21 +638,19 @@
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > statusBarHeight) {
- if (!mDragToDesktopAnimationStarted) {
- mDragToDesktopAnimationStarted = true;
+ if (mMoveToDesktopAnimator == null) {
+ mMoveToDesktopAnimator = new MoveToDesktopAnimator(
+ mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
c -> c.moveToFreeform(relevantDecor.mTaskInfo,
- mDragToDesktopAnimationStartBounds));
- startAnimation(relevantDecor);
+ mDragToDesktopAnimationStartBounds,
+ mMoveToDesktopAnimator));
+ mMoveToDesktopAnimator.startAnimation();
}
}
- if (mDragToDesktopAnimationStarted) {
- Transaction t = mTransactionFactory.get();
- float width = (float) mDragToDesktopValueAnimator.getAnimatedValue()
- * mDragToDesktopAnimationStartBounds.width();
- float x = ev.getX() - (width / 2);
- t.setPosition(relevantDecor.mTaskSurface, x, ev.getY());
- t.apply();
+ if (mMoveToDesktopAnimator != null) {
+ mMoveToDesktopAnimator.updatePosition(ev);
}
}
break;
@@ -662,7 +658,7 @@
case MotionEvent.ACTION_CANCEL: {
mTransitionDragActive = false;
- mDragToDesktopAnimationStarted = false;
+ mMoveToDesktopAnimator = null;
}
}
}
@@ -729,20 +725,6 @@
animator.start();
}
- private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) {
- mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE);
- mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION);
- final Transaction t = mTransactionFactory.get();
- mDragToDesktopValueAnimator.addUpdateListener(animation -> {
- final float animatorValue = (float) animation.getAnimatedValue();
- SurfaceControl sc = focusedDecor.mTaskSurface;
- t.setScale(sc, animatorValue, animatorValue);
- t.apply();
- });
-
- mDragToDesktopValueAnimator.start();
- }
-
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 336d95e..7c6fb99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -336,6 +336,7 @@
private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents;
+ private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -437,7 +438,6 @@
break;
}
case MotionEvent.ACTION_HOVER_EXIT:
- mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
result = true;
break;
}
@@ -477,7 +477,13 @@
if (y > mTaskHeight - mTaskCornerRadius) {
ctrlType |= CTRL_TYPE_BOTTOM;
}
- return checkDistanceFromCenter(ctrlType, x, y);
+ // Check distances from the center if it's in one of four corners.
+ if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
+ && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
+ return checkDistanceFromCenter(ctrlType, x, y);
+ }
+ // Otherwise, we should make sure we don't resize tasks inside task bounds.
+ return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
}
// If corner input is not within appropriate distance of corner radius, do not use it.
@@ -511,7 +517,8 @@
break;
}
default: {
- return ctrlType;
+ throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
+ + Integer.toHexString(ctrlType));
}
}
double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
@@ -564,7 +571,19 @@
cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
break;
}
- mInputManager.setPointerIconType(cursorType);
+ // Only update the cursor type to default once so that views behind the decor container
+ // layer that aren't in the active resizing regions have chances to update the cursor
+ // type. We would like to enforce the cursor type by setting the cursor type multilple
+ // times in active regions because we shouldn't allow the views behind to change it, as
+ // we'll pilfer the gesture initiated in this area. This is necessary because 1) we
+ // should allow the views behind regions only for touches to set the cursor type; and 2)
+ // there is a small region out of each rounded corner that's inside the task bounds,
+ // where views in the task can receive input events because we can't set touch regions
+ // of input sinks to have rounded corners.
+ if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
+ mInputManager.setPointerIconType(cursorType);
+ mLastCursorType = cursorType;
+ }
}
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
new file mode 100644
index 0000000..b2267dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -0,0 +1,72 @@
+package com.android.wm.shell.windowdecor
+
+import android.animation.ValueAnimator
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.SurfaceControl
+
+/**
+ * Creates an animator to shrink and position task after a user drags a fullscreen task from
+ * the top of the screen to transition it into freeform and before the user releases the task. The
+ * MoveToDesktopAnimator object also holds information about the state of the task that are
+ * accessed by the EnterDesktopTaskTransitionHandler.
+ */
+class MoveToDesktopAnimator @JvmOverloads constructor(
+ private val startBounds: Rect,
+ private val taskInfo: RunningTaskInfo,
+ private val taskSurface: SurfaceControl,
+ private val transactionFactory: () -> SurfaceControl.Transaction =
+ SurfaceControl::Transaction
+) {
+ companion object {
+ // The size of the screen during drag relative to the fullscreen size
+ const val DRAG_FREEFORM_SCALE: Float = 0.4f
+ const val ANIMATION_DURATION = 336
+ }
+
+ private val animatedTaskWidth
+ get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width()
+ private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
+ DRAG_FREEFORM_SCALE)
+ .setDuration(ANIMATION_DURATION.toLong())
+ .apply {
+ val t = SurfaceControl.Transaction()
+ addUpdateListener { animation ->
+ val animatorValue = animation.animatedValue as Float
+ t.setScale(taskSurface, animatorValue, animatorValue)
+ .apply()
+ }
+ }
+
+ val taskId get() = taskInfo.taskId
+ val position: PointF = PointF(0.0f, 0.0f)
+
+ /**
+ * Starts the animation that scales the task down.
+ */
+ fun startAnimation() {
+ dragToDesktopAnimator.start()
+ }
+
+ /**
+ * Uses the position of the motion event and the current scale of the task as defined by the
+ * ValueAnimator to update the local position variable and set the task surface's position
+ */
+ fun updatePosition(ev: MotionEvent) {
+ position.x = ev.x - animatedTaskWidth / 2
+ position.y = ev.y
+
+ val t = transactionFactory()
+ t.setPosition(taskSurface, position.x, position.y)
+ t.apply()
+ }
+
+ /**
+ * Ends the animation, setting the scale and position to the final animation value
+ */
+ fun endAnimator() {
+ dragToDesktopAnimator.end()
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index 6fe88ca..d3f3c5b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -18,10 +18,10 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
-import android.tools.common.flicker.assertions.FlickerTest
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -45,7 +45,6 @@
* Swipe right from the bottom of the screen to quick switch back to the app
* ```
*/
-
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -152,9 +151,8 @@
@Test
fun appWindowBecomesAndStaysVisible() {
flicker.assertWm {
- this.isAppWindowInvisible(letterboxApp)
- .then()
- .isAppWindowVisible(letterboxApp) }
+ this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp)
+ }
}
/**
@@ -245,7 +243,8 @@
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
.then()
.isVisible(letterboxApp)
- .isVisible(ComponentNameMatcher.LETTERBOX) }
+ .isVisible(ComponentNameMatcher.LETTERBOX)
+ }
}
/** {@inheritDoc} */
@@ -273,4 +272,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index 8a85374..8bd44c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -66,11 +66,12 @@
AutoEnterPipOnGoToHomeTest(flicker) {
private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
/** Second app used to enter split screen mode */
- private val secondAppForSplitScreen = SimpleAppHelper(
- instrumentation,
- ActivityOptions.SplitScreen.Primary.LABEL,
- ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
- )
+ private val secondAppForSplitScreen =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Primary.LABEL,
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+ )
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..566adec
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..92b6227
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..e6d56b5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+ DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6752c58
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+ DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..7c9ab99
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..4b79571
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..0495079
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..71ef48b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c78729c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..30bce2f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b33ea7c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..07a86a5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..9a1d127
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..266e268
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..83fc30b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b2f1929
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..dae92dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..732047b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..1de7efd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..1a046aa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..6e88f0e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..d26a29c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..4a552b0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b7376ea
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b2d05e4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6de31b1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..aab18a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
+ SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b074f2c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
+ SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c402aa4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..840401c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
index 964a785..a5c5122 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -29,9 +29,7 @@
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
- @ExpectedScenarios([])
- @Test
- override fun copyContentInSplit() = super.copyContentInSplit()
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
companion object {
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
index bc30d41..092fb67 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -29,9 +29,7 @@
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
- @ExpectedScenarios([])
- @Test
- override fun copyContentInSplit() = super.copyContentInSplit()
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
companion object {
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index a43ad9b..1d4c4d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.EdgeExtensionComponentMatcher
@@ -28,13 +27,9 @@
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -60,21 +55,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(textEditApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(textEditApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
-
- // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index a118c08..0d967eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -26,13 +25,9 @@
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -58,19 +53,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index e0a47b3..f236c2d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -28,12 +27,9 @@
import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -59,19 +55,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 8f867df..8aaa98a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -28,15 +27,10 @@
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -62,21 +56,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(thirdApp)
- flicker.appWindowIsVisibleAtStart(fourthApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.appWindowIsInvisibleAtEnd(thirdApp)
- flicker.appWindowIsInvisibleAtEnd(fourthApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerInvisibleAtMiddle() =
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index 9c68aa4..d9d22de 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -16,18 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
@@ -54,21 +51,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 21ac783..7e8d60b4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -61,19 +57,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index 931bff6..770e032 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -47,19 +43,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index 7fa2c0b..46570fd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -16,19 +16,16 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +34,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -45,27 +42,11 @@
transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
- // robust enough to get the correct end state.
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 952051f..5c3d4ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
@@ -57,30 +53,11 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index 1de1c0c..6b122c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromNotificationBenchmark(
+abstract class EnterSplitScreenByDragFromNotificationBenchmark(
override val flicker: LegacyFlickerTest
) : SplitScreenBase(flicker) {
protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -59,21 +55,6 @@
teardown { sendNotificationApp.exit(wmHelper) }
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index 929c7ea..78f9bab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: LegacyFlickerTest) :
- SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromShortcutBenchmark(
+ override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
@@ -62,25 +59,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index 9f829c9..78907f0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -56,26 +52,6 @@
}
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index 1d5518f..2c91e84 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -56,19 +52,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index a7fb93e..fa09c2e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -16,8 +16,6 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -27,10 +25,9 @@
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +36,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,14 +50,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
wmHelper
.StateSyncBuilder()
@@ -134,14 +123,6 @@
return displayBounds.width > displayBounds.height
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
- // robust enough to get the correct end state.
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index 8358aff..ff22006 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
@@ -55,19 +51,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index b63c765..5787b02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,19 +49,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index ce5a409b..b2d5091 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,19 +49,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index 9821bfa..f234e46 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -16,17 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -35,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -64,8 +61,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {}
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 4fc4627..61c3679 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -22,8 +22,8 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -33,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -47,14 +47,6 @@
}
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 8592dea..c6642f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -41,6 +42,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
import junit.framework.AssertionFailedError;
@@ -73,6 +75,10 @@
ShellExecutor mExecutor;
@Mock
SurfaceControl mSurfaceControl;
+ @Mock
+ MoveToDesktopAnimator mMoveToDesktopAnimator;
+ @Mock
+ PointF mPosition;
private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler;
@@ -82,6 +88,7 @@
doReturn(mExecutor).when(mTransitions).getMainExecutor();
doReturn(mAnimationT).when(mTransactionFactory).get();
+ doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition();
mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions,
mTransactionFactory);
@@ -89,12 +96,15 @@
@Test
public void testEnterFreeformAnimation() {
- final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM;
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
- .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+ .startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct,
+ mEnterDesktopTaskTransitionHandler);
+ doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
+
+ mEnterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
+ mMoveToDesktopAnimator, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index f9b7723..91ff3cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -52,9 +52,8 @@
@Before
public void setUp() {
- if (!isTelevision()) {
- return;
- }
+ assumeTelevision();
+
MockitoAnnotations.initMocks(this);
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
@@ -100,20 +99,22 @@
@Test
public void regularPip_defaultGravity() {
- assumeTelevision();
checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.RIGHT | Gravity.BOTTOM);
}
@Test
+ public void regularPip_defaultTvPipGravity() {
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), Gravity.RIGHT | Gravity.BOTTOM);
+ }
+
+ @Test
public void regularPip_defaultGravity_RTL() {
- assumeTelevision();
setRTL(true);
checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.LEFT | Gravity.BOTTOM);
}
@Test
public void updateGravity_expand_vertical() {
- assumeTelevision();
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
@@ -129,7 +130,6 @@
@Test
public void updateGravity_expand_horizontal() {
- assumeTelevision();
// Horizontal expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
@@ -145,7 +145,6 @@
@Test
public void updateGravity_collapse() {
- assumeTelevision();
// Vertical expansion
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.RIGHT,
@@ -163,7 +162,6 @@
@Test
public void updateGravity_collapse_RTL() {
- assumeTelevision();
setRTL(true);
// Horizontal expansion
@@ -176,7 +174,6 @@
@Test
public void updateGravity_expand_collapse() {
- assumeTelevision();
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
@@ -196,7 +193,6 @@
@Test
public void updateGravity_expand_move_collapse() {
- assumeTelevision();
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
expandMoveCollapseCheck(Gravity.TOP | Gravity.RIGHT, KEYCODE_DPAD_LEFT,
@@ -229,7 +225,6 @@
@Test
public void updateGravity_move_regular_valid() {
- assumeTelevision();
mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.RIGHT);
// clockwise
moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.LEFT, true);
@@ -245,7 +240,6 @@
@Test
public void updateGravity_move_expanded_valid() {
- assumeTelevision();
mTvPipBoundsState.setTvPipExpanded(true);
// Vertical expanded PiP.
@@ -263,7 +257,6 @@
@Test
public void updateGravity_move_regular_invalid() {
- assumeTelevision();
int gravity = Gravity.BOTTOM | Gravity.RIGHT;
mTvPipBoundsState.setTvPipGravity(gravity);
moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
@@ -287,7 +280,6 @@
@Test
public void updateGravity_move_expanded_invalid() {
- assumeTelevision();
mTvPipBoundsState.setTvPipExpanded(true);
// Vertical expanded PiP.
@@ -317,7 +309,6 @@
@Test
public void previousCollapsedGravity_defaultValue() {
- assumeTelevision();
assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
mTvPipBoundsState.getDefaultGravity());
setRTL(true);
@@ -327,7 +318,6 @@
@Test
public void previousCollapsedGravity_changes_on_RTL() {
- assumeTelevision();
mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.TOP | Gravity.LEFT);
setRTL(true);
assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 52e7a70..d7b5914 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -40,17 +40,24 @@
public:
ZipEntry entry;
std::string_view name;
- void *cookie;
+ void *cookie = nullptr;
- _ZipEntryRO() : cookie(NULL) {}
+ _ZipEntryRO() = default;
~_ZipEntryRO() {
- EndIteration(cookie);
+ EndIteration(cookie);
+ }
+
+ android::ZipEntryRO convertToPtr() {
+ _ZipEntryRO* result = new _ZipEntryRO;
+ result->entry = std::move(this->entry);
+ result->name = std::move(this->name);
+ result->cookie = std::exchange(this->cookie, nullptr);
+ return result;
}
private:
- _ZipEntryRO(const _ZipEntryRO& other);
- _ZipEntryRO& operator=(const _ZipEntryRO& other);
+ DISALLOW_COPY_AND_ASSIGN(_ZipEntryRO);
};
ZipFileRO::~ZipFileRO() {
@@ -94,17 +101,15 @@
ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const
{
- _ZipEntryRO* data = new _ZipEntryRO;
+ _ZipEntryRO data;
+ data.name = entryName;
- data->name = entryName;
-
- const int32_t error = FindEntry(mHandle, entryName, &(data->entry));
+ const int32_t error = FindEntry(mHandle, entryName, &(data.entry));
if (error) {
- delete data;
- return NULL;
+ return nullptr;
}
- return (ZipEntryRO) data;
+ return data.convertToPtr();
}
/*
@@ -143,35 +148,50 @@
}
bool ZipFileRO::startIteration(void** cookie) {
- return startIteration(cookie, NULL, NULL);
+ return startIteration(cookie, nullptr, nullptr);
}
-bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix)
-{
- _ZipEntryRO* ze = new _ZipEntryRO;
- int32_t error = StartIteration(mHandle, &(ze->cookie),
+bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix) {
+ auto result = startIterationOrError(prefix, suffix);
+ if (!result.ok()) {
+ return false;
+ }
+ *cookie = result.value();
+ return true;
+}
+
+base::expected<void*, int32_t>
+ZipFileRO::startIterationOrError(const char* prefix, const char* suffix) {
+ _ZipEntryRO ze;
+ int32_t error = StartIteration(mHandle, &(ze.cookie),
prefix ? prefix : "", suffix ? suffix : "");
if (error) {
ALOGW("Could not start iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
ErrorCodeString(error));
- delete ze;
- return false;
+ return base::unexpected(error);
}
- *cookie = ze;
- return true;
+ return ze.convertToPtr();
}
-ZipEntryRO ZipFileRO::nextEntry(void* cookie)
-{
+ZipEntryRO ZipFileRO::nextEntry(void* cookie) {
+ auto result = nextEntryOrError(cookie);
+ if (!result.ok()) {
+ return nullptr;
+ }
+ return result.value();
+}
+
+base::expected<ZipEntryRO, int32_t> ZipFileRO::nextEntryOrError(void* cookie) {
_ZipEntryRO* ze = reinterpret_cast<_ZipEntryRO*>(cookie);
int32_t error = Next(ze->cookie, &(ze->entry), &(ze->name));
if (error) {
if (error != -1) {
ALOGW("Error iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
ErrorCodeString(error));
+ return base::unexpected(error);
}
- return NULL;
+ return nullptr;
}
return &(ze->entry);
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index 10f6d06..be1f98f 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -37,6 +37,8 @@
#include <unistd.h>
#include <time.h>
+#include <android-base/expected.h>
+
#include <util/map_ptr.h>
#include <utils/Compat.h>
@@ -102,6 +104,11 @@
*/
bool startIteration(void** cookie);
bool startIteration(void** cookie, const char* prefix, const char* suffix);
+ /*
+ * Same as above, but returns the error code in case of failure.
+ * #see libziparchive/zip_error.h.
+ */
+ base::expected<void*, int32_t> startIterationOrError(const char* prefix, const char* suffix);
/**
* Return the next entry in iteration order, or NULL if there are no more
@@ -109,6 +116,12 @@
*/
ZipEntryRO nextEntry(void* cookie);
+ /**
+ * Same as above, but returns the error code in case of failure.
+ * #see libziparchive/zip_error.h.
+ */
+ base::expected<ZipEntryRO, int32_t> nextEntryOrError(void* cookie);
+
void endIteration(void* cookie);
void releaseEntry(ZipEntryRO entry) const;
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 4c9a23d..740988f 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -90,8 +90,8 @@
requireUnpremul, prefColorSpace);
}
- bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, const SkIRect& desiredSubset,
- int sampleSize, bool requireUnpremul) {
+ bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, int outWidth, int outHeight,
+ const SkIRect& desiredSubset, int sampleSize, bool requireUnpremul) {
SkColorType decodeColorType = mGainmapBRD->computeOutputColorType(kN32_SkColorType);
sk_sp<SkColorSpace> decodeColorSpace =
mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr);
@@ -109,9 +109,8 @@
// kPremul_SkAlphaType is used just as a placeholder as it doesn't change the underlying
// allocation type. RecyclingClippingPixelAllocator will populate this with the
// actual alpha type in either allocPixelRef() or copyIfNecessary()
- sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(
- SkImageInfo::Make(desiredSubset.width(), desiredSubset.height(), decodeColorType,
- kPremul_SkAlphaType, decodeColorSpace));
+ sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+ outWidth, outHeight, decodeColorType, kPremul_SkAlphaType, decodeColorSpace));
if (!nativeBitmap) {
ALOGE("OOM allocating Bitmap for Gainmap");
return false;
@@ -134,9 +133,12 @@
return true;
}
- SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion) {
+ SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion, int* inOutWidth,
+ int* inOutHeight) {
const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width();
const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height();
+ *inOutWidth *= scaleX;
+ *inOutHeight *= scaleY;
// TODO: Account for rounding error?
return SkIRect::MakeLTRB(mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
mainImageRegion.right() * scaleX,
@@ -328,21 +330,16 @@
sp<uirenderer::Gainmap> gainmap;
bool hasGainmap = brd->hasGainmap();
if (hasGainmap) {
- SkIRect adjustedSubset{};
+ int gainmapWidth = bitmap.width();
+ int gainmapHeight = bitmap.height();
if (javaBitmap) {
- // Clamp to the width/height of the recycled bitmap in case the reused bitmap
- // was too small for the specified rectangle, in which case we need to clip
- adjustedSubset = SkIRect::MakeXYWH(inputX, inputY,
- std::min(subset.width(), recycledBitmap->width()),
- std::min(subset.height(), recycledBitmap->height()));
- } else {
- // We are not recycling, so use the decoded width/height for calculating the gainmap
- // subset instead to ensure the gainmap region proportionally matches
- adjustedSubset = SkIRect::MakeXYWH(std::max(0, inputX), std::max(0, inputY),
- bitmap.width(), bitmap.height());
+ // If we are recycling we must match the inBitmap's relative dimensions
+ gainmapWidth = recycledBitmap->width();
+ gainmapHeight = recycledBitmap->height();
}
- SkIRect gainmapSubset = brd->calculateGainmapRegion(adjustedSubset);
- if (!brd->decodeGainmapRegion(&gainmap, gainmapSubset, sampleSize, requireUnpremul)) {
+ SkIRect gainmapSubset = brd->calculateGainmapRegion(subset, &gainmapWidth, &gainmapHeight);
+ if (!brd->decodeGainmapRegion(&gainmap, gainmapWidth, gainmapHeight, gainmapSubset,
+ sampleSize, requireUnpremul)) {
// If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap
hasGainmap = false;
}
diff --git a/libs/input/OWNERS b/libs/input/OWNERS
index d701f23..4c20c4d 100644
--- a/libs/input/OWNERS
+++ b/libs/input/OWNERS
@@ -1 +1 @@
-include /core/java/android/hardware/input/OWNERS
+include /INPUT_OWNERS
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 62af39f..f27a8de 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1084,6 +1084,16 @@
return filteredRoutes;
}
+ @NonNull
+ private List<MediaRoute2Info> getRoutesWithIds(@NonNull List<String> routeIds) {
+ synchronized (mLock) {
+ return routeIds.stream()
+ .map(mRoutes::get)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+ }
+
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record : mRouteCallbackRecords) {
List<MediaRoute2Info> filteredRoutes =
@@ -1388,7 +1398,7 @@
synchronized (mControllerLock) {
selectedRouteIds = mSessionInfo.getSelectedRoutes();
}
- return mImpl.getRoutesWithIds(selectedRouteIds);
+ return getRoutesWithIds(selectedRouteIds);
}
/**
@@ -1400,7 +1410,7 @@
synchronized (mControllerLock) {
selectableRouteIds = mSessionInfo.getSelectableRoutes();
}
- return mImpl.getRoutesWithIds(selectableRouteIds);
+ return getRoutesWithIds(selectableRouteIds);
}
/**
@@ -1412,7 +1422,7 @@
synchronized (mControllerLock) {
deselectableRouteIds = mSessionInfo.getDeselectableRoutes();
}
- return mImpl.getRoutesWithIds(deselectableRouteIds);
+ return getRoutesWithIds(deselectableRouteIds);
}
/**
@@ -1981,7 +1991,6 @@
boolean shouldNotifyStop,
RoutingController controller);
- List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds);
}
/**
@@ -2412,13 +2421,6 @@
releaseSession(controller.getRoutingSessionInfo());
}
- @Override
- public List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
- return getRoutes().stream()
- .filter(r -> routeIds.contains(r.getId()))
- .collect(Collectors.toList());
- }
-
/**
* Sets the routing session's {@linkplain RoutingSessionInfo#getClientPackageName() client
* package name} to {@link #mClientPackageName} if empty and returns the session.
@@ -3065,14 +3067,5 @@
}
}
- @Override
- public List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
- synchronized (mLock) {
- return routeIds.stream()
- .map(mRoutes::get)
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
- }
- }
}
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a443b5c..73fb0f0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -65,6 +65,7 @@
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
"androidx.activity_activity-compose",
+ "androidx.compose.animation_animation-graphics",
],
// By default, Compose is disabled and we compile the ComposeFacade
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2913c16..4fd4723 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -865,7 +865,7 @@
<activity
android:name=".settings.brightness.BrightnessDialog"
android:label="@string/quick_settings_brightness_dialog_title"
- android:theme="@style/Theme.SystemUI.QuickSettings.BrightnessDialog"
+ android:theme="@style/BrightnessDialog"
android:finishOnCloseSystemDialogs="true"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 240bace..d83596e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -99,7 +99,10 @@
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(60.dp),
modifier =
- modifier.background(MaterialTheme.colorScheme.surface).fillMaxSize().padding(32.dp)
+ modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.surface)
+ .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
) {
Crossfade(
targetState = message,
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index f3a6bbe..665c612 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -121,12 +121,10 @@
<LinearLayout
android:id="@+id/shade_header_system_icons"
android:layout_width="wrap_content"
- android:layout_height="@dimen/shade_header_system_icons_height"
+ app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
android:clickable="true"
android:orientation="horizontal"
- android:gravity="center_vertical"
- android:paddingStart="@dimen/shade_header_system_icons_padding_start"
- android:paddingEnd="@dimen/shade_header_system_icons_padding_end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/privacy_container"
app:layout_constraintTop_toTopOf="@id/clock">
@@ -134,13 +132,13 @@
<com.android.systemui.statusbar.phone.StatusIconContainer
android:id="@+id/statusIcons"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:paddingEnd="@dimen/signal_cluster_battery_padding" />
<com.android.systemui.battery.BatteryMeterView
android:id="@+id/batteryRemainingIcon"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
app:textAppearance="@style/TextAppearance.QS.Status" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 50dcaf3..bb32022 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -57,7 +57,6 @@
android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
- android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
android:src="@drawable/ic_alarm"
android:tint="@android:color/white"
android:visibility="gone"
@@ -68,7 +67,6 @@
android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
- android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
android:src="@drawable/ic_qs_dnd_on"
android:tint="@android:color/white"
android:visibility="gone"
@@ -79,7 +77,6 @@
android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
- android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
android:src="@drawable/ic_signal_wifi_off"
android:visibility="gone"
android:contentDescription="@string/dream_overlay_status_bar_wifi_off" />
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 83702ea..9a2db6bfd 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -74,9 +74,6 @@
<dimen name="large_dialog_width">472dp</dimen>
<dimen name="large_screen_shade_header_height">42dp</dimen>
- <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
- <dimen name="shade_header_system_icons_padding_start">11dp</dimen>
- <dimen name="shade_header_system_icons_padding_end">12dp</dimen>
<!-- Lockscreen shade transition values -->
<dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index ce545f4..d053a7a 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -27,9 +27,6 @@
<dimen name="large_screen_shade_header_height">56dp</dimen>
- <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
- <dimen name="shade_header_system_icons_padding_start">10dp</dimen>
-
<!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
<dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b34b8f6..d9466bf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -470,10 +470,6 @@
<dimen name="large_screen_shade_header_height">48dp</dimen>
<dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
<dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
- <dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen>
- <dimen name="shade_header_system_icons_height_large_screen">32dp</dimen>
- <dimen name="shade_header_system_icons_padding_start">0dp</dimen>
- <dimen name="shade_header_system_icons_padding_end">0dp</dimen>
<!-- The top margin of the panel that holds the list of notifications.
On phones it's always 0dp but it's overridden in Car UI
@@ -1797,7 +1793,6 @@
<dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen>
<dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen>
<dimen name="dream_overlay_icon_inset_dimen">0dp</dimen>
- <dimen name="dream_overlay_status_bar_marginTop">22dp</dimen>
<!-- Default device corner radius, used for assist UI -->
<dimen name="config_rounded_mask_size">0px</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fd74c7e..6b85621 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -398,7 +398,8 @@
<item name="android:itemTextAppearance">@style/Control.MenuItem</item>
</style>
- <style name="Theme.SystemUI.QuickSettings.BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+ <!-- Cannot double inherit. Use Theme.SystemUI.QuickSettings in code to match -->
+ <style name="BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
<item name="android:windowBackground">@android:color/transparent</item>
</style>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index cb2c3a1..39f4c81 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -56,7 +56,7 @@
<Constraint android:id="@+id/shade_header_system_icons">
<Layout
android:layout_width="wrap_content"
- android:layout_height="@dimen/shade_header_system_icons_height_large_screen"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/privacy_container"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index de7a669..ff395da 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -36,6 +36,7 @@
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.graphics.common.AlphaInterpretation;
@@ -171,7 +172,7 @@
private int mTintColor = Color.BLACK;
@VisibleForTesting
protected DisplayDecorationSupport mHwcScreenDecorationSupport;
- private Display.Mode mDisplayMode;
+ private final Point mDisplaySize = new Point();
@VisibleForTesting
protected DisplayInfo mDisplayInfo = new DisplayInfo();
private DisplayCutout mDisplayCutout;
@@ -484,7 +485,8 @@
mWindowManager = mContext.getSystemService(WindowManager.class);
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
mRotation = mDisplayInfo.rotation;
- mDisplayMode = mDisplayInfo.getMode();
+ mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+ mDisplaySize.y = mDisplayInfo.getNaturalHeight();
mDisplayUniqueId = mDisplayInfo.uniqueId;
mDisplayCutout = mDisplayInfo.displayCutout;
mRoundedCornerResDelegate =
@@ -505,10 +507,12 @@
public void onDisplayChanged(int displayId) {
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
final int newRotation = mDisplayInfo.rotation;
- final Display.Mode newDisplayMode = mDisplayInfo.getMode();
if ((mOverlays != null || mScreenDecorHwcWindow != null)
&& (mRotation != newRotation
- || displayModeChanged(mDisplayMode, newDisplayMode))) {
+ || displaySizeChanged(mDisplaySize, mDisplayInfo))) {
+ final Point newSize = new Point();
+ newSize.x = mDisplayInfo.getNaturalWidth();
+ newSize.y = mDisplayInfo.getNaturalHeight();
// We cannot immediately update the orientation. Otherwise
// WindowManager is still deferring layout until it has finished dispatching
// the config changes, which may cause divergence between what we draw
@@ -520,9 +524,8 @@
if (mRotation != newRotation) {
mLogger.logRotationChangeDeferred(mRotation, newRotation);
}
- if (displayModeChanged(mDisplayMode, newDisplayMode)) {
- mLogger.logDisplayModeChanged(
- newDisplayMode.getModeId(), mDisplayMode.getModeId());
+ if (!mDisplaySize.equals(newSize)) {
+ mLogger.logDisplaySizeChanged(mDisplaySize, newSize);
}
if (mOverlays != null) {
@@ -531,7 +534,7 @@
final ViewGroup overlayView = mOverlays[i].getRootView();
overlayView.getViewTreeObserver().addOnPreDrawListener(
new RestartingPreDrawListener(
- overlayView, i, newRotation, newDisplayMode));
+ overlayView, i, newRotation, newSize));
}
}
}
@@ -541,7 +544,7 @@
new RestartingPreDrawListener(
mScreenDecorHwcWindow,
-1, // Pass -1 for views with no specific position.
- newRotation, newDisplayMode));
+ newRotation, newSize));
}
if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.pendingConfigChange = true;
@@ -943,15 +946,8 @@
}
}
- private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) {
- if (oldMode == null) {
- return true;
- }
-
- // We purposely ignore refresh rate and id changes here, because we don't need to
- // invalidate for those, and they can trigger the refresh rate to increase
- return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth()
- || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight();
+ private static boolean displaySizeChanged(Point size, DisplayInfo info) {
+ return size.x != info.getNaturalWidth() || size.y != info.getNaturalHeight();
}
private int getOverlayWindowGravity(@BoundsPosition int pos) {
@@ -1170,14 +1166,14 @@
if (mRotation != newRotation) {
mDotViewController.setNewRotation(newRotation);
}
- final Display.Mode newMod = mDisplayInfo.getMode();
final DisplayCutout newCutout = mDisplayInfo.displayCutout;
if (!mPendingConfigChange
- && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod)
+ && (newRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo)
|| !Objects.equals(newCutout, mDisplayCutout))) {
mRotation = newRotation;
- mDisplayMode = newMod;
+ mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+ mDisplaySize.y = mDisplayInfo.getNaturalHeight();
mDisplayCutout = newCutout;
float ratio = getPhysicalPixelDisplaySizeRatio();
mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
@@ -1494,31 +1490,29 @@
private final View mView;
private final int mTargetRotation;
- private final Display.Mode mTargetDisplayMode;
+ private final Point mTargetDisplaySize;
// Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific
// position.
private final int mPosition;
private RestartingPreDrawListener(View view, @BoundsPosition int position,
- int targetRotation, Display.Mode targetDisplayMode) {
+ int targetRotation, Point targetDisplaySize) {
mView = view;
mTargetRotation = targetRotation;
- mTargetDisplayMode = targetDisplayMode;
+ mTargetDisplaySize = targetDisplaySize;
mPosition = position;
}
@Override
public boolean onPreDraw() {
mView.getViewTreeObserver().removeOnPreDrawListener(this);
- if (mTargetRotation == mRotation
- && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) {
+ if (mTargetRotation == mRotation && mDisplaySize.equals(mTargetDisplaySize)) {
if (DEBUG_LOGGING) {
final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
: getWindowTitleByPos(mPosition);
Log.i(TAG, title + " already in target rot "
+ mTargetRotation + " and in target resolution "
- + mTargetDisplayMode.getPhysicalWidth() + "x"
- + mTargetDisplayMode.getPhysicalHeight()
+ + mTargetDisplaySize.x + "x" + mTargetDisplaySize.y
+ ", allow draw without restarting it");
}
return true;
@@ -1533,8 +1527,7 @@
: getWindowTitleByPos(mPosition);
Log.i(TAG, title
+ " restarting listener fired, restarting draw for rot " + mRotation
- + ", resolution " + mDisplayMode.getPhysicalWidth() + "x"
- + mDisplayMode.getPhysicalHeight());
+ + ", resolution " + mDisplaySize.x + "x" + mDisplaySize.y);
}
mView.invalidate();
return false;
@@ -1560,19 +1553,18 @@
public boolean onPreDraw() {
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
final int displayRotation = mDisplayInfo.rotation;
- final Display.Mode displayMode = mDisplayInfo.getMode();
- if ((displayRotation != mRotation || displayModeChanged(mDisplayMode, displayMode))
+ if ((displayRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo))
&& !mPendingConfigChange) {
if (DEBUG_LOGGING) {
if (displayRotation != mRotation) {
Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
+ displayRotation + ". Restarting draw");
}
- if (displayModeChanged(mDisplayMode, displayMode)) {
- Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth()
- + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at "
- + displayMode.getPhysicalWidth() + "x"
- + displayMode.getPhysicalHeight() + ". Restarting draw");
+ if (displaySizeChanged(mDisplaySize, mDisplayInfo)) {
+ Log.i(TAG, "Drawing at " + mDisplaySize.x + "x" + mDisplaySize.y
+ + ", but display is at "
+ + mDisplayInfo.getNaturalWidth() + "x"
+ + mDisplayInfo.getNaturalHeight() + ". Restarting draw");
}
}
mView.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index e6aeb43..802eea3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -37,4 +37,8 @@
dumpManager
) {
override val tag = "UdfpsBpViewController"
+
+ override fun shouldPauseAuth(): Boolean {
+ return false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 40db63d..b1f513d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -42,6 +42,7 @@
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
import com.android.systemui.power.PowerUI
import com.android.systemui.reardisplay.RearDisplayDialogController
import com.android.systemui.recents.Recents
@@ -111,6 +112,14 @@
@ClassKey(KeyboardUI::class)
abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
+ /** Inject into MediaProjectionTaskSwitcherCoreStartable. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaProjectionTaskSwitcherCoreStartable::class)
+ abstract fun bindProjectedTaskListener(
+ sysui: MediaProjectionTaskSwitcherCoreStartable
+ ): CoreStartable
+
/** Inject into KeyguardBiometricLockoutLogger */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 06769dc..eef8508 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -58,6 +58,7 @@
import com.android.systemui.log.dagger.MonitorLog;
import com.android.systemui.log.table.TableLogBuffer;
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -181,6 +182,7 @@
LockscreenLayoutModule.class,
LogModule.class,
MediaProjectionModule.class,
+ MediaProjectionTaskSwitcherModule.class,
MotionToolModule.class,
PeopleHubModule.class,
PeopleModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 68211ee..cf26b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -146,6 +146,14 @@
val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = releasedFlag(208,
"lockscreen_without_secure_lock_when_dreaming")
+ // TODO(b/286092087): Tracking Bug
+ @JvmField
+ val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag(301, "enable_system_ui_dream_controller")
+
+ // TODO(b/288287730): Tracking Bug
+ @JvmField
+ val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag(302, "enable_system_ui_dream_hosting")
+
/**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
* the digits when the clock moves.
@@ -559,6 +567,12 @@
val WALLPAPER_MULTI_CROP =
sysPropBooleanFlag(1118, "persist.wm.debug.wallpaper_multi_crop", default = false)
+ // TODO(b/290220798): Tracking Bug
+ @Keep
+ @JvmField
+ val ENABLE_PIP2_IMPLEMENTATION =
+ sysPropBooleanFlag(1119, "persist.wm.debug.enable_pip2_implementation", default = false)
+
// 1200 - predictive back
@Keep
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 0511314..732102e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -50,6 +50,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.R;
import com.android.systemui.SystemUIAppComponentFactoryBase;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -151,6 +152,9 @@
private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
@Inject
WakeLockLogger mWakeLockLogger;
+ @Inject
+ @Background
+ Handler mBgHandler;
/**
* Receiver responsible for time ticking and updating the date format.
@@ -502,7 +506,7 @@
}
protected void notifyChange() {
- mContentResolver.notifyChange(mSliceUri, null /* observer */);
+ mBgHandler.post(() -> mContentResolver.notifyChange(mSliceUri, null /* observer */));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index 150de26..702a23e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -189,15 +189,15 @@
)
}
- fun logDisplayModeChanged(currentMode: Int, newMode: Int) {
+ fun logDisplaySizeChanged(currentSize: Point, newSize: Point) {
logBuffer.log(
TAG,
INFO,
{
- int1 = currentMode
- int2 = newMode
+ str1 = currentSize.flattenToString()
+ str2 = newSize.flattenToString()
},
- { "Resolution changed, deferring mode change to $int2, staying at $int1" },
+ { "Resolution changed, deferring size change to $str2, staying at $str1" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
index 81de607..2cfe5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
@@ -41,6 +41,7 @@
import android.widget.Button;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ILogAccessDialogCallback;
import com.android.systemui.R;
@@ -69,7 +70,8 @@
private AlertDialog.Builder mAlertDialog;
private AlertDialog mAlert;
- private View mAlertView;
+ @VisibleForTesting
+ protected View mAlertView;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -115,7 +117,10 @@
mAlertDialog = new AlertDialog.Builder(this, themeId);
mAlertDialog.setView(mAlertView);
mAlertDialog.setOnCancelListener(dialog -> declineLogAccess());
- mAlertDialog.setOnDismissListener(dialog -> finish());
+ mAlertDialog.setOnDismissListener(dialog -> {
+ mAlert = null;
+ finish();
+ });
// show Alert
mAlert = mAlertDialog.create();
@@ -127,12 +132,11 @@
}
@Override
- protected void onDestroy() {
- super.onDestroy();
- if (!isChangingConfigurations() && mAlert != null && mAlert.isShowing()) {
- mAlert.dismiss();
+ protected void onStop() {
+ super.onStop();
+ if (!isChangingConfigurations() && mAlert != null) {
+ mAlert.cancel();
}
- mAlert = null;
}
private boolean readIntentInfo(Intent intent) {
@@ -170,7 +174,6 @@
case MSG_DISMISS_DIALOG:
if (mAlert != null) {
mAlert.dismiss();
- mAlert = null;
declineLogAccess();
}
break;
@@ -181,7 +184,7 @@
}
};
- private String getTitleString(Context context, String callingPackage, int uid)
+ protected String getTitleString(Context context, String callingPackage, int uid)
throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
@@ -238,13 +241,13 @@
try {
if (view.getId() == R.id.log_access_dialog_allow_button) {
mCallback.approveAccessForClient(mUid, mPackageName);
- finish();
} else if (view.getId() == R.id.log_access_dialog_deny_button) {
declineLogAccess();
- finish();
}
- } catch (RemoteException e) {
- finish();
+ } catch (RemoteException ignored) {
+ // Do nothing.
+ } finally {
+ mAlert.dismiss();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 4f4b58f..9a1a5b0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -1253,6 +1253,9 @@
return try {
val options = BroadcastOptions.makeBasic()
options.setInteractive(true)
+ options.setPendingIntentBackgroundActivityStartMode(
+ BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
intent.send(options.toBundle())
true
} catch (e: PendingIntent.CanceledException) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 35082fd..a978b92 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -23,6 +23,7 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.WallpaperColors;
@@ -535,7 +536,10 @@
mLockscreenUserManager.getCurrentUserId());
if (showOverLockscreen) {
try {
- clickIntent.send();
+ ActivityOptions opts = ActivityOptions.makeBasic();
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ clickIntent.send(opts.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Pending intent for " + key + " was cancelled");
}
@@ -684,6 +688,8 @@
try {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
deviceIntent.send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Device pending intent was canceled");
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
new file mode 100644
index 0000000..3c50127
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.mediaprojection.taskswitcher
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaProjectionTaskSwitcherCoreStartable
+@Inject
+constructor(
+ private val notificationCoordinator: TaskSwitcherNotificationCoordinator,
+ private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+ override fun start() {
+ if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) {
+ notificationCoordinator.start()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
new file mode 100644
index 0000000..22ad07e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.mediaprojection.taskswitcher
+
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MediaProjectionTaskSwitcherModule {
+
+ @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
+
+ @Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
new file mode 100644
index 0000000..9938f11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.model
+
+import android.app.TaskInfo
+
+/** Represents the state of media projection. */
+sealed interface MediaProjectionState {
+ object NotProjecting : MediaProjectionState
+ object EntireScreen : MediaProjectionState
+ data class SingleTask(val task: TaskInfo) : MediaProjectionState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
new file mode 100644
index 0000000..492d482
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.os.IBinder
+import android.util.Log
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.withContext
+
+/** Implementation of [TasksRepository] that uses [ActivityTaskManager] as the data source. */
+@SysUISingleton
+class ActivityTaskManagerTasksRepository
+@Inject
+constructor(
+ private val activityTaskManager: ActivityTaskManager,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : TasksRepository {
+
+ override suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): RunningTaskInfo? =
+ getRunningTasks().firstOrNull { taskInfo ->
+ taskInfo.token.asBinder() == windowContainerToken
+ }
+
+ private suspend fun getRunningTasks(): List<RunningTaskInfo> =
+ withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) }
+
+ override val foregroundTask: Flow<RunningTaskInfo> =
+ conflatedCallbackFlow {
+ val listener =
+ object : TaskStackListener() {
+ override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) {
+ Log.d(TAG, "onTaskMovedToFront: $taskInfo")
+ trySendWithFailureLogging(taskInfo, TAG)
+ }
+ }
+ activityTaskManager.registerTaskStackListener(listener)
+ awaitClose { activityTaskManager.unregisterTaskStackListener(listener) }
+ }
+ .shareIn(applicationScope, SharingStarted.Lazily, replay = 1)
+
+ companion object {
+ private const val TAG = "TasksRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
new file mode 100644
index 0000000..38d4e69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.util.Log
+import android.view.ContentRecordingSession
+import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class MediaProjectionManagerRepository
+@Inject
+constructor(
+ private val mediaProjectionManager: MediaProjectionManager,
+ @Main private val handler: Handler,
+ @Application private val applicationScope: CoroutineScope,
+ private val tasksRepository: TasksRepository,
+) : MediaProjectionRepository {
+
+ override val mediaProjectionState: Flow<MediaProjectionState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : MediaProjectionManager.Callback() {
+ override fun onStart(info: MediaProjectionInfo?) {
+ Log.d(TAG, "MediaProjectionManager.Callback#onStart")
+ trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ }
+
+ override fun onStop(info: MediaProjectionInfo?) {
+ Log.d(TAG, "MediaProjectionManager.Callback#onStop")
+ trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ }
+
+ override fun onRecordingSessionSet(
+ info: MediaProjectionInfo,
+ session: ContentRecordingSession?
+ ) {
+ Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session")
+ launch { trySendWithFailureLogging(stateForSession(session), TAG) }
+ }
+ }
+ mediaProjectionManager.addCallback(callback, handler)
+ awaitClose { mediaProjectionManager.removeCallback(callback) }
+ }
+ .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1)
+
+ private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState {
+ if (session == null) {
+ return MediaProjectionState.NotProjecting
+ }
+ if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) {
+ return MediaProjectionState.EntireScreen
+ }
+ val matchingTask =
+ tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord)
+ ?: return MediaProjectionState.EntireScreen
+ return MediaProjectionState.SingleTask(matchingTask)
+ }
+
+ companion object {
+ private const val TAG = "MediaProjectionMngrRepo"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
new file mode 100644
index 0000000..5bec692
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+
+/** Represents a repository to retrieve and change data related to media projection. */
+interface MediaProjectionRepository {
+
+ /** Represents the current [MediaProjectionState]. */
+ val mediaProjectionState: Flow<MediaProjectionState>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
new file mode 100644
index 0000000..544eb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a
+ * placeholder, while the real implementation is not completed.
+ */
+@SysUISingleton
+class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository {
+
+ override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
new file mode 100644
index 0000000..6a535e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.os.IBinder
+import kotlinx.coroutines.flow.Flow
+
+/** Repository responsible for retrieving data related to running tasks. */
+interface TasksRepository {
+
+ /**
+ * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when
+ * no matching task was found.
+ */
+ suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): RunningTaskInfo?
+
+ /**
+ * Emits a stream of [RunningTaskInfo] that have been moved to the foreground.
+ *
+ * Note: when subscribing for the first time, it will not immediately emit the current
+ * foreground task. Only after a change in foreground task has occurred.
+ */
+ val foregroundTask: Flow<RunningTaskInfo>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
new file mode 100644
index 0000000..fc5cf7d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.mediaprojection.taskswitcher.domain.interactor
+
+import android.app.TaskInfo
+import android.content.Intent
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Interactor with logic related to task switching in the context of media projection. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class TaskSwitchInteractor
+@Inject
+constructor(
+ mediaProjectionRepository: MediaProjectionRepository,
+ private val tasksRepository: TasksRepository,
+) {
+
+ /**
+ * Emits a stream of changes to the state of task switching, in the context of media projection.
+ */
+ val taskSwitchChanges: Flow<TaskSwitchState> =
+ mediaProjectionRepository.mediaProjectionState.flatMapLatest { projectionState ->
+ Log.d(TAG, "MediaProjectionState -> $projectionState")
+ when (projectionState) {
+ is MediaProjectionState.SingleTask -> {
+ val projectedTask = projectionState.task
+ tasksRepository.foregroundTask.map { foregroundTask ->
+ if (hasForegroundTaskSwitched(projectedTask, foregroundTask)) {
+ TaskSwitchState.TaskSwitched(projectedTask, foregroundTask)
+ } else {
+ TaskSwitchState.TaskUnchanged
+ }
+ }
+ }
+ is MediaProjectionState.EntireScreen,
+ is MediaProjectionState.NotProjecting -> {
+ flowOf(TaskSwitchState.NotProjectingTask)
+ }
+ }
+ }
+
+ /**
+ * Returns whether tasks have been switched.
+ *
+ * Always returns `false` when launcher is in the foreground. The reason is that when going to
+ * recents to switch apps, launcher becomes the new foreground task, and we don't want to show
+ * the notification then.
+ */
+ private fun hasForegroundTaskSwitched(projectedTask: TaskInfo, foregroundTask: TaskInfo) =
+ projectedTask.taskId != foregroundTask.taskId && !foregroundTask.isLauncher
+
+ private val TaskInfo.isLauncher
+ get() =
+ baseIntent.hasCategory(Intent.CATEGORY_HOME) && baseIntent.action == Intent.ACTION_MAIN
+
+ companion object {
+ private const val TAG = "TaskSwitchInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
new file mode 100644
index 0000000..cd1258e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.mediaprojection.taskswitcher.domain.model
+
+import android.app.TaskInfo
+
+/** Represents tha state of task switching in the context of single task media projection. */
+sealed interface TaskSwitchState {
+ /** Currently no task is being projected. */
+ object NotProjectingTask : TaskSwitchState
+ /** The foreground task is the same as the task that is currently being projected. */
+ object TaskUnchanged : TaskSwitchState
+ /** The foreground task is a different one to the task it currently being projected. */
+ data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) :
+ TaskSwitchState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
new file mode 100644
index 0000000..a4f4076
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.mediaprojection.taskswitcher.ui
+
+import android.content.Context
+import android.util.Log
+import android.widget.Toast
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.launch
+
+/** Coordinator responsible for showing/hiding the task switcher notification. */
+@SysUISingleton
+class TaskSwitcherNotificationCoordinator
+@Inject
+constructor(
+ private val context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val viewModel: TaskSwitcherNotificationViewModel,
+) {
+
+ fun start() {
+ applicationScope.launch {
+ viewModel.uiState.flowOn(mainDispatcher).collect { uiState ->
+ Log.d(TAG, "uiState -> $uiState")
+ when (uiState) {
+ is Showing -> showNotification(uiState)
+ is NotShowing -> hideNotification()
+ }
+ }
+ }
+ }
+
+ private fun showNotification(uiState: Showing) {
+ val text =
+ """
+ Sharing pauses when you switch apps.
+ Share this app instead.
+ Switch back.
+ """
+ .trimIndent()
+ // TODO(b/286201515): Create actual notification.
+ Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
+ }
+
+ private fun hideNotification() {}
+
+ companion object {
+ private const val TAG = "TaskSwitchNotifCoord"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
new file mode 100644
index 0000000..21aee72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.mediaprojection.taskswitcher.ui.model
+
+import android.app.TaskInfo
+
+/** Represents the UI state for the task switcher notification. */
+sealed interface TaskSwitcherNotificationUiState {
+ /** The notification should not be shown. */
+ object NotShowing : TaskSwitcherNotificationUiState
+ /** The notification should be shown. */
+ data class Showing(
+ val projectedTask: TaskInfo,
+ val foregroundTask: TaskInfo,
+ ) : TaskSwitcherNotificationUiState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
new file mode 100644
index 0000000..d9754d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.util.Log
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) {
+
+ val uiState: Flow<TaskSwitcherNotificationUiState> =
+ interactor.taskSwitchChanges.map { taskSwitchChange ->
+ Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+ when (taskSwitchChange) {
+ is TaskSwitchState.TaskSwitched -> {
+ TaskSwitcherNotificationUiState.Showing(
+ projectedTask = taskSwitchChange.projectedTask,
+ foregroundTask = taskSwitchChange.foregroundTask,
+ )
+ }
+ is TaskSwitchState.NotProjectingTask,
+ is TaskSwitchState.TaskUnchanged -> {
+ TaskSwitcherNotificationUiState.NotShowing
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "TaskSwitchNotifVM"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index 3aefcb3..7e234ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -71,6 +71,8 @@
ActivityOptions opts = ActivityOptions.makeBasic();
opts.setDisallowEnterPictureInPictureWhileLaunching(
intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
try {
actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index 13678b0..9e8ea3a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -99,6 +99,8 @@
try {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
intent.send(options.toBundle());
finisher.run();
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 9761f59..ef58b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -55,7 +55,8 @@
Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
}
ActivityOptions opts = ActivityOptions.makeBasic();
-
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
try {
pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle());
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 8879501..5199bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -84,6 +84,7 @@
window.getDecorView();
window.setLayout(
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+ getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
setContentView(R.layout.brightness_mirror_container);
FrameLayout frame = findViewById(R.id.brightness_mirror_container);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 4ac5cd8..8789a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -131,7 +131,6 @@
private val date: TextView = header.findViewById(R.id.date)
private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group)
- private val systemIcons: View = header.findViewById(R.id.shade_header_system_icons)
private var roundedCorners = 0
private var cutout: DisplayCutout? = null
@@ -255,14 +254,6 @@
header.paddingRight,
header.paddingBottom
)
- systemIcons.setPaddingRelative(
- resources.getDimensionPixelSize(
- R.dimen.shade_header_system_icons_padding_start
- ),
- systemIcons.paddingTop,
- resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end),
- systemIcons.paddingBottom
- )
}
override fun onDensityOrFontScaleChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e6e3e7e..ea5ca27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,6 +19,7 @@
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.admin.DevicePolicyManager;
@@ -158,7 +159,11 @@
final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
if (intentSender != null) {
try {
- mContext.startIntentSender(intentSender, null, 0, 0, 0);
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ mContext.startIntentSender(intentSender, null, 0, 0, 0,
+ options.toBundle());
} catch (IntentSender.SendIntentException e) {
/* ignore */
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2fa070c..07eb8a00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -28,12 +28,9 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.expansionChanges
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -50,30 +47,29 @@
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
-import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emitAll
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
/**
* Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
+ * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
+ * lockscreen.
*/
@CoordinatorScope
class KeyguardCoordinator
@@ -86,7 +82,6 @@
private val keyguardRepository: KeyguardRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val logger: KeyguardCoordinatorLogger,
- private val notifPipelineFlags: NotifPipelineFlags,
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
private val secureSettings: SecureSettings,
@@ -95,6 +90,8 @@
) : Coordinator, Dumpable {
private val unseenNotifications = mutableSetOf<NotificationEntry>()
+ private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
+ private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
private var unseenFilterEnabled = false
override fun attach(pipeline: NotifPipeline) {
@@ -109,79 +106,130 @@
private fun attachUnseenFilter(pipeline: NotifPipeline) {
pipeline.addFinalizeFilter(unseenNotifFilter)
pipeline.addCollectionListener(collectionListener)
- scope.launch { trackUnseenNotificationsWhileUnlocked() }
- scope.launch { invalidateWhenUnseenSettingChanges() }
+ scope.launch { trackUnseenFilterSettingChanges() }
dumpManager.registerDumpable(this)
}
- private suspend fun trackUnseenNotificationsWhileUnlocked() {
- // Whether or not we're actively tracking unseen notifications to mark them as seen when
- // appropriate.
- val isTrackingUnseen: Flow<Boolean> =
- keyguardRepository.isKeyguardShowing
- // transformLatest so that we can cancel listening to keyguard transitions once
- // isKeyguardShowing changes (after a successful transition to the keyguard).
- .transformLatest { isShowing ->
- if (isShowing) {
- // If the keyguard is showing, we're not tracking unseen.
- emit(false)
- } else {
- // If the keyguard stops showing, then start tracking unseen notifications.
- emit(true)
- // If the screen is turning off, stop tracking, but if that transition is
- // cancelled, then start again.
- emitAll(
- keyguardTransitionRepository.transitions.map { step ->
- !step.isScreenTurningOff
- }
- )
- }
- }
- // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
- // showing
+ private suspend fun trackSeenNotifications() {
+ // Whether or not keyguard is visible (or occluded).
+ val isKeyguardPresent: Flow<Boolean> =
+ keyguardTransitionRepository.transitions
+ .map { step -> step.to != KeyguardState.GONE }
.distinctUntilChanged()
.onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
- // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
- // showing again
- var clearUnseenOnBeginTracking = false
- isTrackingUnseen.collectLatest { trackingUnseen ->
- if (!trackingUnseen) {
- // Wait for the user to spend enough time on the lock screen before clearing unseen
- // set when unlocked
- awaitTimeSpentNotDozing(SEEN_TIMEOUT)
- clearUnseenOnBeginTracking = true
- logger.logSeenOnLockscreen()
+ // Separately track seen notifications while the device is locked, applying once the device
+ // is unlocked.
+ val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>()
+
+ // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes.
+ isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean ->
+ if (isKeyguardPresent) {
+ // Keyguard is not gone, notifications need to be visible for a certain threshold
+ // before being marked as seen
+ trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked)
} else {
- if (clearUnseenOnBeginTracking) {
- clearUnseenOnBeginTracking = false
- logger.logAllMarkedSeenOnUnlock()
- unseenNotifications.clear()
+ // Mark all seen-while-locked notifications as seen for real.
+ if (notificationsSeenWhileLocked.isNotEmpty()) {
+ unseenNotifications.removeAll(notificationsSeenWhileLocked)
+ logger.logAllMarkedSeenOnUnlock(
+ seenCount = notificationsSeenWhileLocked.size,
+ remainingUnseenCount = unseenNotifications.size
+ )
+ notificationsSeenWhileLocked.clear()
}
unseenNotifFilter.invalidateList("keyguard no longer showing")
- trackUnseenNotifications()
+ // Keyguard is gone, notifications can be immediately marked as seen when they
+ // become visible.
+ trackSeenNotificationsWhileUnlocked()
}
}
}
- private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
- keyguardRepository.isDozing
- // Use transformLatest so that the timeout delay is cancelled if the device enters doze,
- // and is restarted when doze ends.
- .transformLatest { isDozing ->
- if (!isDozing) {
- delay(duration)
- // Signal timeout has completed
- emit(Unit)
+ /**
+ * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+ * been "seen" while the device is on the keyguard.
+ */
+ private suspend fun trackSeenNotificationsWhileLocked(
+ notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
+ ) = coroutineScope {
+ // Remove removed notifications from the set
+ launch {
+ unseenEntryRemoved.collect { entry ->
+ if (notificationsSeenWhileLocked.remove(entry)) {
+ logger.logRemoveSeenOnLockscreen(entry)
}
}
- // Suspend until the first emission
- .first()
+ }
+ // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and
+ // is restarted when doze ends.
+ keyguardRepository.isDozing.collectLatest { isDozing ->
+ if (!isDozing) {
+ trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked)
+ }
+ }
}
- // Track "unseen" notifications, marking them as seen when either shade is expanded or the
+ /**
+ * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+ * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen
+ * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration.
+ */
+ private suspend fun trackSeenNotificationsWhileLockedAndNotDozing(
+ notificationsSeenWhileLocked: MutableSet<NotificationEntry>
+ ) = coroutineScope {
+ // All child tracking jobs will be cancelled automatically when this is cancelled.
+ val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>()
+
+ /**
+ * Wait for the user to spend enough time on the lock screen before removing notification
+ * from unseen set upon unlock.
+ */
+ suspend fun trackSeenDurationThreshold(entry: NotificationEntry) {
+ if (notificationsSeenWhileLocked.remove(entry)) {
+ logger.logResetSeenOnLockscreen(entry)
+ }
+ delay(SEEN_TIMEOUT)
+ notificationsSeenWhileLocked.add(entry)
+ trackingJobsByEntry.remove(entry)
+ logger.logSeenOnLockscreen(entry)
+ }
+
+ /** Stop any unseen tracking when a notification is removed. */
+ suspend fun stopTrackingRemovedNotifs(): Nothing =
+ unseenEntryRemoved.collect { entry ->
+ trackingJobsByEntry.remove(entry)?.let {
+ it.cancel()
+ logger.logStopTrackingLockscreenSeenDuration(entry)
+ }
+ }
+
+ /** Start tracking new notifications when they are posted. */
+ suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope {
+ unseenEntryAdded.collect { entry ->
+ logger.logTrackingLockscreenSeenDuration(entry)
+ // If this is an update, reset the tracking.
+ trackingJobsByEntry[entry]?.let {
+ it.cancel()
+ logger.logResetSeenOnLockscreen(entry)
+ }
+ trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+ }
+ }
+
+ // Start tracking for all notifications that are currently unseen.
+ logger.logTrackingLockscreenSeenDuration(unseenNotifications)
+ unseenNotifications.forEach { entry ->
+ trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+ }
+
+ launch { trackNewUnseenNotifs() }
+ launch { stopTrackingRemovedNotifs() }
+ }
+
+ // Track "seen" notifications, marking them as such when either shade is expanded or the
// notification becomes heads up.
- private suspend fun trackUnseenNotifications() {
+ private suspend fun trackSeenNotificationsWhileUnlocked() {
coroutineScope {
launch { clearUnseenNotificationsWhenShadeIsExpanded() }
launch { markHeadsUpNotificationsAsSeen() }
@@ -212,7 +260,7 @@
}
}
- private suspend fun invalidateWhenUnseenSettingChanges() {
+ private suspend fun trackUnseenFilterSettingChanges() {
secureSettings
// emit whenever the setting has changed
.observerFlow(
@@ -228,17 +276,23 @@
UserHandle.USER_CURRENT,
) == 1
}
+ // don't emit anything if nothing has changed
+ .distinctUntilChanged()
// perform lookups on the bg thread pool
.flowOn(bgDispatcher)
// only track the most recent emission, if events are happening faster than they can be
// consumed
.conflate()
- // update local field and invalidate if necessary
- .collect { setting ->
+ .collectLatest { setting ->
+ // update local field and invalidate if necessary
if (setting != unseenFilterEnabled) {
unseenFilterEnabled = setting
unseenNotifFilter.invalidateList("unseen setting changed")
}
+ // if the setting is enabled, then start tracking and filtering unseen notifications
+ if (setting) {
+ trackSeenNotifications()
+ }
}
}
@@ -250,6 +304,7 @@
) {
logger.logUnseenAdded(entry.key)
unseenNotifications.add(entry)
+ unseenEntryAdded.tryEmit(entry)
}
}
@@ -259,12 +314,14 @@
) {
logger.logUnseenUpdated(entry.key)
unseenNotifications.add(entry)
+ unseenEntryAdded.tryEmit(entry)
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
if (unseenNotifications.remove(entry)) {
logger.logUnseenRemoved(entry.key)
+ unseenEntryRemoved.tryEmit(entry)
}
}
}
@@ -347,6 +404,3 @@
private val SEEN_TIMEOUT = 5.seconds
}
}
-
-private val TransitionStep.isScreenTurningOff: Boolean
- get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
index 4c33524..788659e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
@@ -19,6 +19,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.UnseenNotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
private const val TAG = "KeyguardCoordinator"
@@ -28,11 +29,14 @@
constructor(
@UnseenNotificationLog private val buffer: LogBuffer,
) {
- fun logSeenOnLockscreen() =
+ fun logSeenOnLockscreen(entry: NotificationEntry) =
buffer.log(
TAG,
LogLevel.DEBUG,
- "Notifications on lockscreen will be marked as seen when unlocked."
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Notification [$str1] on lockscreen will be marked as seen when unlocked."
+ },
)
fun logTrackingUnseen(trackingUnseen: Boolean) =
@@ -43,11 +47,21 @@
messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
)
- fun logAllMarkedSeenOnUnlock() =
+ fun logAllMarkedSeenOnUnlock(
+ seenCount: Int,
+ remainingUnseenCount: Int,
+ ) =
buffer.log(
TAG,
LogLevel.DEBUG,
- "Notifications have been marked as seen now that device is unlocked."
+ messageInitializer = {
+ int1 = seenCount
+ int2 = remainingUnseenCount
+ },
+ messagePrinter = {
+ "$int1 Notifications have been marked as seen now that device is unlocked. " +
+ "$int2 notifications remain unseen."
+ },
)
fun logShadeExpanded() =
@@ -96,4 +110,60 @@
messageInitializer = { str1 = key },
messagePrinter = { "Unseen notif has become heads up: $str1" },
)
+
+ fun logTrackingLockscreenSeenDuration(unseenNotifications: Set<NotificationEntry>) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = {
+ str1 = unseenNotifications.joinToString { it.key }
+ int1 = unseenNotifications.size
+ },
+ messagePrinter = {
+ "Tracking $int1 unseen notifications for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Tracking new notification for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logStopTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Stop tracking removed notification for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logResetSeenOnLockscreen(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Reset tracking updated notification for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logRemoveSeenOnLockscreen(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = { "Notification marked as seen on lockscreen removed: $str1" },
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 0d3dfae..8902a18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -42,6 +42,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -1773,7 +1774,10 @@
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
wakeUpForFullScreenIntent();
- notification.fullScreenIntent.send();
+ ActivityOptions opts = ActivityOptions.makeBasic();
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ notification.fullScreenIntent.send(opts.toBundle());
entry.notifyFullScreenIntentLaunched();
} catch (PendingIntent.CanceledException e) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index 22b4c9d..736b145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.app.ActivityOptions
import android.app.Notification
import android.app.PendingIntent
import android.app.RemoteInput
@@ -275,7 +276,10 @@
entry.sbn.instanceId)
try {
- pendingIntent.send(view.context, 0, intent)
+ val options = ActivityOptions.makeBasic()
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ pendingIntent.send(view.context, 0, intent, null, null, null, options.toBundle())
} catch (e: PendingIntent.CanceledException) {
Log.i(TAG, "Unable to send remote input result", e)
uiEventLogger.logWithInstanceId(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index cac5e32..1776e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.app.ActivityOptions
import android.app.Notification
import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
import android.app.PendingIntent
@@ -491,7 +492,11 @@
entry.setHasSentReply()
try {
val intent = createRemoteInputIntent(smartReplies, choice)
- smartReplies.pendingIntent.send(context, 0, intent)
+ val opts = ActivityOptions.makeBasic()
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ smartReplies.pendingIntent.send(context, 0, intent, /* onFinished */null,
+ /* handler */ null, /* requiredPermission */ null, opts.toBundle())
} catch (e: PendingIntent.CanceledException) {
Log.w(TAG, "Unable to send smart reply", e)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
index 3362097..fd7c30f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
@@ -103,6 +103,8 @@
if (mPendingIntent != null) {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
mPendingIntent.send(options.toBundle());
}
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 12da17f..03fde9f 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -192,6 +192,12 @@
android:excludeFromRecents="true" />
<activity
+ android:name="com.android.systemui.logcat.LogAccessDialogActivityTest$DialogTestable"
+ android:exported="false"
+ android:permission="com.android.systemui.permission.SELF"
+ android:excludeFromRecents="true" />
+
+ <activity
android:name="com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity"
android:exported="false"
android:permission="com.android.systemui.permission.SELF"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
new file mode 100644
index 0000000..7de78a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.biometrics
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class UdfpsBpViewControllerTest : SysuiTestCase() {
+
+ @JvmField @Rule var rule = MockitoJUnit.rule()
+
+ @Mock lateinit var udfpsBpView: UdfpsBpView
+ @Mock lateinit var statusBarStateController: StatusBarStateController
+ @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
+ @Mock lateinit var dumpManager: DumpManager
+
+ private lateinit var udfpsBpViewController: UdfpsBpViewController
+
+ @Before
+ fun setup() {
+ udfpsBpViewController =
+ UdfpsBpViewController(
+ udfpsBpView,
+ statusBarStateController,
+ shadeExpansionStateManager,
+ systemUIDialogManager,
+ dumpManager
+ )
+ }
+
+ @Test
+ fun testShouldNeverPauseAuth() {
+ assertFalse(udfpsBpViewController.shouldPauseAuth())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 4210675..71d2ec1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -11,9 +11,9 @@
import android.window.OnBackInvokedDispatcher
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.flags.FakeFeatureFlags
@@ -63,24 +63,19 @@
@JvmField
var activityRule =
ActivityTestRule(
- object :
- SingleActivityFactory<TestableControlsEditingActivity>(
- TestableControlsEditingActivity::class.java
- ) {
- override fun create(intent: Intent?): TestableControlsEditingActivity {
- return TestableControlsEditingActivity(
- featureFlags,
- uiExecutor,
- controller,
- userTracker,
- customIconCache,
- mockDispatcher,
- latch
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestableControlsEditingActivity(
+ featureFlags,
+ uiExecutor,
+ controller,
+ userTracker,
+ customIconCache,
+ mockDispatcher,
+ latch
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index f4cc8bc..f11c296 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -13,9 +13,9 @@
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
@@ -91,24 +91,19 @@
@JvmField
var activityRule =
ActivityTestRule(
- object :
- SingleActivityFactory<TestableControlsFavoritingActivity>(
- TestableControlsFavoritingActivity::class.java
- ) {
- override fun create(intent: Intent?): TestableControlsFavoritingActivity {
- return TestableControlsFavoritingActivity(
- featureFlags,
- executor,
- controller,
- listingController,
- userTracker,
- mockDispatcher,
- latch
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestableControlsFavoritingActivity(
+ featureFlags,
+ executor,
+ controller,
+ listingController,
+ userTracker,
+ mockDispatcher,
+ latch
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index 4ba6718..d17495f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -29,8 +29,8 @@
import android.window.OnBackInvokedDispatcher
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
@@ -91,26 +91,21 @@
@JvmField
var activityRule =
ActivityTestRule(
- object :
- SingleActivityFactory<TestableControlsProviderSelectorActivity>(
- TestableControlsProviderSelectorActivity::class.java
- ) {
- override fun create(intent: Intent?): TestableControlsProviderSelectorActivity {
- return TestableControlsProviderSelectorActivity(
- executor,
- backExecutor,
- listingController,
- controlsController,
- userTracker,
- authorizedPanelsRepository,
- dialogFactory,
- mockDispatcher,
- latch
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestableControlsProviderSelectorActivity(
+ executor,
+ backExecutor,
+ listingController,
+ controlsController,
+ userTracker,
+ authorizedPanelsRepository,
+ dialogFactory,
+ mockDispatcher,
+ latch
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index 314b176..ca970bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -30,8 +30,8 @@
import androidx.lifecycle.Lifecycle
import androidx.test.filters.MediumTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.settings.UserTracker
@@ -81,19 +81,18 @@
@Rule
@JvmField
- var activityRule = ActivityTestRule<TestControlsRequestDialog>(
- object : SingleActivityFactory<TestControlsRequestDialog>(
- TestControlsRequestDialog::class.java
- ) {
- override fun create(intent: Intent?): TestControlsRequestDialog {
- return TestControlsRequestDialog(
- mainExecutor,
- controller,
- userTracker,
- listingController
- )
- }
- }, false, false)
+ var activityRule = ActivityTestRule(
+ /* activityFactory= */ SingleActivityFactory {
+ TestControlsRequestDialog(
+ mainExecutor,
+ controller,
+ userTracker,
+ listingController
+ )
+ },
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
+ )
private lateinit var control: Control
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
index 2d3e10e..e279d28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
@@ -23,8 +23,8 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.flags.FeatureFlags
@@ -53,23 +53,18 @@
@JvmField
var activityRule =
ActivityTestRule(
- object :
- SingleActivityFactory<TestableControlsActivity>(
- TestableControlsActivity::class.java
- ) {
- override fun create(intent: Intent?): TestableControlsActivity {
- return TestableControlsActivity(
- uiController,
- broadcastDispatcher,
- dreamManager,
- featureFlags,
- controlsSettingsDialogManager,
- keyguardStateController,
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestableControlsActivity(
+ uiController,
+ broadcastDispatcher,
+ dreamManager,
+ featureFlags,
+ controlsSettingsDialogManager,
+ keyguardStateController,
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 729a1cc..ce8028c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -19,6 +19,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -31,6 +32,7 @@
import android.media.MediaMetadata;
import android.media.session.PlaybackState;
import android.net.Uri;
+import android.os.Handler;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -166,6 +168,7 @@
@Test
public void updatesClock() {
+ clearInvocations(mContentResolver);
mProvider.mKeyguardUpdateMonitorCallback.onTimeChanged();
TestableLooper.get(this).processAllMessages();
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -217,11 +220,13 @@
reset(mContentResolver);
mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
PlaybackState.STATE_PLAYING);
+ TestableLooper.get(this).processAllMessages();
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
// Hides after waking up
reset(mContentResolver);
mProvider.onDozingChanged(false);
+ TestableLooper.get(this).processAllMessages();
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
}
@@ -231,6 +236,7 @@
mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
PlaybackState.STATE_PLAYING);
reset(mContentResolver);
+ TestableLooper.get(this).processAllMessages();
// Show media when dozing
mProvider.onDozingChanged(true);
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -272,6 +278,8 @@
mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
+ mBgHandler =
+ new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper());
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/logcat/LogAccessDialogActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/logcat/LogAccessDialogActivityTest.java
new file mode 100644
index 0000000..1d7d5df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/logcat/LogAccessDialogActivityTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.logcat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.app.ILogAccessDialogCallback;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+public class LogAccessDialogActivityTest extends SysuiTestCase {
+
+ public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
+ private final DialogCallbackTestable mDialogCallback = new DialogCallbackTestable();
+ @Rule
+ public ActivityScenarioRule<DialogTestable> mActivityScenarioRule =
+ new ActivityScenarioRule<>(getIntent());
+
+ static final class DialogCallbackTestable extends ILogAccessDialogCallback.Stub {
+
+ int mNumOfApprove = 0;
+ int mNumOfDecline = 0;
+ CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+ @Override
+ public void approveAccessForClient(int i, String s) {
+ mNumOfApprove++;
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void declineAccessForClient(int i, String s) {
+ mNumOfDecline++;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mDialogCallback.mNumOfDecline = 0;
+ mDialogCallback.mNumOfApprove = 0;
+ mDialogCallback.mCountDownLatch = new CountDownLatch(1);
+ }
+
+ private Intent getIntent() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ final Intent intent = new Intent(context, DialogTestable.class);
+
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, "packageName");
+ intent.putExtra(Intent.EXTRA_UID, 1);
+
+ intent.putExtra(EXTRA_CALLBACK, mDialogCallback.asBinder());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ return intent;
+ }
+
+ @Test
+ public void test_dialogDisappear_withoutClick_autoDeclined() throws InterruptedException {
+ ActivityScenario<DialogTestable> activityScenario = mActivityScenarioRule.getScenario();
+ activityScenario.onActivity(Activity::finish);
+
+ assertTrue(mDialogCallback.mCountDownLatch.await(2, TimeUnit.SECONDS));
+ assertEquals(mDialogCallback.mNumOfDecline, 1);
+ assertEquals(mDialogCallback.mNumOfApprove, 0);
+
+ }
+
+ @Test
+ public void test_clickAllow() throws InterruptedException {
+ ActivityScenario<DialogTestable> activityScenario = mActivityScenarioRule.getScenario();
+
+ activityScenario.onActivity(activity -> {
+ View allowButton =
+ activity.mAlertView.findViewById(R.id.log_access_dialog_allow_button);
+ assertNotNull(allowButton);
+ allowButton.performClick();
+ });
+
+ assertTrue(mDialogCallback.mCountDownLatch.await(10, TimeUnit.SECONDS));
+ assertEquals(mDialogCallback.mNumOfDecline, 0);
+ assertEquals(mDialogCallback.mNumOfApprove, 1);
+ }
+
+ @Test
+ public void test_clickDeny() throws InterruptedException {
+ ActivityScenario<DialogTestable> activityScenario = mActivityScenarioRule.getScenario();
+
+ activityScenario.onActivity(activity -> {
+ View denyButton =
+ activity.mAlertView.findViewById(R.id.log_access_dialog_deny_button);
+ assertNotNull(denyButton);
+ denyButton.performClick();
+ });
+
+ assertTrue(mDialogCallback.mCountDownLatch.await(10, TimeUnit.SECONDS));
+ assertEquals(mDialogCallback.mNumOfDecline, 1);
+ assertEquals(mDialogCallback.mNumOfApprove, 0);
+ }
+
+ public static class DialogTestable extends LogAccessDialogActivity {
+
+ @Override
+ protected String getTitleString(Context context, String callingPackage, int uid) {
+ return "DialogTitle";
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index f902be3..dc771d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -1793,7 +1793,7 @@
// THEN it sends the PendingIntent without dismissing keyguard first,
// and does not use the Intent directly (see b/271845008)
captor.value.onClick(viewHolder.player)
- verify(pendingIntent).send()
+ verify(pendingIntent).send(any(Bundle::class.java))
verify(pendingIntent, never()).getIntent()
verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
new file mode 100644
index 0000000..bcbf666
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.mediaprojection.taskswitcher
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
+
+ @Mock private lateinit var flags: FeatureFlags
+ @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator
+
+ private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags)
+ }
+
+ @Test
+ fun start_flagEnabled_startsCoordinator() {
+ whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true)
+
+ coreStartable.start()
+
+ verify(coordinator).start()
+ }
+
+ @Test
+ fun start_flagDisabled_doesNotStartCoordinator() {
+ whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false)
+
+ coreStartable.start()
+
+ verifyZeroInteractions(coordinator)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
new file mode 100644
index 0000000..83932b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.os.Binder
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val repo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ @Test
+ fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() {
+ fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2))
+
+ testScope.runTest {
+ val matchingTask =
+ repo.findRunningTaskFromWindowContainerToken(windowContainerToken = Binder())
+
+ assertThat(matchingTask).isNull()
+ }
+ }
+
+ @Test
+ fun findRunningTaskFromWindowContainerToken_matchingToken_returnsTaskInfo() {
+ val expectedToken = createToken()
+ val expectedTask = createTask(taskId = 1, token = expectedToken)
+
+ fakeActivityTaskManager.addRunningTasks(
+ createTask(taskId = 2),
+ expectedTask,
+ )
+
+ testScope.runTest {
+ val actualTask =
+ repo.findRunningTaskFromWindowContainerToken(
+ windowContainerToken = expectedToken.asBinder()
+ )
+
+ assertThat(actualTask).isEqualTo(expectedTask)
+ }
+ }
+
+ @Test
+ fun foregroundTask_returnsStreamOfTasksMovedToFront() =
+ testScope.runTest {
+ val foregroundTask by collectLastValue(repo.foregroundTask)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ assertThat(foregroundTask?.taskId).isEqualTo(1)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 2))
+ assertThat(foregroundTask?.taskId).isEqualTo(2)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 3))
+ assertThat(foregroundTask?.taskId).isEqualTo(3)
+ }
+
+ @Test
+ fun foregroundTask_lastValueIsCached() =
+ testScope.runTest {
+ val foregroundTaskA by collectLastValue(repo.foregroundTask)
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ assertThat(foregroundTaskA?.taskId).isEqualTo(1)
+
+ val foregroundTaskB by collectLastValue(repo.foregroundTask)
+ assertThat(foregroundTaskB?.taskId).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
new file mode 100644
index 0000000..1c4870b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.content.Intent
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeActivityTaskManager {
+
+ private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val taskTaskListeners = mutableListOf<TaskStackListener>()
+
+ val activityTaskManager = mock<ActivityTaskManager>()
+
+ init {
+ whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer {
+ taskTaskListeners += it.arguments[0] as TaskStackListener
+ return@thenAnswer Unit
+ }
+ whenever(activityTaskManager.unregisterTaskStackListener(any())).thenAnswer {
+ taskTaskListeners -= it.arguments[0] as TaskStackListener
+ return@thenAnswer Unit
+ }
+ whenever(activityTaskManager.getTasks(any())).thenAnswer {
+ val maxNumTasks = it.arguments[0] as Int
+ return@thenAnswer runningTasks.take(maxNumTasks)
+ }
+ }
+
+ fun moveTaskToForeground(task: RunningTaskInfo) {
+ taskTaskListeners.forEach { it.onTaskMovedToFront(task) }
+ }
+
+ fun addRunningTasks(vararg tasks: RunningTaskInfo) {
+ runningTasks += tasks
+ }
+
+ companion object {
+
+ fun createTask(
+ taskId: Int,
+ token: WindowContainerToken = createToken(),
+ baseIntent: Intent = Intent()
+ ) =
+ RunningTaskInfo().apply {
+ this.taskId = taskId
+ this.token = token
+ this.baseIntent = baseIntent
+ }
+
+ fun createToken(): WindowContainerToken {
+ val realToken = object : IWindowContainerToken.Stub() {}
+ return WindowContainerToken(realToken)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
new file mode 100644
index 0000000..c59fd60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.TaskInfo
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeMediaProjectionRepository : MediaProjectionRepository {
+
+ private val state = MutableStateFlow<MediaProjectionState>(MediaProjectionState.NotProjecting)
+
+ fun switchProjectedTask(newTask: TaskInfo) {
+ state.value = MediaProjectionState.SingleTask(newTask)
+ }
+
+ override val mediaProjectionState: Flow<MediaProjectionState> = state.asStateFlow()
+
+ fun projectEntireScreen() {
+ state.value = MediaProjectionState.EntireScreen
+ }
+
+ fun stopProjecting() {
+ state.value = MediaProjectionState.NotProjecting
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
new file mode 100644
index 0000000..593e389
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Intent
+import android.os.IBinder
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTasksRepository : TasksRepository {
+
+ private val _foregroundTask = MutableStateFlow(DEFAULT_TASK)
+
+ override val foregroundTask: Flow<RunningTaskInfo> = _foregroundTask.asStateFlow()
+
+ private val runningTasks = mutableListOf(DEFAULT_TASK)
+
+ override suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): RunningTaskInfo? = runningTasks.firstOrNull { it.token.asBinder() == windowContainerToken }
+
+ fun addRunningTask(task: RunningTaskInfo) {
+ runningTasks.add(task)
+ }
+
+ fun moveTaskToForeground(task: RunningTaskInfo) {
+ _foregroundTask.value = task
+ }
+
+ companion object {
+ val DEFAULT_TASK = createTask(taskId = -1)
+ val LAUNCHER_INTENT: Intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+
+ fun createTask(
+ taskId: Int,
+ token: WindowContainerToken = createToken(),
+ baseIntent: Intent = Intent()
+ ) =
+ RunningTaskInfo().apply {
+ this.taskId = taskId
+ this.token = token
+ this.baseIntent = baseIntent
+ }
+
+ fun createToken(): WindowContainerToken {
+ val realToken = object : IWindowContainerToken.Stub() {}
+ return WindowContainerToken(realToken)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
new file mode 100644
index 0000000..2b07465
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.Handler
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.ContentRecordingSession
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
+
+ private val mediaProjectionManager = mock<MediaProjectionManager>()
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private val tasksRepo = FakeTasksRepository()
+
+ private lateinit var callback: MediaProjectionManager.Callback
+ private lateinit var repo: MediaProjectionManagerRepository
+
+ @Before
+ fun setUp() {
+ whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
+ callback = it.arguments[0] as MediaProjectionManager.Callback
+ return@thenAnswer Unit
+ }
+ repo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo
+ )
+ }
+
+ @Test
+ fun mediaProjectionState_onStart_emitsNotProjecting() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ callback.onStart(TEST_MEDIA_INFO)
+
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
+ fun mediaProjectionState_onStop_emitsNotProjecting() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ callback.onStop(TEST_MEDIA_INFO)
+
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
+ fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, /* session= */ null)
+
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
+ fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+ }
+
+ @Test
+ fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val session =
+ ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+ }
+
+ @Test
+ fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val taskWindowContainerToken = Binder()
+ val session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+ }
+
+ @Test
+ fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
+ testScope.runTest {
+ val token = FakeTasksRepository.createToken()
+ val task = FakeTasksRepository.createTask(taskId = 1, token = token)
+ tasksRepo.addRunningTask(task)
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val session = ContentRecordingSession.createTaskSession(token.asBinder())
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
+ }
+
+ companion object {
+ val TEST_MEDIA_INFO =
+ MediaProjectionInfo(/* packageName= */ "com.test.package", UserHandle.CURRENT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
new file mode 100644
index 0000000..112950b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.mediaprojection.taskswitcher.domain.interactor
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitchInteractorTest : SysuiTestCase() {
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val mediaRepo = FakeMediaProjectionRepository()
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+ @Test
+ fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
+ testScope.runTest {
+ mediaRepo.stopProjecting()
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() =
+ testScope.runTest {
+ mediaRepo.projectEntireScreen()
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_emitsTaskChanged() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(taskSwitchState)
+ .isEqualTo(
+ TaskSwitchState.TaskSwitched(
+ projectedTask = projectedTask,
+ foregroundTask = foregroundTask
+ )
+ )
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 0)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+ }
+
+ companion object {
+ private val LAUNCHER_INTENT: Intent =
+ Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
new file mode 100644
index 0000000..ea44fb3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val mediaRepo = FakeMediaProjectionRepository()
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+ private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+ private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+
+ @Test
+ fun uiState_notProjecting_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.stopProjecting()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.stopProjecting()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ mediaRepo.switchProjectedTask(createTask(taskId = 1))
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingEntireScreen_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.projectEntireScreen()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.projectEntireScreen()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ mediaRepo.switchProjectedTask(createTask(taskId = 1))
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingTask_foregroundTaskChanged_different_emitsShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(uiState)
+ .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+ }
+
+ @Test
+ fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.moveTaskToForeground(projectedTask)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingTask_foregroundTaskChanged_different_taskIsLauncher_emitsNotShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ companion object {
+ private val LAUNCHER_INTENT: Intent =
+ Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
index 36b913f..bdb095a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
@@ -22,9 +22,9 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.util.mockito.any
@@ -47,13 +47,9 @@
@Rule
@JvmField
val activityRule =
- ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>(
- /* activityFactory= */ object :
- SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>(
- LaunchNotesRoleSettingsTrampolineActivity::class.java
- ) {
- override fun create(intent: Intent?) =
- LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
+ ActivityTestRule(
+ /* activityFactory= */ SingleActivityFactory {
+ LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
},
/* initialTouchMode= */ false,
/* launchActivity= */ false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
index 627c4a8..1f0f0d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
@@ -16,14 +16,13 @@
package com.android.systemui.notetask.shortcut
-import android.content.Intent
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
import com.android.systemui.util.mockito.any
@@ -47,12 +46,8 @@
@JvmField
val activityRule =
ActivityTestRule<LaunchNoteTaskActivity>(
- /* activityFactory= */ object :
- SingleActivityFactory<LaunchNoteTaskActivity>(LaunchNoteTaskActivity::class.java) {
- override fun create(intent: Intent?) =
- LaunchNoteTaskActivity(
- controller = noteTaskController,
- )
+ /* activityFactory= */ SingleActivityFactory {
+ LaunchNoteTaskActivity(controller = noteTaskController)
},
/* initialTouchMode= */ false,
/* launchActivity= */ false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 7646193..16751c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.settings.brightness
-import android.content.Intent
import android.graphics.Rect
import android.os.Handler
import android.testing.AndroidTestingRunner
@@ -25,9 +24,9 @@
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
@@ -59,19 +58,17 @@
@JvmField
var activityRule =
ActivityTestRule(
- object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
- override fun create(intent: Intent?): TestDialog {
- return TestDialog(
- userTracker,
- displayTracker,
- brightnessSliderControllerFactory,
- mainExecutor,
- backgroundHandler
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestDialog(
+ userTracker,
+ displayTracker,
+ brightnessSliderControllerFactory,
+ mainExecutor,
+ backgroundHandler
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 2fbe871..ea70e9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -32,7 +32,6 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -46,11 +45,14 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -62,9 +64,8 @@
import org.mockito.ArgumentMatchers.same
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import java.util.function.Consumer
-import kotlin.time.Duration.Companion.seconds
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -75,7 +76,6 @@
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
private val keyguardRepository = FakeKeyguardRepository()
private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- private val notifPipelineFlags: NotifPipelineFlags = mock()
private val notifPipeline: NotifPipeline = mock()
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
private val statusBarStateController: StatusBarStateController = mock()
@@ -136,13 +136,8 @@
)
testScheduler.runCurrent()
- // WHEN: The shade is expanded
- whenever(statusBarStateController.isExpanded).thenReturn(true)
- statusBarStateListener.onExpandedChanged(true)
- testScheduler.runCurrent()
-
- // THEN: The notification is still treated as "unseen" and is not filtered out.
- assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ // THEN: We are no longer listening for shade expansions
+ verify(statusBarStateController, never()).addCallback(any())
}
}
@@ -152,6 +147,10 @@
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(false)
runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+
// WHEN: A notification is posted
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
@@ -162,6 +161,9 @@
// WHEN: The keyguard is now showing
keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+ )
testScheduler.runCurrent()
// THEN: The notification is recognized as "seen" and is filtered out.
@@ -169,6 +171,9 @@
// WHEN: The keyguard goes away
keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ )
testScheduler.runCurrent()
// THEN: The notification is shown regardless
@@ -182,9 +187,10 @@
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
- val fakeEntry = NotificationEntryBuilder()
+ val fakeEntry =
+ NotificationEntryBuilder()
.setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
- .build()
+ .build()
collectionListener.onEntryAdded(fakeEntry)
// WHEN: The keyguard is now showing
@@ -202,11 +208,13 @@
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
- val fakeEntry = NotificationEntryBuilder().build().apply {
- row = mock<ExpandableNotificationRow>().apply {
- whenever(isMediaRow).thenReturn(true)
+ val fakeEntry =
+ NotificationEntryBuilder().build().apply {
+ row =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(isMediaRow).thenReturn(true)
+ }
}
- }
collectionListener.onEntryAdded(fakeEntry)
// WHEN: The keyguard is now showing
@@ -299,14 +307,12 @@
runKeyguardCoordinatorTest {
// WHEN: A new notification is posted
val fakeSummary = NotificationEntryBuilder().build()
- val fakeChild = NotificationEntryBuilder()
+ val fakeChild =
+ NotificationEntryBuilder()
.setGroup(context, "group")
.setGroupSummary(context, false)
.build()
- GroupEntryBuilder()
- .setSummary(fakeSummary)
- .addChild(fakeChild)
- .build()
+ GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build()
collectionListener.onEntryAdded(fakeSummary)
collectionListener.onEntryAdded(fakeChild)
@@ -331,6 +337,10 @@
runKeyguardCoordinatorTest {
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
// WHEN: five seconds have passed
testScheduler.advanceTimeBy(5.seconds)
@@ -338,10 +348,16 @@
// WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
testScheduler.runCurrent()
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+ )
testScheduler.runCurrent()
// THEN: The notification is now recognized as "seen" and is filtered out.
@@ -354,11 +370,17 @@
// GIVEN: Keyguard is showing, unseen notification is present
keyguardRepository.setKeyguardShowing(true)
runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
// WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
@@ -369,14 +391,212 @@
}
}
+ @Test
+ fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() {
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ val firstEntry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(firstEntry)
+ testScheduler.runCurrent()
+
+ // WHEN: one second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: another unseen notification is posted
+ val secondEntry = NotificationEntryBuilder().setId(2).build()
+ collectionListener.onEntryAdded(secondEntry)
+ testScheduler.runCurrent()
+
+ // WHEN: four more seconds have passed
+ testScheduler.advanceTimeBy(4.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The first notification is considered seen and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue()
+
+ // THEN: The second notification is still considered unseen and is not filtered out
+ assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() {
+ // GIVEN: Keyguard is showing, not dozing
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: a new notification is posted
+ val entry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: five more seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is removed
+ collectionListener.onEntryRemoved(entry, 0)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is re-posted
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one more second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The notification is considered unseen and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() {
+ // GIVEN: Keyguard is showing, not dozing
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: a new notification is posted
+ val entry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is removed
+ collectionListener.onEntryRemoved(entry, 0)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is re-posted
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one more second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The notification is considered unseen and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() {
+ // GIVEN: Keyguard is showing, not dozing
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: a new notification is posted
+ val entry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is updated
+ collectionListener.onEntryUpdated(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: four more seconds have passed
+ testScheduler.advanceTimeBy(4.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The notification is considered unseen and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+ }
+ }
+
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
val testDispatcher = UnconfinedTestDispatcher()
val testScope = TestScope(testDispatcher)
- val fakeSettings = FakeSettings().apply {
- putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
- }
+ val fakeSettings =
+ FakeSettings().apply {
+ putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+ }
val seenNotificationsProvider = SeenNotificationsProviderImpl()
val keyguardCoordinator =
KeyguardCoordinator(
@@ -387,7 +607,6 @@
keyguardRepository,
keyguardTransitionRepository,
mock<KeyguardCoordinatorLogger>(),
- notifPipelineFlags,
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
fakeSettings,
@@ -397,11 +616,12 @@
keyguardCoordinator.attach(notifPipeline)
testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
KeyguardCoordinatorTestScope(
- keyguardCoordinator,
- testScope,
- seenNotificationsProvider,
- fakeSettings,
- ).testBlock()
+ keyguardCoordinator,
+ testScope,
+ seenNotificationsProvider,
+ fakeSettings,
+ )
+ .testBlock()
}
}
@@ -414,10 +634,9 @@
val testScheduler: TestCoroutineScheduler
get() = scope.testScheduler
- val onStateChangeListener: Consumer<String> =
- withArgCaptor {
- verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
- }
+ val onStateChangeListener: Consumer<String> = withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
val unseenFilter: NotifFilter
get() = keyguardCoordinator.unseenNotifFilter
@@ -426,11 +645,11 @@
verify(notifPipeline).addCollectionListener(capture())
}
- val onHeadsUpChangedListener: OnHeadsUpChangedListener get() =
- withArgCaptor { verify(headsUpManager).addListener(capture()) }
+ val onHeadsUpChangedListener: OnHeadsUpChangedListener
+ get() = withArgCaptor { verify(headsUpManager).addListener(capture()) }
- val statusBarStateListener: StatusBarStateController.StateListener get() =
- withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
+ val statusBarStateListener: StatusBarStateController.StateListener
+ get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
get() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
index b30c20d..b04eb01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -25,8 +25,8 @@
import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.google.common.truth.Truth.assertThat
import javax.inject.Inject
@@ -49,19 +49,17 @@
open class UsbPermissionActivityTestable @Inject constructor (
val message: UsbAudioWarningDialogMessage
- )
- : UsbPermissionActivity(UsbAudioWarningDialogMessage())
+ ) : UsbPermissionActivity(UsbAudioWarningDialogMessage())
@Rule
@JvmField
- var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
- object : SingleActivityFactory<UsbPermissionActivityTestable>(
- UsbPermissionActivityTestable::class.java
- ) {
- override fun create(intent: Intent?): UsbPermissionActivityTestable {
- return UsbPermissionActivityTestable(mMessage)
- }
- }, false, false)
+ var activityRule = ActivityTestRule(
+ /* activityFactory= */ SingleActivityFactory {
+ UsbPermissionActivityTestable(mMessage)
+ },
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
+ )
private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
.apply {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index a09af00..ef12b2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -39,6 +39,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -153,6 +154,7 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
import org.junit.After;
import org.junit.Before;
@@ -282,7 +284,7 @@
@Mock
private ScreenOffAnimationController mScreenOffAnimationController;
@Mock
- private TaskViewTransitions mTaskViewTransitions;
+ Transitions mTransitions;
@Mock
private Optional<OneHandedController> mOneHandedOptional;
@Mock
@@ -294,6 +296,8 @@
@Mock
private Icon mAppBubbleIcon;
+ private TaskViewTransitions mTaskViewTransitions;
+
private TestableBubblePositioner mPositioner;
private BubbleData mBubbleData;
@@ -309,6 +313,12 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ doReturn(true).when(mTransitions).isRegistered();
+ }
+ mTaskViewTransitions = new TaskViewTransitions(mTransitions);
+
mTestableLooper = TestableLooper.get(this);
// For the purposes of this test, just run everything synchronously
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt
new file mode 100644
index 0000000..5a92fb1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.activity
+
+import android.app.Activity
+import android.content.Intent
+import androidx.test.runner.intercepting.SingleActivityFactory
+
+/**
+ * Builds a new [SingleActivityFactory] which delegating any call of [SingleActivityFactory.create]
+ * to the [instantiate] parameter.
+ *
+ * For more details, see [SingleActivityFactory].
+ */
+inline fun <reified T : Activity> SingleActivityFactory(
+ crossinline instantiate: (intent: Intent?) -> T,
+): SingleActivityFactory<T> {
+ return object : SingleActivityFactory<T>(T::class.java) {
+ override fun create(intent: Intent?): T = instantiate(intent)
+ }
+}
diff --git a/services/core/java/com/android/server/PendingIntentUtils.java b/services/core/java/com/android/server/PendingIntentUtils.java
index 1600101..a72a4d25 100644
--- a/services/core/java/com/android/server/PendingIntentUtils.java
+++ b/services/core/java/com/android/server/PendingIntentUtils.java
@@ -34,6 +34,7 @@
public static Bundle createDontSendToRestrictedAppsBundle(@Nullable Bundle bundle) {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setDontSendToRestrictedApps(true);
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
if (bundle == null) {
return options.toBundle();
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 73dbb86a..ecc0aa5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2069,7 +2069,7 @@
}
};
// TODO(b/149391976): Use different handler?
- monitor.register(mContext, user, true, mHandler);
+ monitor.register(mContext, user, mHandler);
mPackageMonitorsForUser.put(userId, monitor);
} else {
Slog.w(TAG, "PackageMonitor is already registered for: " + userId);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 7e9283a..cc9628f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -241,7 +241,8 @@
private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000;
- @VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false;
+ // If enabled, any compaction issued will apply to code mappings and share memory mappings.
+ @VisibleForTesting static final boolean ENABLE_SHARED_AND_CODE_COMPACT = false;
// Defaults for phenotype flags.
@VisibleForTesting static final boolean DEFAULT_USE_COMPACTION = true;
@@ -1686,7 +1687,7 @@
}
}
- if (!ENABLE_FILE_COMPACT) {
+ if (!ENABLE_SHARED_AND_CODE_COMPACT) {
if (profile == CompactProfile.SOME) {
profile = CompactProfile.NONE;
} else if (profile == CompactProfile.FULL) {
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index f84a58c..472c1f5 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -22,7 +22,6 @@
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.Display;
import android.view.DisplayAddress;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,6 +37,7 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.math.BigInteger;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -115,13 +115,16 @@
Slog.i(TAG, "Display layout config not found: " + configFile);
return;
}
- int leadDisplayId = Display.DEFAULT_DISPLAY;
for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) {
final int state = l.getState().intValue();
final Layout layout = createLayout(state);
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
assert layout != null;
int position = getPosition(d.getPosition());
+ BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress();
+ DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null
+ : DisplayAddress.fromPhysicalDisplayId(
+ leadDisplayPhysicalId.longValue());
layout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
d.isDefaultDisplay(),
@@ -129,11 +132,12 @@
d.getDisplayGroup(),
mIdProducer,
position,
- leadDisplayId,
+ leadDisplayAddress,
d.getBrightnessThrottlingMapId(),
d.getRefreshRateZoneId(),
d.getRefreshRateThermalThrottlingMapId());
}
+ layout.postProcessLocked();
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: "
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index b55d7d5..d9ec3de 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -22,6 +22,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.DisplayAddress;
@@ -77,7 +79,7 @@
DisplayIdProducer idProducer) {
createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
- NO_LEAD_DISPLAY, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
/* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
}
@@ -90,19 +92,20 @@
* @param displayGroupName Name of the display group to which the display is assigned.
* @param idProducer Produces the logical display id.
* @param position Indicates the position this display is facing in this layout.
- * @param leadDisplayId Display that this one follows (-1 if none).
+ * @param leadDisplayAddress Address of a display that this one follows ({@code null} if none).
* @param brightnessThrottlingMapId Name of which brightness throttling policy should be used.
* @param refreshRateZoneId Layout limited refresh rate zone name.
* @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
* policy should be used.
-
+ *
* @exception IllegalArgumentException When a default display owns a display group other than
* DEFAULT_DISPLAY_GROUP.
*/
public void createDisplayLocked(
@NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
- String displayGroupName, DisplayIdProducer idProducer, int position, int leadDisplayId,
- String brightnessThrottlingMapId, @Nullable String refreshRateZoneId,
+ String displayGroupName, DisplayIdProducer idProducer, int position,
+ @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,
+ @Nullable String refreshRateZoneId,
@Nullable String refreshRateThermalThrottlingMapId) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
@@ -115,21 +118,27 @@
return;
}
- // Assign a logical display ID and create the new display.
- // Note that the logical display ID is saved into the layout, so when switching between
- // different layouts, a logical display can be destroyed and later recreated with the
- // same logical display ID.
if (displayGroupName == null) {
displayGroupName = DEFAULT_DISPLAY_GROUP_NAME;
}
if (isDefault && !displayGroupName.equals(DEFAULT_DISPLAY_GROUP_NAME)) {
throw new IllegalArgumentException("Default display should own DEFAULT_DISPLAY_GROUP");
}
+ if (isDefault && leadDisplayAddress != null) {
+ throw new IllegalArgumentException("Default display cannot have a lead display");
+ }
+ if (address.equals(leadDisplayAddress)) {
+ throw new IllegalArgumentException("Lead display address cannot be the same as display "
+ + " address");
+ }
+ // Assign a logical display ID and create the new display.
+ // Note that the logical display ID is saved into the layout, so when switching between
+ // different layouts, a logical display can be destroyed and later recreated with the
+ // same logical display ID.
final int logicalDisplayId = idProducer.getId(isDefault);
- leadDisplayId = isDefault ? NO_LEAD_DISPLAY : leadDisplayId;
final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
- brightnessThrottlingMapId, position, leadDisplayId, refreshRateZoneId,
+ brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId,
refreshRateThermalThrottlingMapId);
mDisplays.add(display);
@@ -146,6 +155,43 @@
}
/**
+ * Applies post-processing to displays to make sure the information of each display is
+ * up-to-date.
+ *
+ * <p>At creation of a display, lead display is specified by display address. At post
+ * processing, we convert it to logical display ID.
+ */
+ public void postProcessLocked() {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ Display display = mDisplays.get(i);
+ if (display.getLogicalDisplayId() == DEFAULT_DISPLAY) {
+ display.setLeadDisplayId(NO_LEAD_DISPLAY);
+ continue;
+ }
+ DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+ if (leadDisplayAddress == null) {
+ display.setLeadDisplayId(NO_LEAD_DISPLAY);
+ continue;
+ }
+ Display leadDisplay = getByAddress(leadDisplayAddress);
+ if (leadDisplay == null) {
+ throw new IllegalArgumentException("Cannot find a lead display whose address is "
+ + leadDisplayAddress);
+ }
+ if (!TextUtils.equals(display.getDisplayGroupName(),
+ leadDisplay.getDisplayGroupName())) {
+ throw new IllegalArgumentException("Lead display(" + leadDisplay + ") should be in "
+ + "the same display group of the display(" + display + ")");
+ }
+ if (hasCyclicLeadDisplay(display)) {
+ throw new IllegalArgumentException("Display(" + display + ") has a cyclic lead "
+ + "display");
+ }
+ display.setLeadDisplayId(leadDisplay.getLogicalDisplayId());
+ }
+ }
+
+ /**
* @param address The address to check.
*
* @return True if the specified address is used in this layout.
@@ -208,6 +254,20 @@
return mDisplays.size();
}
+ private boolean hasCyclicLeadDisplay(Display display) {
+ ArraySet<Display> visited = new ArraySet<>();
+
+ while (display != null) {
+ if (visited.contains(display)) {
+ return true;
+ }
+ visited.add(display);
+ DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+ display = leadDisplayAddress == null ? null : getByAddress(leadDisplayAddress);
+ }
+ return false;
+ }
+
/**
* Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
*/
@@ -240,8 +300,9 @@
@Nullable
private final String mThermalBrightnessThrottlingMapId;
- // The ID of the lead display that this display will follow in a layout. -1 means no lead.
- private final int mLeadDisplayId;
+ // The address of the lead display that is specified in display-layout-configuration.
+ @Nullable
+ private final DisplayAddress mLeadDisplayAddress;
// Refresh rate zone id for specific layout
@Nullable
@@ -250,9 +311,13 @@
@Nullable
private final String mThermalRefreshRateThrottlingMapId;
+ // The ID of the lead display that this display will follow in a layout. -1 means no lead.
+ // This is determined using {@code mLeadDisplayAddress}.
+ private int mLeadDisplayId;
+
private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
@NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
- int leadDisplayId, @Nullable String refreshRateZoneId,
+ @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId,
@Nullable String refreshRateThermalThrottlingMapId) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
@@ -260,9 +325,10 @@
mDisplayGroupName = displayGroupName;
mPosition = position;
mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId;
+ mLeadDisplayAddress = leadDisplayAddress;
mRefreshRateZoneId = refreshRateZoneId;
mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
- mLeadDisplayId = leadDisplayId;
+ mLeadDisplayId = NO_LEAD_DISPLAY;
}
@Override
@@ -276,6 +342,7 @@
+ ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId
+ ", mRefreshRateZoneId: " + mRefreshRateZoneId
+ ", mLeadDisplayId: " + mLeadDisplayId
+ + ", mLeadDisplayAddress: " + mLeadDisplayAddress
+ ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
+ "}";
}
@@ -297,6 +364,7 @@
otherDisplay.mThermalBrightnessThrottlingMapId)
&& Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
&& this.mLeadDisplayId == otherDisplay.mLeadDisplayId
+ && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)
&& Objects.equals(mThermalRefreshRateThrottlingMapId,
otherDisplay.mThermalRefreshRateThrottlingMapId);
}
@@ -309,9 +377,10 @@
result = 31 * result + mLogicalDisplayId;
result = 31 * result + mDisplayGroupName.hashCode();
result = 31 * result + mAddress.hashCode();
- result = 31 * result + mThermalBrightnessThrottlingMapId.hashCode();
+ result = 31 * result + Objects.hashCode(mThermalBrightnessThrottlingMapId);
result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
result = 31 * result + mLeadDisplayId;
+ result = 31 * result + Objects.hashCode(mLeadDisplayAddress);
result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
return result;
}
@@ -360,8 +429,20 @@
return mLeadDisplayId;
}
+ /**
+ * @return Display address of the display that this one follows.
+ */
+ @Nullable
+ public DisplayAddress getLeadDisplayAddress() {
+ return mLeadDisplayAddress;
+ }
+
public String getRefreshRateThermalThrottlingMapId() {
return mThermalRefreshRateThrottlingMapId;
}
+
+ private void setLeadDisplayId(int id) {
+ mLeadDisplayId = id;
+ }
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
index 2e1ff03..5db114b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
@@ -191,6 +191,11 @@
mService.sendCecCommand(cmd, callback);
}
+ protected final void sendCommandWithoutRetries(HdmiCecMessage cmd,
+ HdmiControlService.SendMessageCallback callback) {
+ mService.sendCecCommandWithoutRetries(cmd, callback);
+ }
+
protected final void addAndStartAction(HdmiCecFeatureAction action) {
mSource.addAndStartAction(action);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index b5e8195..207d38e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1013,6 +1013,12 @@
assertRunOnServiceThread();
mActions.add(action);
if (mService.isPowerStandby() || !mService.isAddressAllocated()) {
+ if (action.getClass() == ResendCecCommandAction.class) {
+ Slog.i(TAG, "Not ready to start ResendCecCommandAction. "
+ + "This action is cancelled.");
+ removeAction(action);
+ return;
+ }
Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action);
return;
}
@@ -1295,6 +1301,7 @@
removeAction(AbsoluteVolumeAudioStatusAction.class);
removeAction(SetAudioVolumeLevelDiscoveryAction.class);
removeAction(ActiveSourceAction.class);
+ removeAction(ResendCecCommandAction.class);
mPendingActionClearedCallback =
new PendingActionClearedCallback() {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 91cf503..a7e78be 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1518,6 +1518,51 @@
}
}
+ @ServiceThreadOnly
+ void sendCecCommand(HdmiCecMessage command) {
+ sendCecCommand(command, null);
+ }
+
+ @ServiceThreadOnly
+ void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
+ switch (command.getOpcode()) {
+ case Constants.MESSAGE_ACTIVE_SOURCE:
+ case Constants.MESSAGE_IMAGE_VIEW_ON:
+ case Constants.MESSAGE_INACTIVE_SOURCE:
+ case Constants.MESSAGE_ROUTING_CHANGE:
+ case Constants.MESSAGE_SET_STREAM_PATH:
+ case Constants.MESSAGE_TEXT_VIEW_ON:
+ sendCecCommandWithRetries(command, callback);
+ break;
+ default:
+ sendCecCommandWithoutRetries(command, callback);
+ }
+ }
+
+ /**
+ * Create a {@link ResendCecCommandAction} that will retry sending the CEC message if it fails.
+ * @param command command to be sent on the CEC bus.
+ * @param callback callback for handling the result of sending the command.
+ */
+ @ServiceThreadOnly
+ private void sendCecCommandWithRetries(HdmiCecMessage command,
+ @Nullable SendMessageCallback callback) {
+ assertRunOnServiceThread();
+ HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0);
+ if (localDevice != null) {
+ sendCecCommandWithoutRetries(command, new SendMessageCallback() {
+ @Override
+ public void onSendCompleted(int result) {
+ if (result != SendMessageResult.SUCCESS) {
+ localDevice.addAndStartAction(new
+ ResendCecCommandAction(localDevice, command, callback));
+ }
+ }
+ });
+ }
+ }
+
+
/**
* Transmit a CEC command to CEC bus.
*
@@ -1525,7 +1570,8 @@
* @param callback interface used to the result of send command
*/
@ServiceThreadOnly
- void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
+ void sendCecCommandWithoutRetries(HdmiCecMessage command,
+ @Nullable SendMessageCallback callback) {
assertRunOnServiceThread();
if (command.getValidationResult() == HdmiCecMessageValidator.OK
&& verifyPhysicalAddresses(command)) {
@@ -1538,37 +1584,6 @@
}
}
- @ServiceThreadOnly
- void sendCecCommand(HdmiCecMessage command) {
- assertRunOnServiceThread();
- switch (command.getOpcode()) {
- case Constants.MESSAGE_ACTIVE_SOURCE:
- case Constants.MESSAGE_IMAGE_VIEW_ON:
- case Constants.MESSAGE_INACTIVE_SOURCE:
- case Constants.MESSAGE_ROUTING_CHANGE:
- case Constants.MESSAGE_SET_STREAM_PATH:
- case Constants.MESSAGE_TEXT_VIEW_ON:
- sendCecCommandWithRetries(command);
- break;
- default:
- sendCecCommand(command, null);
- }
- }
-
- /**
- * Create a {@link SendCecCommandAction} that will retry to send the CEC message in case it
- * fails.
- * @param command that has to be sent in the CEC bus.
- */
- @ServiceThreadOnly
- public void sendCecCommandWithRetries(HdmiCecMessage command) {
- assertRunOnServiceThread();
- HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0);
- if (localDevice != null) {
- localDevice.addAndStartAction(new SendCecCommandAction(localDevice, command));
- }
- }
-
/**
* Send <Feature Abort> command on the given CEC message if possible.
* If the aborted message is invalid, then it wont send the message.
diff --git a/services/core/java/com/android/server/hdmi/SendCecCommandAction.java b/services/core/java/com/android/server/hdmi/ResendCecCommandAction.java
similarity index 62%
rename from services/core/java/com/android/server/hdmi/SendCecCommandAction.java
rename to services/core/java/com/android/server/hdmi/ResendCecCommandAction.java
index 000ada8..b130c46 100644
--- a/services/core/java/com/android/server/hdmi/SendCecCommandAction.java
+++ b/services/core/java/com/android/server/hdmi/ResendCecCommandAction.java
@@ -16,23 +16,29 @@
package com.android.server.hdmi;
-import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.hardware.tv.hdmi.cec.SendMessageResult;
import android.util.Slog;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
/**
- * Action that sends a CEC command. If the message fails to be sent, it tries again for
- * RETRANSMISSION_COUNT times.
+ * Action that retries RETRANSMISSION_COUNT times to send a CEC command when the first attempt to
+ * send the message failed.
+ *
+ * This action starts with a delay of SEND_COMMAND_RETRY_MS milliseconds.
+ *
+ * If this action can't be started it will be canceled and not deferred.
+ * See {@link HdmiCecLocalDevice#addAndStartAction}.
*/
-public class SendCecCommandAction extends HdmiCecFeatureAction {
- private static final String TAG = "SendCecCommandAction";
- private static final int RETRANSMISSION_COUNT = 2;
+public class ResendCecCommandAction extends HdmiCecFeatureAction {
+ private static final String TAG = "ResendCecCommandAction";
+ private static final int RETRANSMISSION_COUNT = 1;
private static final int STATE_WAIT_FOR_RESEND_COMMAND = 1;
static final int SEND_COMMAND_RETRY_MS = 300;
private final HdmiCecMessage mCommand;
private int mRetransmissionCount = 0;
+ private final SendMessageCallback mResultCallback;
private final SendMessageCallback mCallback = new SendMessageCallback(){
@Override
public void onSendCompleted(int result) {
@@ -41,20 +47,26 @@
mState = STATE_WAIT_FOR_RESEND_COMMAND;
addTimer(mState, SEND_COMMAND_RETRY_MS);
} else {
+ if (mResultCallback != null) {
+ mResultCallback.onSendCompleted(result);
+ }
finish();
}
}
};
- SendCecCommandAction(HdmiCecLocalDevice source, HdmiCecMessage command) {
+ ResendCecCommandAction(HdmiCecLocalDevice source, HdmiCecMessage command,
+ SendMessageCallback callback) {
super(source);
mCommand = command;
+ mResultCallback = callback;
+ mState = STATE_WAIT_FOR_RESEND_COMMAND;
+ addTimer(mState, SEND_COMMAND_RETRY_MS);
}
@Override
boolean start() {
- Slog.d(TAG, "SendCecCommandAction started");
- sendCommand(mCommand, mCallback);
+ Slog.d(TAG, "ResendCecCommandAction started");
return true;
}
@@ -66,8 +78,8 @@
return;
}
if (mState == STATE_WAIT_FOR_RESEND_COMMAND) {
- Slog.d(TAG, "sendCecCommand failed, retry");
- sendCommand(mCommand, mCallback);
+ Slog.d(TAG, "sendCommandWithoutRetries failed, retry");
+ sendCommandWithoutRetries(mCommand, mCallback);
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index eb1a0e2..09f373f 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -421,6 +421,9 @@
android.hardware.contexthub.IContextHubCallback.Stub {
private final int mContextHubId;
private final ICallback mCallback;
+ // 9a17008d-6bf1-445a-9011-6d21bd985b6c
+ private static final byte[] UUID = {-102, 23, 0, -115, 107, -15, 68, 90,
+ -112, 17, 109, 33, -67, -104, 91, 108};
ContextHubAidlCallback(int contextHubId, ICallback callback) {
mContextHubId = contextHubId;
@@ -463,6 +466,10 @@
// TODO(271471342): Implement
}
+ public byte[] getUuid() {
+ return UUID;
+ }
+
@Override
public String getInterfaceHash() {
return android.hardware.contexthub.IContextHubCallback.HASH;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index af6c1a2..5a81a1a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2998,12 +2998,14 @@
// action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
// installer app or null for registered apps. The callback only need to send back to the
// registered apps so we check the null condition here.
- notifyPackageMonitor(action, pkg, extras, userIds);
+ notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds);
}
}
- void notifyPackageMonitor(String action, String pkg, Bundle extras, int[] userIds) {
- mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds);
+ void notifyPackageMonitor(String action, String pkg, Bundle extras, int[] userIds,
+ int[] instantUserIds) {
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
+ instantUserIds);
}
void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
@@ -4053,7 +4055,7 @@
packageName, dontKillApp, componentNames, packageUid, reason, userIds,
instantUserIds, broadcastAllowList));
mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
- packageUid, reason, userIds);
+ packageUid, reason, userIds, instantUserIds);
}
/**
@@ -4297,6 +4299,7 @@
mAppsFilter.onUserDeleted(snapshotComputer(), userId);
}
mInstantAppRegistry.onUserRemoved(userId);
+ mPackageMonitorCallbackHelper.onUserRemoved(userId);
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index c582321..20d1508 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -68,6 +68,21 @@
}
}
+ public void onUserRemoved(int userId) {
+ RemoteCallbackList<IRemoteCallback> callbacks;
+ synchronized (mLock) {
+ callbacks = mCallbacks;
+ }
+ int registerCount = callbacks.getRegisteredCallbackCount();
+ for (int i = 0; i < registerCount; i++) {
+ int registerUserId = (int) callbacks.getRegisteredCallbackCookie(i);
+ if (registerUserId == userId) {
+ IRemoteCallback callback = callbacks.getRegisteredCallbackItem(i);
+ unregisterPackageMonitorCallback(callback);
+ }
+ }
+ }
+
public void notifyPackageAddedForNewUsers(String packageName,
@AppIdInt int appId, @NonNull int[] userIds, @NonNull int[] instantUserIds,
int dataLoaderType) {
@@ -78,7 +93,7 @@
extras.putInt(Intent.EXTRA_UID, uid);
extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras ,
- userIds /* userIds */);
+ userIds /* userIds */, instantUserIds);
}
public void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
@@ -91,11 +106,13 @@
}
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
- notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */);
+ notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */,
+ null /* instantUserIds */);
}
public void notifyPackageChanged(String packageName, boolean dontKillApp,
- ArrayList<String> componentNames, int packageUid, String reason, int[] userIds) {
+ ArrayList<String> componentNames, int packageUid, String reason, int[] userIds,
+ int[] instantUserIds) {
Bundle extras = new Bundle(4);
extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0));
String[] nameList = new String[componentNames.size()];
@@ -106,11 +123,12 @@
if (reason != null) {
extras.putString(Intent.EXTRA_REASON, reason);
}
- notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds);
+ notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds,
+ instantUserIds);
}
public void notifyPackageMonitor(String action, String pkg, Bundle extras,
- int[] userIds) {
+ int[] userIds, int[] instantUserIds) {
if (!isAllowedCallbackAction(action)) {
return;
}
@@ -122,7 +140,12 @@
} else {
resolvedUserIds = userIds;
}
- doNotifyCallbacks(action, pkg, extras, resolvedUserIds);
+
+ if (ArrayUtils.isEmpty(instantUserIds)) {
+ doNotifyCallbacks(action, pkg, extras, resolvedUserIds);
+ } else {
+ doNotifyCallbacks(action, pkg, extras, instantUserIds);
+ }
} catch (RemoteException e) {
// do nothing
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 89aff9e..893bc11a 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -633,7 +633,8 @@
(callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
mPm.snapshotComputer(), callingUid, intentExtras),
options));
- mPm.notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId});
+ mPm.notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
+ null /* instantUserIds */);
}
/**
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index a622d07..01e0fbd 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -88,6 +88,15 @@
"include-filter": "android.uidmigration.cts"
}
]
+ },
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "file_patterns": ["(/|^)PackageMonitorCallbackHelper\\.java"],
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.EphemeralTest#testGetSearchableInfo"
+ }
+ ]
}
],
"presubmit-large": [
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index fc88776..46b3db8 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -92,6 +92,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManagerInternal;
@@ -648,6 +649,8 @@
SettingsObserver mSettingsObserver;
ModifierShortcutManager mModifierShortcutManager;
+ /** Currently fully consumed key codes per device */
+ private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
PowerManager.WakeLock mBroadcastWakeLock;
PowerManager.WakeLock mPowerKeyWakeLock;
boolean mHavePendingMediaKeyRepeatWithWakeLock;
@@ -1892,7 +1895,7 @@
mDisplayId = displayId;
}
- int handleHomeButton(IBinder focusedToken, KeyEvent event) {
+ boolean handleHomeButton(IBinder focusedToken, KeyEvent event) {
final boolean keyguardOn = keyguardOn();
final int repeatCount = event.getRepeatCount();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -1913,12 +1916,12 @@
mHomePressed = false;
if (mHomeConsumed) {
mHomeConsumed = false;
- return -1;
+ return true;
}
if (canceled) {
Log.i(TAG, "Ignoring HOME; event canceled.");
- return -1;
+ return true;
}
// Delay handling home if a double-tap is possible.
@@ -1930,13 +1933,13 @@
mHomeDoubleTapPending = true;
mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
ViewConfiguration.getDoubleTapTimeout());
- return -1;
+ return true;
}
}
// Post to main thread to avoid blocking input pipeline.
mHandler.post(() -> handleShortPressOnHome(mDisplayId));
- return -1;
+ return true;
}
final KeyInterceptionInfo info =
@@ -1948,12 +1951,12 @@
|| (info.layoutParamsType == TYPE_NOTIFICATION_SHADE
&& isKeyguardShowing())) {
// the "app" is keyguard, so give it the key
- return 0;
+ return false;
}
for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) {
if (info.layoutParamsType == t) {
// don't do anything, but also don't pass it to the app
- return -1;
+ return true;
}
}
}
@@ -1978,7 +1981,7 @@
event.getEventTime()));
}
}
- return -1;
+ return true;
}
private void handleDoubleTapOnHome() {
@@ -3042,24 +3045,21 @@
@Override
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
- final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
- final int repeatCount = event.getRepeatCount();
- final int metaState = event.getMetaState();
final int flags = event.getFlags();
- final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
- final boolean canceled = event.isCanceled();
- final int displayId = event.getDisplayId();
- final long key_consumed = -1;
- final long key_not_consumed = 0;
+ final long keyConsumed = -1;
+ final long keyNotConsumed = 0;
+ final int deviceId = event.getDeviceId();
if (DEBUG_INPUT) {
- Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
- + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
+ Log.d(TAG,
+ "interceptKeyTi keyCode=" + keyCode + " action=" + event.getAction()
+ + " repeatCount=" + event.getRepeatCount() + " keyguardOn="
+ + keyguardOn() + " canceled=" + event.isCanceled());
}
if (mKeyCombinationManager.isKeyConsumed(event)) {
- return key_consumed;
+ return keyConsumed;
}
if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
@@ -3070,8 +3070,54 @@
}
}
- // Cancel any pending meta actions if we see any other keys being pressed between the down
- // of the meta key and its corresponding up.
+ Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
+ if (consumedKeys == null) {
+ consumedKeys = new HashSet<>();
+ mConsumedKeysForDevice.put(deviceId, consumedKeys);
+ }
+
+ if (interceptSystemKeysAndShortcuts(focusedToken, event)
+ && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ consumedKeys.add(keyCode);
+ return keyConsumed;
+ }
+
+ boolean needToConsumeKey = consumedKeys.contains(keyCode);
+ if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) {
+ consumedKeys.remove(keyCode);
+ if (consumedKeys.isEmpty()) {
+ mConsumedKeysForDevice.remove(deviceId);
+ }
+ }
+
+ return needToConsumeKey ? keyConsumed : keyNotConsumed;
+ }
+
+ // You can only start consuming the key gesture if ACTION_DOWN and repeat count
+ // is 0. If you start intercepting the key halfway, then key will not be consumed
+ // and will be sent to apps for processing too.
+ // e.g. If a certain combination is only handled on ACTION_UP (i.e.
+ // interceptShortcut() returns true only for ACTION_UP), then since we already
+ // sent the ACTION_DOWN events to the application, we MUST also send the
+ // ACTION_UP to the application.
+ // So, to ensure that your intercept logic works properly, and we don't send any
+ // conflicting events to application, make sure to consume the event on
+ // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential
+ // to maintain event parity and to not have incomplete key gestures.
+ @SuppressLint("MissingPermission")
+ private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
+ final boolean keyguardOn = keyguardOn();
+ final int keyCode = event.getKeyCode();
+ final int repeatCount = event.getRepeatCount();
+ final int metaState = event.getMetaState();
+ final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ final boolean canceled = event.isCanceled();
+ final int displayId = event.getDisplayId();
+ final int deviceId = event.getDeviceId();
+ final boolean firstDown = down && repeatCount == 0;
+
+ // Cancel any pending meta actions if we see any other keys being pressed between the
+ // down of the meta key and its corresponding up.
if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
mPendingMetaAction = false;
}
@@ -3085,50 +3131,49 @@
dismissKeyboardShortcutsMenu();
mPendingMetaAction = false;
mPendingCapsLockToggle = false;
- return key_consumed;
+ return true;
}
}
- switch(keyCode) {
+ switch (keyCode) {
case KeyEvent.KEYCODE_HOME:
- logKeyboardSystemsEvent(event, FrameworkStatsLog
- .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
+ logKeyboardSystemsEvent(event,
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
return handleHomeShortcuts(displayId, focusedToken, event);
case KeyEvent.KEYCODE_MENU:
// Hijack modified menu keys for debugging features
final int chordBug = KeyEvent.META_SHIFT_ON;
- if (down && repeatCount == 0) {
- if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) {
- Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
- null, null, null, 0, null, null);
- return key_consumed;
- }
+ if (mEnableShiftMenuBugReports && firstDown
+ && (metaState & chordBug) == chordBug) {
+ Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
+ null, null, null, 0, null, null);
+ return true;
}
break;
case KeyEvent.KEYCODE_RECENT_APPS:
- if (down && repeatCount == 0) {
+ if (firstDown) {
showRecentApps(false /* triggeredFromAltTab */);
- logKeyboardSystemsEvent(event, FrameworkStatsLog
- .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
+ logKeyboardSystemsEvent(event,
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_APP_SWITCH:
if (!keyguardOn) {
- if (down && repeatCount == 0) {
+ if (firstDown) {
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
}
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_A:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
- event.getDeviceId(),
- event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
- return key_consumed;
+ deviceId, event.getEventTime(),
+ AssistUtils.INVOCATION_TYPE_UNKNOWN);
+ return true;
}
break;
case KeyEvent.KEYCODE_H:
@@ -3138,73 +3183,73 @@
}
break;
case KeyEvent.KEYCODE_I:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
showSystemSettings();
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_L:
- if (down && event.isMetaPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed()) {
lockNow(null /* options */);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_N:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
if (event.isCtrlPressed()) {
sendSystemKeyToStatusBarAsync(event);
} else {
toggleNotificationPanel();
}
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_S:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_T:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
toggleTaskbar();
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_DPAD_UP:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
statusbar.goToFullscreenFromSplit();
+ return true;
}
- return key_consumed;
}
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
enterStageSplitFromRunningApp(true /* leftOrTop */);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
enterStageSplitFromRunningApp(false /* leftOrTop */);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_SLASH:
- if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
+ if (firstDown && event.isMetaPressed() && !keyguardOn) {
toggleKeyboardShortcutsMenu(event.getDeviceId());
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_ASSIST:
Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_VOICE_ASSIST:
Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
+ " interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_VIDEO_APP_1:
case KeyEvent.KEYCODE_VIDEO_APP_2:
case KeyEvent.KEYCODE_VIDEO_APP_3:
@@ -3222,7 +3267,7 @@
case KeyEvent.KEYCODE_DEMO_APP_3:
case KeyEvent.KEYCODE_DEMO_APP_4:
Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
if (down) {
@@ -3262,20 +3307,20 @@
startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
UserHandle.CURRENT_OR_SELF);
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
if (down) {
mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
if (down) {
mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
// TODO: Add logic
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE:
@@ -3283,7 +3328,7 @@
// On TVs or when the configuration is enabled, volume keys never
// go to the foreground app.
dispatchDirectAudioEvent(event);
- return key_consumed;
+ return true;
}
// If the device is in VR mode and keys are "internal" (e.g. on the side of the
@@ -3292,26 +3337,23 @@
if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) {
final InputDevice d = event.getDevice();
if (d != null && !d.isExternal()) {
- return key_consumed;
+ return true;
}
}
break;
case KeyEvent.KEYCODE_TAB:
- if (down && event.isMetaPressed()) {
- if (!keyguardOn && isUserSetupComplete()) {
+ if (firstDown && !keyguardOn && isUserSetupComplete()) {
+ if (event.isMetaPressed()) {
showRecentApps(false);
- return key_consumed;
- }
- } else if (down && repeatCount == 0) {
- // Display task switcher for ALT-TAB.
- if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
+ return true;
+ } else if (mRecentAppsHeldModifiers == 0) {
final int shiftlessModifiers =
event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
if (KeyEvent.metaStateHasModifiers(
shiftlessModifiers, KeyEvent.META_ALT_ON)) {
mRecentAppsHeldModifiers = shiftlessModifiers;
showRecentApps(true);
- return key_consumed;
+ return true;
}
}
}
@@ -3323,18 +3365,18 @@
msg.setAsynchronous(true);
msg.sendToTarget();
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_NOTIFICATION:
if (!down) {
toggleNotificationPanel();
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_SEARCH:
- if (down && repeatCount == 0 && !keyguardOn()) {
- switch(mSearchKeyBehavior) {
+ if (firstDown && !keyguardOn) {
+ switch (mSearchKeyBehavior) {
case SEARCH_BEHAVIOR_TARGET_ACTIVITY: {
launchTargetSearchActivity();
- return key_consumed;
+ return true;
}
case SEARCH_BEHAVIOR_DEFAULT_SEARCH:
default:
@@ -3343,21 +3385,18 @@
}
break;
case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
- if (down && repeatCount == 0) {
+ if (firstDown) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
sendSwitchKeyboardLayout(event, direction);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_SPACE:
// Handle keyboard layout switching. (META + SPACE)
- if ((metaState & KeyEvent.META_META_MASK) == 0) {
- return key_not_consumed;
- }
- if (down && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed()) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
sendSwitchKeyboardLayout(event, direction);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_META_LEFT:
@@ -3382,7 +3421,7 @@
mPendingMetaAction = false;
}
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
if (down) {
@@ -3398,14 +3437,14 @@
&& (metaState & mRecentAppsHeldModifiers) == 0) {
mRecentAppsHeldModifiers = 0;
hideRecentApps(true, false);
- return key_consumed;
+ return true;
}
// Toggle Caps Lock on META-ALT.
if (mPendingCapsLockToggle) {
mInputManagerInternal.toggleCapsLock(event.getDeviceId());
mPendingCapsLockToggle = false;
- return key_consumed;
+ return true;
}
}
break;
@@ -3415,24 +3454,18 @@
case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL:
Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
+ " interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
}
-
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
- return key_consumed;
+ return true;
}
// Reserve all the META modifier combos for system behavior
- if ((metaState & KeyEvent.META_META_ON) != 0) {
- return key_consumed;
- }
-
- // Let the application handle the key.
- return key_not_consumed;
+ return (metaState & KeyEvent.META_META_ON) != 0;
}
- private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
+ private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
// First we always handle the home key here, so applications
// can never break it, although if keyguard is on, we do let
// it handle it, because that gives us the correct 5 second
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 32104f6..80c21f4 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -16246,7 +16246,7 @@
}
NP = in.readInt();
- if (NP > 1000) {
+ if (NP > 10000) {
throw new ParcelFormatException("File corrupt: too many processes " + NP);
}
for (int ip = 0; ip < NP; ip++) {
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index ac97038..b178269 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -98,7 +98,7 @@
Preconditions.checkState(!mRegistered);
mRegistered = true;
- mPackageMonitor.register(mContext, UserHandle.ALL, /*externalStorage=*/ true, mHandler);
+ mPackageMonitor.register(mContext, UserHandle.ALL, mHandler);
mServiceSupplier.register(this);
onServiceChanged(false);
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index bf511adf0..2394da9 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -433,7 +433,7 @@
final byte[] data = saveParamsToXml();
final File launchParamFolder = getLaunchParamFolder(mUserId);
- if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdirs()) {
+ if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdir()) {
Slog.w(TAG, "Failed to create folder for " + mUserId);
return;
}
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index 7e0e5a4..5ec0119 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -509,7 +509,7 @@
private static boolean createParentDirectory(String filePath) {
File parentDir = new File(filePath).getParentFile();
- return parentDir.exists() || parentDir.mkdirs();
+ return parentDir.isDirectory() || parentDir.mkdir();
}
private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem {
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 0488247..4343edd 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -333,7 +333,10 @@
return -1;
}
static int getAnonPageAdvice(const Vma& vma) {
- if (vma.inode == 0 && !vma.is_shared) {
+ bool hasReadFlag = (vma.flags & PROT_READ) > 0;
+ bool hasWriteFlag = (vma.flags & PROT_WRITE) > 0;
+ bool hasExecuteFlag = (vma.flags & PROT_EXEC) > 0;
+ if ((hasReadFlag || hasWriteFlag) && !hasExecuteFlag && !vma.is_shared) {
return MADV_PAGEOUT;
}
return -1;
@@ -376,6 +379,9 @@
++coldVmaIndex;
break;
case MADV_PAGEOUT:
+#ifdef DEBUG_COMPACTION
+ ALOGE("Adding to compact vma=%s", vma.name.c_str());
+#endif
if (pageoutVmaIndex < pageoutVmas.size()) {
pageoutVmas[pageoutVmaIndex] = vma;
} else {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c74c727..cb0b9c9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -968,7 +968,7 @@
void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
const std::set<gui::Uid>& uids) {
static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
- sysprop::InputProperties::enable_input_device_usage_metrics().value_or(false);
+ sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return;
mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids);
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index ce022e9..57b5d00 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -53,6 +53,7 @@
<xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="refreshRateThermalThrottlingMapId" type="xs:string" minOccurs="0" />
+ <xs:element name="leadDisplayAddress" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 42a800d..2d4f7a4 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -6,6 +6,7 @@
method public java.math.BigInteger getAddress();
method public String getBrightnessThrottlingMapId();
method public String getDisplayGroup();
+ method public java.math.BigInteger getLeadDisplayAddress();
method public String getPosition();
method public String getRefreshRateThermalThrottlingMapId();
method public String getRefreshRateZoneId();
@@ -16,6 +17,7 @@
method public void setDefaultDisplay(boolean);
method public void setDisplayGroup(String);
method public void setEnabled(boolean);
+ method public void setLeadDisplayAddress(java.math.BigInteger);
method public void setPosition(String);
method public void setRefreshRateThermalThrottlingMapId(String);
method public void setRefreshRateZoneId(String);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2f1d67d..7857b25 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5132,8 +5132,11 @@
boolean deviceWideOnly) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)
- && (isSystemUid(caller) || hasCallingOrSelfPermission(
- permission.SET_INITIAL_LOCK)));
+ && (isSystemUid(caller)
+ // Accept any permission that ILockSettings#setLockCredential() accepts.
+ || hasCallingOrSelfPermission(permission.SET_INITIAL_LOCK)
+ || hasCallingOrSelfPermission(permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)
+ || hasCallingOrSelfPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)));
return getPasswordMinimumMetricsUnchecked(userHandle, deviceWideOnly);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2f2e1cb..ec83876 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2876,9 +2876,11 @@
mSystemServiceManager.startService(SAFETY_CENTER_SERVICE_CLASS);
t.traceEnd();
- t.traceBegin("AppSearchModule");
- mSystemServiceManager.startService(APPSEARCH_MODULE_LIFECYCLE_CLASS);
- t.traceEnd();
+ if (!isTv) {
+ t.traceBegin("AppSearchModule");
+ mSystemServiceManager.startService(APPSEARCH_MODULE_LIFECYCLE_CLASS);
+ t.traceEnd();
+ }
if (SystemProperties.getBoolean("ro.config.isolated_compilation_enabled", false)) {
t.traceBegin("IsolatedCompilationService");
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 1a8e00c..0ed863e 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -19,6 +19,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.mockito.ArgumentMatchers.any;
@@ -145,8 +146,7 @@
.mockStatic(SystemServerInitThreadPool.class)
.startMocking();
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- spyOn(mContext);
+ mContext = spy(InstrumentationRegistry.getInstrumentation().getContext());
mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
mIsLargeScreen = mContext.getResources().getConfiguration()
diff --git a/services/tests/PackageManagerComponentOverrideTests/OWNERS b/services/tests/PackageManagerComponentOverrideTests/OWNERS
index d825dfd..ecdd028 100644
--- a/services/tests/PackageManagerComponentOverrideTests/OWNERS
+++ b/services/tests/PackageManagerComponentOverrideTests/OWNERS
@@ -1 +1,2 @@
+# Bug component: 36137
include /services/core/java/com/android/server/pm/OWNERS
diff --git a/services/tests/PackageManagerServiceTests/OWNERS b/services/tests/PackageManagerServiceTests/OWNERS
index 86ae581..95f146d 100644
--- a/services/tests/PackageManagerServiceTests/OWNERS
+++ b/services/tests/PackageManagerServiceTests/OWNERS
@@ -1 +1,2 @@
+# Bug component: 36137
include /PACKAGE_MANAGER_OWNERS
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 130e6ad..4b124ca 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -19,8 +19,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
-import android.view.Display;
import android.view.DisplayAddress;
import androidx.test.filters.SmallTest;
@@ -65,9 +65,15 @@
Layout testLayout = new Layout();
createDefaultDisplay(testLayout, 123456L);
- createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null);
- createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1");
- createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2");
+ createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null,
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1",
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2",
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(testLayout, 1092L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(78910L));
+ testLayout.postProcessLocked();
assertEquals(testLayout, configLayout);
}
@@ -78,7 +84,9 @@
Layout testLayout = new Layout();
createDefaultDisplay(testLayout, 78910L);
- createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null);
+ createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null,
+ /* leadDisplayAddress= */ null);
+ testLayout.postProcessLocked();
assertEquals(testLayout, configLayout);
}
@@ -122,20 +130,132 @@
Layout testLayout = new Layout();
testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(345L),
/* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
- mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY,
- /* brightnessThrottlingMapId= */ "brightness1",
+ mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",
/* refreshRateZoneId= */ "zone1",
/* refreshRateThermalThrottlingMapId= */ "rr1");
testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
/* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
- mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY,
- /* brightnessThrottlingMapId= */ "brightness2",
+ mDisplayIdProducerMock, Layout.Display.POSITION_REAR,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",
/* refreshRateZoneId= */ "zone2",
/* refreshRateThermalThrottlingMapId= */ "rr2");
+ testLayout.postProcessLocked();
assertEquals(testLayout, configLayout);
}
+ @Test
+ public void testLeadDisplayAddress() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 111L, /* enabled= */ true, /* group= */ null,
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(111L));
+
+ layout.postProcessLocked();
+
+ com.android.server.display.layout.Layout.Display display111 =
+ layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(111));
+ com.android.server.display.layout.Layout.Display display222 =
+ layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+ assertEquals(display111.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+ assertEquals(display222.getLeadDisplayId(), display111.getLogicalDisplayId());
+ }
+
+ @Test
+ public void testLeadDisplayAddress_defaultDisplay() {
+ Layout layout = new Layout();
+ createDefaultDisplay(layout, 123456L);
+
+ layout.postProcessLocked();
+
+ com.android.server.display.layout.Layout.Display display =
+ layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(123456));
+ assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+ }
+
+ @Test
+ public void testLeadDisplayAddress_noLeadDisplay() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+ /* leadDisplayAddress= */ null);
+
+ layout.postProcessLocked();
+
+ com.android.server.display.layout.Layout.Display display =
+ layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+ assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+ }
+
+ @Test
+ public void testLeadDisplayAddress_selfLeadDisplayForNonDefaultDisplay() {
+ Layout layout = new Layout();
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when the display points out"
+ + " itself as a lead display",
+ IllegalArgumentException.class,
+ () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+ /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+ mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
+ DisplayAddress.fromPhysicalDisplayId(123L),
+ /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+ /* refreshRateThermalThrottlingMapId= */ null));
+ }
+
+ @Test
+ public void testLeadDisplayAddress_wrongLeadDisplayForDefaultDisplay() {
+ Layout layout = new Layout();
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when the default display "
+ + "has a lead display",
+ IllegalArgumentException.class,
+ () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+ /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+ mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
+ DisplayAddress.fromPhysicalDisplayId(987L),
+ /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+ /* refreshRateThermalThrottlingMapId= */ null));
+ }
+
+ @Test
+ public void testLeadDisplayAddress_notExistingLeadDisplayForNonDefaultDisplay() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(111L));
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when a lead display doesn't"
+ + " exist", IllegalArgumentException.class, () -> layout.postProcessLocked());
+ }
+
+ @Test
+ public void testLeadDisplayAddress_leadDisplayInDifferentDisplayGroup() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ "group1",
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ "group2",
+ DisplayAddress.fromPhysicalDisplayId(111L));
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+ + "display in the different group",
+ IllegalArgumentException.class, () -> layout.postProcessLocked());
+ }
+
+ @Test
+ public void testLeadDisplayAddress_cyclicLeadDisplay() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(222L));
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(333L));
+ createNonDefaultDisplay(layout, 333L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(222L));
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+ + "display in the different group",
+ IllegalArgumentException.class, () -> layout.postProcessLocked());
+ }
+
////////////////////
// Helper Methods //
////////////////////
@@ -145,10 +265,11 @@
mDisplayIdProducerMock);
}
- private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group) {
+ private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group,
+ DisplayAddress leadDisplayAddress) {
layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
- Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null,
+ leadDisplayAddress, /* brightnessThrottlingMapId= */ null,
/* refreshRateZoneId= */ null,
/* refreshRateThermalThrottlingMapId= */ null);
}
@@ -177,6 +298,10 @@
+ "<display enabled=\"false\" displayGroup=\"group2\">\n"
+ "<address>786</address>\n"
+ "</display>\n"
+ + "<display enabled=\"true\">\n"
+ + "<address>1092</address>\n"
+ + "<leadDisplayAddress>78910</leadDisplayAddress>\n"
+ + "</display>\n"
+ "</layout>\n"
+ "<layout>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 1eec70d..3e695c9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -604,13 +604,13 @@
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
/* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
mIdProducer, POSITION_UNKNOWN,
- /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
+ /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ "concurrent",
/* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
/* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
mIdProducer, POSITION_UNKNOWN,
- /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
+ /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ "concurrent",
/* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
@@ -646,7 +646,7 @@
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1)
.getLeadDisplayIdLocked());
- assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2)
+ assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device2)
.getLeadDisplayIdLocked());
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
.getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
@@ -811,7 +811,7 @@
mIdProducer);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
/* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
- mIdProducer, POSITION_REAR, Display.DEFAULT_DISPLAY,
+ mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
/* refreshRateThermalThrottlingMapId= */null);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
@@ -861,7 +861,7 @@
private void createNonDefaultDisplay(Layout layout, DisplayAddress address, boolean enabled,
String group) {
layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
- Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY,
+ Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
/* refreshRateThermalThrottlingMapId= */ null);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 22ad7c4..03439e55 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -969,7 +969,7 @@
mCachedAppOptimizerUnderTest.mLastCompactionStats.clear();
- if (CachedAppOptimizer.ENABLE_FILE_COMPACT) {
+ if (CachedAppOptimizer.ENABLE_SHARED_AND_CODE_COMPACT) {
// We force a some compaction
mCachedAppOptimizerUnderTest.compactApp(processRecord,
CachedAppOptimizer.CompactProfile.SOME, CachedAppOptimizer.CompactSource.APP,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 9698552..6f2cca5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -19,10 +19,9 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.after;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -52,6 +51,7 @@
private static final String FAKE_PACKAGE_NAME = "com.android.server.pm.fakeapp";
private static final int FAKE_PACKAGE_UID = 123;
+ private static final int WAIT_CALLBACK_CALLED_IN_MS = 300;
private final Handler mHandler = new Handler(Looper.getMainLooper());
PackageMonitorCallbackHelper mPackageMonitorCallbackHelper;
@@ -77,10 +77,10 @@
IRemoteCallback callback = createMockPackageMonitorCallback();
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
- FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */);
- Thread.sleep(300);
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+ null /* instantUserIds */);
- verify(callback, never()).sendResult(any());
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@Test
@@ -89,18 +89,17 @@
mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
- FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0});
- Thread.sleep(300);
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */);
- verify(callback, times(1)).sendResult(any());
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
reset(callback);
mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
- FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */);
- Thread.sleep(300);
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+ null /* instantUserIds */);
- verify(callback, never()).sendResult(any());
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@Test
@@ -109,11 +108,12 @@
mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
- FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */);
- Thread.sleep(300);
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+ null /* instantUserIds */);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
- verify(callback, times(1)).sendResult(bundleCaptor.capture());
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+ bundleCaptor.capture());
Bundle bundle = bundleCaptor.getValue();
Intent intent = bundle.getParcelable(
PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
@@ -131,10 +131,10 @@
mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
// Notify for user 10
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
- FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */);
- Thread.sleep(300);
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
+ null /* instantUserIds */);
- verify(callback, never()).sendResult(any());
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@Test
@@ -147,11 +147,11 @@
mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */);
mPackageMonitorCallbackHelper.notifyPackageChanged(FAKE_PACKAGE_NAME,
false /* dontKillApp */, components, FAKE_PACKAGE_UID, null /* reason */,
- new int[]{0} /* userIds */);
- Thread.sleep(300);
+ new int[]{0} /* userIds */, null /* instantUserIds */);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
- verify(callback, times(1)).sendResult(bundleCaptor.capture());
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+ bundleCaptor.capture());
Bundle bundle = bundleCaptor.getValue();
Intent intent = bundle.getParcelable(
PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
@@ -175,10 +175,10 @@
mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(FAKE_PACKAGE_NAME,
FAKE_PACKAGE_UID, new int[]{0} /* userIds */, new int[0],
PackageInstaller.DATA_LOADER_TYPE_STREAMING);
- Thread.sleep(300);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
- verify(callback, times(1)).sendResult(bundleCaptor.capture());
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+ bundleCaptor.capture());
Bundle bundle = bundleCaptor.getValue();
Intent intent = bundle.getParcelable(
PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
@@ -198,10 +198,10 @@
mPackageMonitorCallbackHelper.notifyResourcesChanged(true /* mediaStatus */,
true /* replacing */, new String[]{FAKE_PACKAGE_NAME},
new int[]{FAKE_PACKAGE_UID} /* uids */);
- Thread.sleep(300);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
- verify(callback, times(1)).sendResult(bundleCaptor.capture());
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+ bundleCaptor.capture());
Bundle bundle = bundleCaptor.getValue();
Intent intent = bundle.getParcelable(
PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SendCecCommandActionPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
similarity index 74%
rename from services/tests/servicestests/src/com/android/server/hdmi/SendCecCommandActionPlaybackTest.java
rename to services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
index 9022b18..061e1f9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SendCecCommandActionPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
@@ -17,11 +17,12 @@
package com.android.server.hdmi;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
-import static com.android.server.hdmi.SendCecCommandAction.SEND_COMMAND_RETRY_MS;
+import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_MS;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Looper;
@@ -38,11 +39,11 @@
import java.util.Collections;
-/** Tests for {@link SendCecCommandAction} */
+/** Tests for {@link ResendCecCommandAction} */
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
-public class SendCecCommandActionPlaybackTest {
+public class ResendCecCommandActionPlaybackTest {
private static final String TAG = "SendCecCommandActionPlaybackTest";
private HdmiControlService mHdmiControlService;
private HdmiCecLocalDevice mPlaybackDevice;
@@ -51,6 +52,7 @@
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
private int mPhysicalAddress;
+ private boolean mIsPowerStandby;
@Before
public void setUp() throws Exception {
@@ -63,7 +65,7 @@
@Override
boolean isPowerStandby() {
- return false;
+ return mIsPowerStandby;
}
@Override
@@ -71,6 +73,7 @@
// do nothing
}
};
+ mIsPowerStandby = false;
mMyLooper = mTestLooper.getLooper();
mHdmiControlService.setIoLooper(mMyLooper);
@@ -149,6 +152,35 @@
}
@Test
+ public void sendCecCommand_inactiveSource_onStandby_powerControlModeNone_sendMessage() {
+ mPlaybackDevice.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
+ HdmiControlManager.POWER_CONTROL_MODE_NONE);
+ mPlaybackDevice.setActiveSource(mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
+ mPhysicalAddress, "SendCecCommandActionPlaybackTest");
+ mIsPowerStandby = true;
+ mPlaybackDevice.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+ mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+ }
+
+ @Test
+ public void sendCecCommand_inactiveSource_onStandby_initiatedByCec_sendMessage() {
+ mPlaybackDevice.setActiveSource(mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
+ mPhysicalAddress, "SendCecCommandActionPlaybackTest");
+ mIsPowerStandby = true;
+ mPlaybackDevice.onStandby(true, HdmiControlService.STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+ mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+ }
+
+ @Test
public void sendCecCommand_routingChange_sendMessageFails_resendMessage() {
mNativeWrapper.setMessageSendResult(Constants.MESSAGE_ROUTING_CHANGE,
SendMessageResult.BUSY);
@@ -293,4 +325,54 @@
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPhysicalAddress);
}
+
+ @Test
+ public void sendCecCommand_inactiveSource_sendMessageFails_afterStandby_noResendMessage() {
+ mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INACTIVE_SOURCE,
+ SendMessageResult.BUSY);
+ mPlaybackDevice.setActiveSource(mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
+ mPhysicalAddress, "SendCecCommandActionPlaybackTest");
+ mIsPowerStandby = true;
+ mPlaybackDevice.onStandby(true, HdmiControlService.STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+ mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+ assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+
+ mNativeWrapper.clearResultMessages();
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+
+ mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+ }
+
+ @Test
+ public void sendCecCommand_onStandby_removeAction_noResendMessage() {
+ mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INACTIVE_SOURCE,
+ SendMessageResult.BUSY);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+ mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+ mHdmiControlService.sendCecCommand(inactiveSourceMessage);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+
+ mTestLooper.dispatchAll();
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+
+ mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SendCecCommandActionTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/hdmi/SendCecCommandActionTvTest.java
rename to services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
index aae8398..b25ea2c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SendCecCommandActionTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
@@ -17,7 +17,7 @@
package com.android.server.hdmi;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
-import static com.android.server.hdmi.SendCecCommandAction.SEND_COMMAND_RETRY_MS;
+import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -38,11 +38,11 @@
import java.util.Collections;
-/** Tests for {@link SendCecCommandAction} */
+/** Tests for {@link ResendCecCommandAction} */
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
-public class SendCecCommandActionTvTest {
+public class ResendCecCommandActionTvTest {
private static final String TAG = "SendCecCommandActionTvTest";
private HdmiControlService mHdmiControlService;
private HdmiCecLocalDevice mTvDevice;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 7cb7c79d..2b19ad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -107,6 +107,9 @@
InstrumentationRegistry.getInstrumentation().getContext().getCacheDir();
mFolder = new File(cacheFolder, "launch_params_tests");
deleteRecursively(mFolder);
+ mFolder.mkdir();
+ mUserFolderGetter.apply(TEST_USER_ID).mkdir();
+ mUserFolderGetter.apply(ALTERNATIVE_USER_ID).mkdir();
mDisplayUniqueId = "test:" + sNextUniqueId++;
mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index 44bd8c8..52e9652 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -19,9 +19,7 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.common.Timestamp
-import android.tools.common.flicker.subject.exceptions.ExceptionMessageBuilder
-import android.tools.common.flicker.subject.exceptions.InvalidPropertyException
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -85,44 +83,37 @@
@Postsubmit
@Test
fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() {
- // Check if the snapshot appeared during the trace
- var imeSnapshotRemovedTimestamp: Timestamp? = null
+ val layerTrace = flicker.reader.readLayersTrace() ?: error("Unable to read layers trace")
- val layerTrace = flicker.reader.readLayersTrace()
- val layerTraceEntries = layerTrace?.entries?.toList() ?: emptyList()
-
- layerTraceEntries.zipWithNext { prev, next ->
- val prevSnapshotLayerVisible =
- ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers)
- val nextSnapshotLayerVisible =
- ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers)
-
- if (
- imeSnapshotRemovedTimestamp == null &&
- (prevSnapshotLayerVisible && !nextSnapshotLayerVisible)
- ) {
- imeSnapshotRemovedTimestamp = next.timestamp
+ // Find the entries immediately after the IME snapshot has disappeared
+ val imeSnapshotRemovedEntries = layerTrace.entries.asSequence()
+ .zipWithNext { prev, next ->
+ if (ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers) &&
+ !ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers)) {
+ next
+ } else {
+ null
+ }
}
- }
+ .filterNotNull()
- // if so, make an assertion
- imeSnapshotRemovedTimestamp?.let { timestamp ->
- val stateAfterSnapshot =
- layerTrace?.getEntryAt(timestamp) ?: error("State not found for $timestamp")
+ // If we find it, make sure the IME is visible and fully animated in.
+ imeSnapshotRemovedEntries.forEach { entry ->
+ val entrySubject = LayerTraceEntrySubject(entry)
+ val imeLayerSubjects = entrySubject.subjects.filter {
+ ComponentNameMatcher.IME.layerMatchesAnyOf(it.layer) && it.isVisible
+ }
- val imeLayers =
- ComponentNameMatcher.IME.filterLayers(stateAfterSnapshot.visibleLayers.toList())
+ entrySubject
+ .check { "InputMethod must exist and be visible" }
+ .that(imeLayerSubjects.isNotEmpty())
+ .isEqual(true)
- require(imeLayers.isNotEmpty()) { "IME layer not found" }
- if (imeLayers.any { it.color.a != 1.0f }) {
- val errorMsgBuilder =
- ExceptionMessageBuilder()
- .setTimestamp(timestamp)
- .forInvalidProperty("IME layer alpha")
- .setExpected("is 1.0")
- .setActual("not 1.0")
- .addExtraDescription("Filter", ComponentNameMatcher.IME.toLayerIdentifier())
- throw InvalidPropertyException(errorMsgBuilder)
+ imeLayerSubjects.forEach { imeLayerSubject ->
+ imeLayerSubject
+ .check { "alpha" }
+ .that(imeLayerSubject.layer.color.a)
+ .isEqual(1.0f)
}
}
}
diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp
index 90e61c5..0d2f2ef 100644
--- a/tests/UiBench/Android.bp
+++ b/tests/UiBench/Android.bp
@@ -24,6 +24,5 @@
"androidx.recyclerview_recyclerview",
"androidx.leanback_leanback",
],
- certificate: "platform",
test_suites: ["device-tests"],
}
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 47211c5..4fc6ec7 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -18,7 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.test.uibench">
- <uses-permission android:name="android.permission.INJECT_EVENTS" />
<application android:allowBackup="false"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
index 1b2c3c6..06b65a7 100644
--- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
@@ -15,15 +15,11 @@
*/
package com.android.test.uibench;
-import android.app.Instrumentation;
+import android.content.Intent;
import android.os.Bundle;
-import android.os.Looper;
-import android.os.MessageQueue;
-import androidx.appcompat.app.AppCompatActivity;
-import android.view.KeyEvent;
import android.widget.EditText;
-import java.util.concurrent.Semaphore;
+import androidx.appcompat.app.AppCompatActivity;
/**
* Note: currently incomplete, complexity of input continuously grows, instead of looping
@@ -32,7 +28,13 @@
* Simulates typing continuously into an EditText.
*/
public class EditTextTypeActivity extends AppCompatActivity {
- Thread mThread;
+
+ /**
+ * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the
+ * test activity was paused.
+ */
+ private static final String ACTION_CANCEL_TYPING_CALLBACK =
+ "com.android.uibench.action.CANCEL_TYPING_CALLBACK";
private static String sSeedText = "";
static {
@@ -46,9 +48,6 @@
sSeedText = builder.toString();
}
- final Object mLock = new Object();
- boolean mShouldStop = false;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -56,55 +55,13 @@
EditText editText = new EditText(this);
editText.setText(sSeedText);
setContentView(editText);
-
- final Instrumentation instrumentation = new Instrumentation();
- final Semaphore sem = new Semaphore(0);
- MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() {
- @Override
- public boolean queueIdle() {
- // TODO: consider other signaling approaches
- sem.release();
- return true;
- }
- };
- Looper.myQueue().addIdleHandler(handler);
- synchronized (mLock) {
- mShouldStop = false;
- }
- mThread = new Thread(new Runnable() {
- int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L,
- KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE };
- int i = 0;
- @Override
- public void run() {
- while (true) {
- try {
- sem.acquire();
- } catch (InterruptedException e) {
- // TODO, maybe
- }
- int code = codes[i % codes.length];
- if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER;
-
- synchronized (mLock) {
- if (mShouldStop) break;
- }
-
- // TODO: bit of a race here, since the event can arrive after pause/stop.
- // (Can't synchronize on key send, since it's synchronous.)
- instrumentation.sendKeyDownUpSync(code);
- i++;
- }
- }
- });
- mThread.start();
}
@Override
protected void onPause() {
- synchronized (mLock) {
- mShouldStop = true;
- }
+ // Cancel the typing when the test activity was paused.
+ sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags(
+ Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY));
super.onPause();
}
}