Merge "Refactor PropertyInvalidatedCache locks" into tm-qpr-dev
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
index 6b0f78f..0f66fcb 100644
--- a/core/java/android/ddm/DdmHandleViewDebug.java
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -16,12 +16,16 @@
package android.ddm;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import android.util.Log;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
+import com.android.internal.annotations.VisibleForTesting;
+
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
@@ -34,6 +38,7 @@
import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
/**
* Handle various requests related to profiling / debugging of the view system.
@@ -123,14 +128,15 @@
}
if (type == CHUNK_VURT) {
- if (op == VURT_DUMP_HIERARCHY)
+ if (op == VURT_DUMP_HIERARCHY) {
return dumpHierarchy(rootView, in);
- else if (op == VURT_CAPTURE_LAYERS)
+ } else if (op == VURT_CAPTURE_LAYERS) {
return captureLayers(rootView);
- else if (op == VURT_DUMP_THEME)
+ } else if (op == VURT_DUMP_THEME) {
return dumpTheme(rootView);
- else
+ } else {
return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op);
+ }
}
final View targetView = getTargetView(rootView, in);
@@ -207,9 +213,9 @@
/**
* Returns the view hierarchy and/or view properties starting at the provided view.
* Based on the input options, the return data may include:
- * - just the view hierarchy
- * - view hierarchy & the properties for each of the views
- * - just the view properties for a specific view.
+ * - just the view hierarchy
+ * - view hierarchy & the properties for each of the views
+ * - just the view properties for a specific view.
* TODO: Currently this only returns views starting at the root, need to fix so that
* it can return properties of any view.
*/
@@ -220,7 +226,7 @@
long start = System.currentTimeMillis();
- ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024);
+ ByteArrayOutputStream b = new ByteArrayOutputStream(2 * 1024 * 1024);
try {
if (v2) {
ViewDebug.dumpv2(rootView, b);
@@ -304,17 +310,47 @@
* Invokes provided method on the view.
* The method name and its arguments are passed in as inputs via the byte buffer.
* The buffer contains:<ol>
- * <li> len(method name) </li>
- * <li> method name </li>
- * <li> # of args </li>
- * <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
- * The type specifier is a single character as used in JNI:
- * (Z - boolean, B - byte, C - char, S - short, I - int, J - long,
- * F - float, D - double). <p>
- * The type specifier is followed by the actual value of argument.
- * Booleans are encoded via bytes with 0 indicating false.</li>
+ * <li> len(method name) </li>
+ * <li> method name (encoded as UTF-16 2-byte characters) </li>
+ * <li> # of args </li>
+ * <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
+ * The type specifier is one character modelled after JNI signatures:
+ * <ul>
+ * <li>[ - array<br>
+ * This is followed by a second character according to this spec, indicating the
+ * array type, then the array length as an Int, followed by a repeated encoding
+ * of the actual data.
+ * WARNING: Only <b>byte[]</b> is supported currently.
+ * </li>
+ * <li>Z - boolean<br>
+ * Booleans are encoded via bytes with 0 indicating false</li>
+ * <li>B - byte</li>
+ * <li>C - char</li>
+ * <li>S - short</li>
+ * <li>I - int</li>
+ * <li>J - long</li>
+ * <li>F - float</li>
+ * <li>D - double</li>
+ * <li>V - void<br>
+ * NOT followed by a value. Only used for return types</li>
+ * <li>R - String (not a real JNI type, but added for convenience)<br>
+ * Strings are encoded as an unsigned short of the number of <b>bytes</b>,
+ * followed by the actual UTF-8 encoded bytes.
+ * WARNING: This is the same encoding as produced by
+ * ViewHierarchyEncoder#writeString. However, note that this encoding is
+ * different to what DdmHandle#getString() expects, which is used in other places
+ * in this class.
+ * WARNING: Since the length is the number of UTF-8 encoded bytes, Strings can
+ * contain up to 64k ASCII characters, yet depending on the actual data, the true
+ * maximum might be as little as 21844 unicode characters.
+ * <b>null</b> String objects are encoded as an empty string
+ * </li>
+ * </ul>
+ * </li>
* </ol>
* Methods that take no arguments need only specify the method name.
+ *
+ * The return value is encoded the same way as a single parameter (type + value)
*/
private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
int l = in.getInt();
@@ -327,54 +363,17 @@
args = new Object[0];
} else {
int nArgs = in.getInt();
-
argTypes = new Class<?>[nArgs];
args = new Object[nArgs];
- for (int i = 0; i < nArgs; i++) {
- char c = in.getChar();
- switch (c) {
- case 'Z':
- argTypes[i] = boolean.class;
- args[i] = in.get() == 0 ? false : true;
- break;
- case 'B':
- argTypes[i] = byte.class;
- args[i] = in.get();
- break;
- case 'C':
- argTypes[i] = char.class;
- args[i] = in.getChar();
- break;
- case 'S':
- argTypes[i] = short.class;
- args[i] = in.getShort();
- break;
- case 'I':
- argTypes[i] = int.class;
- args[i] = in.getInt();
- break;
- case 'J':
- argTypes[i] = long.class;
- args[i] = in.getLong();
- break;
- case 'F':
- argTypes[i] = float.class;
- args[i] = in.getFloat();
- break;
- case 'D':
- argTypes[i] = double.class;
- args[i] = in.getDouble();
- break;
- default:
- Log.e(TAG, "arg " + i + ", unrecognized type: " + c);
- return createFailChunk(ERR_INVALID_PARAM,
- "Unsupported parameter type (" + c + ") to invoke view method.");
- }
+ try {
+ deserializeMethodParameters(args, argTypes, in);
+ } catch (ViewMethodInvocationSerializationException e) {
+ return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
}
}
- Method method = null;
+ Method method;
try {
method = targetView.getClass().getMethod(methodName, argTypes);
} catch (NoSuchMethodException e) {
@@ -384,7 +383,10 @@
}
try {
- ViewDebug.invokeViewMethod(targetView, method, args);
+ Object result = ViewDebug.invokeViewMethod(targetView, method, args);
+ Class<?> returnType = method.getReturnType();
+ byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result));
+ return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length);
} catch (Exception e) {
Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
String msg = e.getCause().getMessage();
@@ -393,8 +395,6 @@
}
return createFailChunk(ERR_EXCEPTION, msg);
}
-
- return null;
}
private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
@@ -406,7 +406,7 @@
} catch (Exception e) {
Log.e(TAG, "Exception setting layout parameter: " + e);
return createFailChunk(ERR_EXCEPTION, "Error accessing field "
- + param + ":" + e.getMessage());
+ + param + ":" + e.getMessage());
}
return null;
@@ -431,4 +431,175 @@
byte[] data = b.toByteArray();
return new Chunk(CHUNK_VUOP, data, 0, data.length);
}
+
+ /**
+ * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
+ * buffer.
+ *
+ * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
+ * be the same length, and will be set to the argument types of the data read.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static void deserializeMethodParameters(
+ Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
+ ViewMethodInvocationSerializationException {
+ checkArgument(args.length == argTypes.length);
+
+ for (int i = 0; i < args.length; i++) {
+ char typeSignature = in.getChar();
+ boolean isArray = typeSignature == SIG_ARRAY;
+ if (isArray) {
+ char arrayType = in.getChar();
+ if (arrayType != SIG_BYTE) {
+ // This implementation only supports byte-arrays for now.
+ throw new ViewMethodInvocationSerializationException(
+ "Unsupported array parameter type (" + typeSignature
+ + ") to invoke view method @argument " + i);
+ }
+
+ int arrayLength = in.getInt();
+ if (arrayLength > in.remaining()) {
+ // The sender did not actually sent the specified amount of bytes. This
+ // avoids a malformed packet to trigger an out-of-memory error.
+ throw new BufferUnderflowException();
+ }
+
+ byte[] byteArray = new byte[arrayLength];
+ in.get(byteArray);
+
+ argTypes[i] = byte[].class;
+ args[i] = byteArray;
+ } else {
+ switch (typeSignature) {
+ case SIG_BOOLEAN:
+ argTypes[i] = boolean.class;
+ args[i] = in.get() != 0;
+ break;
+ case SIG_BYTE:
+ argTypes[i] = byte.class;
+ args[i] = in.get();
+ break;
+ case SIG_CHAR:
+ argTypes[i] = char.class;
+ args[i] = in.getChar();
+ break;
+ case SIG_SHORT:
+ argTypes[i] = short.class;
+ args[i] = in.getShort();
+ break;
+ case SIG_INT:
+ argTypes[i] = int.class;
+ args[i] = in.getInt();
+ break;
+ case SIG_LONG:
+ argTypes[i] = long.class;
+ args[i] = in.getLong();
+ break;
+ case SIG_FLOAT:
+ argTypes[i] = float.class;
+ args[i] = in.getFloat();
+ break;
+ case SIG_DOUBLE:
+ argTypes[i] = double.class;
+ args[i] = in.getDouble();
+ break;
+ case SIG_STRING: {
+ argTypes[i] = String.class;
+ int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
+ byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
+ in.get(rawStringBuffer);
+ args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
+ break;
+ }
+ default:
+ Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
+ throw new ViewMethodInvocationSerializationException(
+ "Unsupported parameter type (" + typeSignature
+ + ") to invoke view method.");
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
+ * @hide
+ */
+ @VisibleForTesting
+ public static byte[] serializeReturnValue(Class<?> type, Object value)
+ throws ViewMethodInvocationSerializationException, IOException {
+ ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
+ DataOutputStream dos = new DataOutputStream(byteOutStream);
+
+ if (type.isArray()) {
+ if (!type.equals(byte[].class)) {
+ // Only byte arrays are supported currently.
+ throw new ViewMethodInvocationSerializationException(
+ "Unsupported array return type (" + type + ")");
+ }
+ byte[] byteArray = (byte[]) value;
+ dos.writeChar(SIG_ARRAY);
+ dos.writeChar(SIG_BYTE);
+ dos.writeInt(byteArray.length);
+ dos.write(byteArray);
+ } else if (boolean.class.equals(type)) {
+ dos.writeChar(SIG_BOOLEAN);
+ dos.write((boolean) value ? 1 : 0);
+ } else if (byte.class.equals(type)) {
+ dos.writeChar(SIG_BYTE);
+ dos.writeByte((byte) value);
+ } else if (char.class.equals(type)) {
+ dos.writeChar(SIG_CHAR);
+ dos.writeChar((char) value);
+ } else if (short.class.equals(type)) {
+ dos.writeChar(SIG_SHORT);
+ dos.writeShort((short) value);
+ } else if (int.class.equals(type)) {
+ dos.writeChar(SIG_INT);
+ dos.writeInt((int) value);
+ } else if (long.class.equals(type)) {
+ dos.writeChar(SIG_LONG);
+ dos.writeLong((long) value);
+ } else if (double.class.equals(type)) {
+ dos.writeChar(SIG_DOUBLE);
+ dos.writeDouble((double) value);
+ } else if (float.class.equals(type)) {
+ dos.writeChar(SIG_FLOAT);
+ dos.writeFloat((float) value);
+ } else if (String.class.equals(type)) {
+ dos.writeChar(SIG_STRING);
+ dos.writeUTF(value != null ? (String) value : "");
+ } else {
+ dos.writeChar(SIG_VOID);
+ }
+
+ return byteOutStream.toByteArray();
+ }
+
+ // Prefixes for simple primitives. These match the JNI definitions.
+ private static final char SIG_ARRAY = '[';
+ private static final char SIG_BOOLEAN = 'Z';
+ private static final char SIG_BYTE = 'B';
+ private static final char SIG_SHORT = 'S';
+ private static final char SIG_CHAR = 'C';
+ private static final char SIG_INT = 'I';
+ private static final char SIG_LONG = 'J';
+ private static final char SIG_FLOAT = 'F';
+ private static final char SIG_DOUBLE = 'D';
+ private static final char SIG_VOID = 'V';
+ // Prefixes for some commonly used objects
+ private static final char SIG_STRING = 'R';
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public static class ViewMethodInvocationSerializationException extends Exception {
+ ViewMethodInvocationSerializationException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/core/java/android/ddm/OWNERS b/core/java/android/ddm/OWNERS
new file mode 100644
index 0000000..369025b
--- /dev/null
+++ b/core/java/android/ddm/OWNERS
@@ -0,0 +1 @@
+per-file DdmHandleViewDebug.java = michschn@google.com
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 267b2ff..4d33bfd 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -91,6 +91,12 @@
public final int icon;
/**
+ * The maximum string length for any string contained in this condition.
+ * @hide
+ */
+ public static final int MAX_STRING_LENGTH = 1000;
+
+ /**
* An object representing the current state of a {@link android.app.AutomaticZenRule}.
* @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule
* @param summary a user visible description of the rule state.
@@ -104,16 +110,19 @@
if (id == null) throw new IllegalArgumentException("id is required");
if (summary == null) throw new IllegalArgumentException("summary is required");
if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state);
- this.id = id;
- this.summary = summary;
- this.line1 = line1;
- this.line2 = line2;
+ this.id = getTrimmedUri(id);
+ this.summary = getTrimmedString(summary);
+ this.line1 = getTrimmedString(line1);
+ this.line2 = getTrimmedString(line2);
this.icon = icon;
this.state = state;
this.flags = flags;
}
public Condition(Parcel source) {
+ // This constructor passes all fields directly into the constructor that takes all the
+ // fields as arguments; that constructor will trim each of the input strings to
+ // max length if necessary.
this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class),
source.readString(),
source.readString(),
@@ -240,4 +249,25 @@
return new Condition[size];
}
};
+
+ /**
+ * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH.
+ */
+ private static String getTrimmedString(String input) {
+ if (input != null && input.length() > MAX_STRING_LENGTH) {
+ return input.substring(0, MAX_STRING_LENGTH);
+ }
+ return input;
+ }
+
+ /**
+ * Returns a truncated copy of the Uri by trimming the string representation to the maximum
+ * string length.
+ */
+ private static Uri getTrimmedUri(Uri input) {
+ if (input != null && input.toString().length() > MAX_STRING_LENGTH) {
+ return Uri.parse(getTrimmedString(input.toString()));
+ }
+ return input;
+ }
}
diff --git a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java b/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
new file mode 100644
index 0000000..7248983
--- /dev/null
+++ b/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ddm;
+
+import static android.ddm.DdmHandleViewDebug.deserializeMethodParameters;
+import static android.ddm.DdmHandleViewDebug.serializeReturnValue;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.ddm.DdmHandleViewDebug.ViewMethodInvocationSerializationException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public final class DdmHandleViewDebugTest {
+ // true
+ private static final byte[] SERIALIZED_BOOLEAN_TRUE = {0x00, 0x5A, 1};
+
+ @Test
+ public void serializeReturnValue_booleanTrue() throws Exception {
+ assertArrayEquals(SERIALIZED_BOOLEAN_TRUE, serializeReturnValue(boolean.class, true));
+ }
+
+ @Test
+ public void deserializeMethodParameters_booleanTrue() throws Exception {
+ expectDeserializedArgument(boolean.class, true, SERIALIZED_BOOLEAN_TRUE);
+ }
+
+ // false
+ private static final byte[] SERIALIZED_BOOLEAN_FALSE = {0x00, 0x5A, 0};
+
+ @Test
+ public void serializeReturnValue_booleanFalse() throws Exception {
+ assertArrayEquals(SERIALIZED_BOOLEAN_FALSE, serializeReturnValue(boolean.class, false));
+ }
+
+ @Test
+ public void deserializeMethodParameters_booleanFalse() throws Exception {
+ expectDeserializedArgument(boolean.class, false, SERIALIZED_BOOLEAN_FALSE);
+ }
+
+ // (byte) 42
+ private static final byte[] SERIALIZED_BYTE = {0x00, 0x42, 42};
+
+ @Test
+ public void serializeReturnValue_byte() throws Exception {
+ assertArrayEquals(SERIALIZED_BYTE, serializeReturnValue(byte.class, (byte) 42));
+ }
+
+ @Test
+ public void deserializeMethodParameters_byte() throws Exception {
+ expectDeserializedArgument(byte.class, (byte) 42, SERIALIZED_BYTE);
+ }
+
+ // '\u1122'
+ private static final byte[] SERIALIZED_CHAR = {0x00, 0x43, 0x11, 0x22};
+
+ @Test
+ public void serializeReturnValue_char() throws Exception {
+ assertArrayEquals(SERIALIZED_CHAR, serializeReturnValue(char.class, '\u1122'));
+ }
+
+ @Test
+ public void deserializeMethodParameters_char() throws Exception {
+ expectDeserializedArgument(char.class, '\u1122', SERIALIZED_CHAR);
+ }
+
+ // (short) 0x1011
+ private static final byte[] SERIALIZED_SHORT = {0x00, 0x53, 0x10, 0x11};
+
+ @Test
+ public void serializeReturnValue_short() throws Exception {
+ assertArrayEquals(SERIALIZED_SHORT,
+ serializeReturnValue(short.class, (short) 0x1011));
+ }
+
+ @Test
+ public void deserializeMethodParameters_short() throws Exception {
+ expectDeserializedArgument(short.class, (short) 0x1011, SERIALIZED_SHORT);
+ }
+
+ // 0x11223344
+ private static final byte[] SERIALIZED_INT = {0x00, 0x49, 0x11, 0x22, 0x33, 0x44};
+
+ @Test
+ public void serializeReturnValue_int() throws Exception {
+ assertArrayEquals(SERIALIZED_INT,
+ serializeReturnValue(int.class, 0x11223344));
+ }
+
+ @Test
+ public void deserializeMethodParameters_int() throws Exception {
+ expectDeserializedArgument(int.class, 0x11223344, SERIALIZED_INT);
+ }
+
+ // 0x0011223344556677L
+ private static final byte[] SERIALIZED_LONG =
+ {0x00, 0x4a, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
+
+ @Test
+ public void serializeReturnValue_long() throws Exception {
+ assertArrayEquals(SERIALIZED_LONG,
+ serializeReturnValue(long.class, 0x0011223344556677L));
+ }
+
+ @Test
+ public void deserializeMethodParameters_long() throws Exception {
+ expectDeserializedArgument(long.class, 0x0011223344556677L, SERIALIZED_LONG);
+ }
+
+ // 3.141d
+ private static final byte[] SERIALIZED_DOUBLE =
+ {0x00, 0x44, (byte) 0x40, (byte) 0x09, (byte) 0x20, (byte) 0xc4, (byte) 0x9b,
+ (byte) 0xa5, (byte) 0xe3, (byte) 0x54};
+
+ @Test
+ public void serializeReturnValue_double() throws Exception {
+ assertArrayEquals(
+ SERIALIZED_DOUBLE,
+ serializeReturnValue(double.class, 3.141d));
+ }
+
+ @Test
+ public void deserializeMethodParameters_double() throws Exception {
+ expectDeserializedArgument(double.class, 3.141d, SERIALIZED_DOUBLE);
+ }
+
+ // 3.141f
+ private static final byte[] SERIALIZED_FLOAT =
+ {0x00, 0x46, (byte) 0x40, (byte) 0x49, (byte) 0x06, (byte) 0x25};
+
+ @Test
+ public void serializeReturnValue_float() throws Exception {
+ assertArrayEquals(SERIALIZED_FLOAT,
+ serializeReturnValue(float.class, 3.141f));
+ }
+
+ @Test
+ public void deserializeMethodParameters_float() throws Exception {
+ expectDeserializedArgument(float.class, 3.141f, SERIALIZED_FLOAT);
+ }
+
+ // "foo"
+ private static final byte[] SERIALIZED_ASCII_STRING = {0x00, 0x52, 0, 3, 0x66, 0x6f, 0x6f};
+
+ @Test
+ public void serializeReturnValue_asciiString() throws Exception {
+ assertArrayEquals(SERIALIZED_ASCII_STRING,
+ serializeReturnValue(String.class, "foo"));
+ }
+
+ @Test
+ public void deserializeMethodParameters_asciiString() throws Exception {
+ expectDeserializedArgument(String.class, "foo", SERIALIZED_ASCII_STRING);
+ }
+
+ // "\u1122"
+ private static final byte[] SERIALIZED_NON_ASCII_STRING =
+ {0x00, 0x52, 0, 3, (byte) 0xe1, (byte) 0x84, (byte) 0xa2};
+
+ @Test
+ public void serializeReturnValue_nonAsciiString_encodesAsUtf8() throws Exception {
+ assertArrayEquals(SERIALIZED_NON_ASCII_STRING,
+ serializeReturnValue(String.class, "\u1122"));
+ }
+
+ @Test
+ public void deserializeMethodParameters_decodesFromUtf8() throws Exception {
+ expectDeserializedArgument(String.class, "\u1122", SERIALIZED_NON_ASCII_STRING);
+ }
+
+ // ""
+ private static final byte[] SERIALIZED_EMPTY_STRING = {0x00, 0x52, 0, 0};
+
+ @Test
+ public void serializeReturnValue_emptyString() throws Exception {
+ assertArrayEquals(SERIALIZED_EMPTY_STRING, serializeReturnValue(String.class, ""));
+ }
+
+ @Test
+ public void deserializeMethodParameters_emptyString() throws Exception {
+ expectDeserializedArgument(String.class, "", SERIALIZED_EMPTY_STRING);
+ }
+
+ @Test
+ public void serializeReturnValue_nullString_encodesAsEmptyString() throws Exception {
+ assertArrayEquals(new byte[]{0x00, 0x52, 0, 0}, serializeReturnValue(String.class, null));
+ }
+
+ // Illegal - string length exceeding actual bytes
+ private static final byte[] SERIALIZED_INVALID_STRING =
+ {0x00, 0x52, 0, 3, 0x66};
+
+ @Test
+ public void deserializeMethodParameters_stringPayloadMissing_throws() throws Exception {
+ Object[] args = new Object[1];
+ Class<?>[] argTypes = new Class<?>[1];
+ assertThrows(BufferUnderflowException.class,
+ () -> deserializeMethodParameters(args, argTypes,
+ ByteBuffer.wrap(SERIALIZED_INVALID_STRING)));
+ }
+
+ @Test
+ public void serializeAndDeserialize_handlesStringsUpTo64k() throws Exception {
+ char[] chars = new char[65535];
+ Arrays.fill(chars, 'a');
+ String original = new String(chars);
+ byte[] serialized = serializeReturnValue(String.class, original);
+
+ // 2 bytes for the R signature char, 2 bytes char string byte count, 2^16-1 bytes ASCII
+ // payload
+ assertEquals(2 + 2 + 65535, serialized.length);
+
+ // length is unsigned short
+ assertArrayEquals(new byte[]{0x00, 0x52, (byte) 0xff, (byte) 0xff},
+ Arrays.copyOfRange(serialized, 0, 4));
+
+ // length of string must be interpreted as unsigned short, returning original content
+ expectDeserializedArgument(String.class, original, serialized);
+ }
+
+ private static final byte[] SERIALIZED_VOID = {0x00, 0x56};
+
+ @Test
+ public void serializeReturnValue_void() throws Exception {
+ assertArrayEquals(SERIALIZED_VOID, serializeReturnValue(void.class, null));
+ }
+
+ @Test
+ public void deserializeMethodParameters_void_throws() throws Exception {
+ Object[] args = new Object[1];
+ Class<?>[] argTypes = new Class<?>[1];
+ assertThrows(ViewMethodInvocationSerializationException.class,
+ () -> deserializeMethodParameters(args, argTypes,
+ ByteBuffer.wrap(SERIALIZED_VOID)));
+ }
+
+ // new byte[]{}
+ private static final byte[] SERIALIZED_EMPTY_BYTE_ARRAY = {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 0};
+
+ @Test
+ public void serializeReturnValue_emptyByteArray() throws Exception {
+ assertArrayEquals(SERIALIZED_EMPTY_BYTE_ARRAY,
+ serializeReturnValue(byte[].class, new byte[]{}));
+ }
+
+ @Test
+ public void deserializeMethodParameters_emptyByteArray() throws Exception {
+ expectDeserializedArgument(byte[].class, new byte[]{}, SERIALIZED_EMPTY_BYTE_ARRAY);
+ }
+
+ // new byte[]{0, 42}
+ private static final byte[] SERIALIZED_SIMPLE_BYTE_ARRAY =
+ {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 2, 0, 42};
+
+ @Test
+ public void serializeReturnValue_byteArray() throws Exception {
+ assertArrayEquals(SERIALIZED_SIMPLE_BYTE_ARRAY,
+ serializeReturnValue(byte[].class, new byte[]{0, 42}));
+ }
+
+ @Test
+ public void deserializeMethodParameters_byteArray() throws Exception {
+ expectDeserializedArgument(byte[].class, new byte[]{0, 42}, SERIALIZED_SIMPLE_BYTE_ARRAY);
+ }
+
+ @Test
+ public void serializeReturnValue_largeByteArray_encodesSizeCorrectly() throws Exception {
+ byte[] result = serializeReturnValue(byte[].class, new byte[0x012233]);
+ // 2 bytes for the each [Z signature char, 4 bytes int array length, 0x012233 bytes payload
+ assertEquals(2 + 2 + 4 + 74291, result.length);
+
+ assertArrayEquals(new byte[]{0x00, 0x5b, 0x00, 0x42, 0x00, 0x01, 0x22, 0x33},
+ Arrays.copyOfRange(result, 0, 8));
+ }
+
+ // Illegal - declared size exceeds remaining buffer length
+ private static final byte[] SERIALIZED_INVALID_BYTE_ARRAY =
+ {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 3, 0, 42};
+
+ @Test
+ public void deserializeMethodParameters_sizeExceedsBuffer_throws() throws Exception {
+ Object[] args = new Object[1];
+ Class<?>[] argTypes = new Class<?>[1];
+ assertThrows(BufferUnderflowException.class,
+ () -> deserializeMethodParameters(args, argTypes,
+ ByteBuffer.wrap(SERIALIZED_INVALID_BYTE_ARRAY)));
+ }
+
+ // new int[]{}
+ private static final byte[] SERIALIZED_EMPTY_INT_ARRAY = {0x00, 0x5b, 0x00, 0x49, 0, 0, 0, 0};
+
+ @Test
+ public void serializeReturnValue_nonByteArrayType_throws() throws Exception {
+ assertThrows(ViewMethodInvocationSerializationException.class,
+ () -> serializeReturnValue(int[].class, 42));
+ }
+
+ @Test
+ public void deserializeMethodParameters_nonByteArrayType_throws() throws Exception {
+ Object[] args = new Object[1];
+ Class<?>[] argTypes = new Class<?>[1];
+ assertThrows(ViewMethodInvocationSerializationException.class,
+ () -> deserializeMethodParameters(args, argTypes,
+ ByteBuffer.wrap(SERIALIZED_EMPTY_INT_ARRAY)));
+ }
+
+ // new byte[]{0, 42}
+ private static final byte[] SERIALIZED_MULTIPLE_PARAMETERS =
+ {0x00, 0x42, 42, 0x00, 0x5A, 1};
+
+ @Test
+ public void deserializeMethodParameters_multipleParameters() throws Exception {
+ expectDeserializedArguments(new Class[]{byte.class, boolean.class},
+ new Object[]{(byte) 42, true}, SERIALIZED_MULTIPLE_PARAMETERS);
+ }
+
+ // Illegal - type 'X'
+ private static final byte[] SERIALIZED_INVALID_UNKNOWN_TYPE = {0x00, 0x58};
+
+ @Test
+ public void deserializeMethodParameters_unknownType_throws() throws Exception {
+ Object[] args = new Object[1];
+ Class<?>[] argTypes = new Class<?>[1];
+ assertThrows(ViewMethodInvocationSerializationException.class,
+ () -> deserializeMethodParameters(args, argTypes,
+ ByteBuffer.wrap(SERIALIZED_INVALID_UNKNOWN_TYPE)));
+ }
+
+ @Test
+ public void deserializeMethodParameters_noArgumentsEmptyPacket_isNoop() throws Exception {
+ Object[] args = new Object[0];
+ Class<?>[] argTypes = new Class<?>[0];
+ deserializeMethodParameters(args, argTypes, ByteBuffer.wrap(new byte[0]));
+ }
+
+ @Test
+ public void deserializeMethodParameters_withArgumentsEmptyPacket_throws() throws Exception {
+ Object[] args = new Object[1];
+ Class<?>[] argTypes = new Class<?>[1];
+ assertThrows(BufferUnderflowException.class,
+ () -> deserializeMethodParameters(args, argTypes, ByteBuffer.wrap(new byte[0])));
+ }
+
+ private static void expectDeserializedArgument(Class<?> expectedType, Object expectedValue,
+ byte[] argumentBuffer) throws Exception {
+ expectDeserializedArguments(new Class[]{expectedType}, new Object[]{expectedValue},
+ argumentBuffer);
+ }
+
+ private static void expectDeserializedArguments(Class<?>[] expectedTypes,
+ Object[] expectedValues, byte[] argumentBuffer) throws Exception {
+ final int argCount = expectedTypes.length;
+ assertEquals("test helper not used correctly", argCount, expectedValues.length);
+ Object[] actualArgs = new Object[argCount];
+ Class<?>[] actualArgTypes = new Class<?>[argCount];
+
+ ByteBuffer buffer = ByteBuffer.wrap(argumentBuffer);
+ deserializeMethodParameters(actualArgs, actualArgTypes, buffer);
+
+ for (int i = 0; i < argCount; i++) {
+ String context = "argument " + i;
+ assertEquals(context, expectedTypes[i], actualArgTypes[i]);
+ if (byte[].class.equals(expectedTypes[i])) {
+ assertArrayEquals((byte[]) expectedValues[i], (byte[]) actualArgs[i]);
+ } else {
+ assertEquals(expectedValues[i], actualArgs[i]);
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/ddm/OWNERS b/core/tests/coretests/src/android/ddm/OWNERS
new file mode 100644
index 0000000..c8be191
--- /dev/null
+++ b/core/tests/coretests/src/android/ddm/OWNERS
@@ -0,0 +1 @@
+michschn@google.com
diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java
new file mode 100644
index 0000000..42629ba
--- /dev/null
+++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
+import android.net.Uri;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConditionTest {
+ private static final String CLASS = "android.service.notification.Condition";
+
+ @Test
+ public void testLongFields_inConstructors() {
+ String longString = Strings.repeat("A", 65536);
+ Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
+
+ // Confirm strings are truncated via short constructor
+ Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE);
+
+ assertEquals(Condition.MAX_STRING_LENGTH, cond1.id.toString().length());
+ assertEquals(Condition.MAX_STRING_LENGTH, cond1.summary.length());
+
+ // Confirm strings are truncated via long constructor
+ Condition cond2 = new Condition(longUri, longString, longString, longString,
+ -1, Condition.STATE_TRUE, Condition.FLAG_RELEVANT_ALWAYS);
+
+ assertEquals(Condition.MAX_STRING_LENGTH, cond2.id.toString().length());
+ assertEquals(Condition.MAX_STRING_LENGTH, cond2.summary.length());
+ assertEquals(Condition.MAX_STRING_LENGTH, cond2.line1.length());
+ assertEquals(Condition.MAX_STRING_LENGTH, cond2.line2.length());
+ }
+
+ @Test
+ public void testLongFields_viaParcel() {
+ // Set fields via reflection to force them to be long, then parcel and unparcel to make sure
+ // it gets truncated upon unparcelling.
+ Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+ Condition.STATE_TRUE);
+
+ try {
+ String longString = Strings.repeat("A", 65536);
+ Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
+ Field id = Class.forName(CLASS).getDeclaredField("id");
+ id.setAccessible(true);
+ id.set(cond, longUri);
+ Field summary = Class.forName(CLASS).getDeclaredField("summary");
+ summary.setAccessible(true);
+ summary.set(cond, longString);
+ Field line1 = Class.forName(CLASS).getDeclaredField("line1");
+ line1.setAccessible(true);
+ line1.set(cond, longString);
+ Field line2 = Class.forName(CLASS).getDeclaredField("line2");
+ line2.setAccessible(true);
+ line2.set(cond, longString);
+ } catch (NoSuchFieldException e) {
+ fail(e.toString());
+ } catch (ClassNotFoundException e) {
+ fail(e.toString());
+ } catch (IllegalAccessException e) {
+ fail(e.toString());
+ }
+
+ Parcel parcel = Parcel.obtain();
+ cond.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ Condition fromParcel = new Condition(parcel);
+ assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.id.toString().length());
+ assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.summary.length());
+ assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length());
+ assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length());
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 6af3d2b..126f835 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -139,6 +139,16 @@
}
}
+ @Override
+ public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) {
+ // TODO: Implement this method
+ }
+
+ @Override
+ public void clearSplitAttributesCalculator() {
+ // TODO: Implement this method
+ }
+
@NonNull
List<EmbeddingRule> getSplitRules() {
return mSplitRules;
@@ -1516,13 +1526,20 @@
.toActivityStack();
final ActivityStack secondaryContainer = container.getSecondaryContainer()
.toActivityStack();
+ final SplitAttributes.SplitType splitType = shouldShowSideBySide(container)
+ ? new SplitAttributes.SplitType.RatioSplitType(
+ container.getSplitRule().getSplitRatio())
+ : new SplitAttributes.SplitType.ExpandContainersSplitType();
final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
// Splits that are not showing side-by-side are reported as having 0 split
// ratio, since by definition in the API the primary container occupies no
// width of the split when covered by the secondary.
- shouldShowSideBySide(container)
- ? container.getSplitRule().getSplitRatio()
- : 0.0f);
+ // TODO(b/241042437): use v2 APIs for splitAttributes
+ new SplitAttributes.Builder()
+ .setSplitType(splitType)
+ .setLayoutDirection(container.getSplitRule().getLayoutDirection())
+ .build()
+ );
splitStates.add(splitState);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
index cdee9e3..af5d8c5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -16,7 +16,6 @@
package androidx.window.extensions.embedding;
-import static android.graphics.Matrix.MSCALE_X;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
@@ -41,30 +40,44 @@
*/
private static final int LAYER_NO_OVERRIDE = -1;
+ @NonNull
final Animation mAnimation;
+ @NonNull
final RemoteAnimationTarget mTarget;
+ @NonNull
final SurfaceControl mLeash;
+ /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
+ @NonNull
+ private final Rect mWholeAnimationBounds = new Rect();
+ @NonNull
final Transformation mTransformation = new Transformation();
+ @NonNull
final float[] mMatrix = new float[9];
+ @NonNull
final float[] mVecs = new float[4];
+ @NonNull
final Rect mRect = new Rect();
private boolean mIsFirstFrame = true;
private int mOverrideLayer = LAYER_NO_OVERRIDE;
TaskFragmentAnimationAdapter(@NonNull Animation animation,
@NonNull RemoteAnimationTarget target) {
- this(animation, target, target.leash);
+ this(animation, target, target.leash, target.screenSpaceBounds);
}
/**
* @param leash the surface to animate.
+ * @param wholeAnimationBounds area in absolute coordinate that the animation surface shouldn't
+ * go beyond.
*/
TaskFragmentAnimationAdapter(@NonNull Animation animation,
- @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash) {
+ @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
+ @NonNull Rect wholeAnimationBounds) {
mAnimation = animation;
mTarget = target;
mLeash = leash;
+ mWholeAnimationBounds.set(wholeAnimationBounds);
}
/**
@@ -94,23 +107,32 @@
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ // Update the surface position and alpha.
mTransformation.getMatrix().postTranslate(
mTarget.localBounds.left, mTarget.localBounds.top);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
- // Get current animation position.
+
+ // Get current surface bounds in absolute coordinate.
+ // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
final int positionX = Math.round(mMatrix[MTRANS_X]);
final int positionY = Math.round(mMatrix[MTRANS_Y]);
- // The exiting surface starts at position: mTarget.localBounds and moves with
- // positionX varying. Offset our crop region by the amount we have slided so crop
- // regions stays exactly on the original container in split.
- final int cropOffsetX = mTarget.localBounds.left - positionX;
- final int cropOffsetY = mTarget.localBounds.top - positionY;
- final Rect cropRect = new Rect();
- cropRect.set(mTarget.localBounds);
- // Because window crop uses absolute position.
- cropRect.offsetTo(0, 0);
- cropRect.offset(cropOffsetX, cropOffsetY);
+ final Rect cropRect = new Rect(mTarget.screenSpaceBounds);
+ final Rect localBounds = mTarget.localBounds;
+ cropRect.offset(positionX - localBounds.left, positionY - localBounds.top);
+
+ // Store the current offset of the surface top left from (0,0) in absolute coordinate.
+ final int offsetX = cropRect.left;
+ final int offsetY = cropRect.top;
+
+ // Intersect to make sure the animation happens within the whole animation bounds.
+ if (!cropRect.intersect(mWholeAnimationBounds)) {
+ // Hide the surface when it is outside of the animation area.
+ t.setAlpha(mLeash, 0);
+ }
+
+ // cropRect is in absolute coordinate, so we need to translate it to surface top left.
+ cropRect.offset(-offsetX, -offsetY);
t.setCrop(mLeash, cropRect);
}
@@ -124,52 +146,6 @@
}
/**
- * Should be used when the {@link RemoteAnimationTarget} is in split with others, and want to
- * animate together as one. This adapter will offset the animation leash to make the animate of
- * two windows look like a single window.
- */
- static class SplitAdapter extends TaskFragmentAnimationAdapter {
- private final boolean mIsLeftHalf;
- private final int mWholeAnimationWidth;
-
- /**
- * @param isLeftHalf whether this is the left half of the animation.
- * @param wholeAnimationWidth the whole animation windows width.
- */
- SplitAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target,
- boolean isLeftHalf, int wholeAnimationWidth) {
- super(animation, target);
- mIsLeftHalf = isLeftHalf;
- mWholeAnimationWidth = wholeAnimationWidth;
- if (wholeAnimationWidth == 0) {
- throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth");
- }
- }
-
- @Override
- void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
- float posX = mTarget.localBounds.left;
- final float posY = mTarget.localBounds.top;
- // This window is half of the whole animation window. Offset left/right to make it
- // look as one with the other half.
- mTransformation.getMatrix().getValues(mMatrix);
- final int targetWidth = mTarget.localBounds.width();
- final float scaleX = mMatrix[MSCALE_X];
- final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2;
- final float curOffset = targetWidth * (1 - scaleX) / 2;
- final float offsetDiff = totalOffset - curOffset;
- if (mIsLeftHalf) {
- posX += offsetDiff;
- } else {
- posX -= offsetDiff;
- }
- mTransformation.getMatrix().postTranslate(posX, posY);
- t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
- t.setAlpha(mLeash, mTransformation.getAlpha());
- }
- }
-
- /**
* Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
* size change.
*/
@@ -177,7 +153,7 @@
SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
// Start leash is the snapshot of the starting surface.
- super(animation, target, target.startLeash);
+ super(animation, target, target.startLeash, target.screenSpaceBounds);
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 8af2d9c..8c416e8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -213,10 +213,10 @@
for (RemoteAnimationTarget target : targets) {
if (target.mode != MODE_CLOSING) {
openingTargets.add(target);
- openingWholeScreenBounds.union(target.localBounds);
+ openingWholeScreenBounds.union(target.screenSpaceBounds);
} else {
closingTargets.add(target);
- closingWholeScreenBounds.union(target.localBounds);
+ closingWholeScreenBounds.union(target.screenSpaceBounds);
}
}
@@ -249,20 +249,8 @@
@NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
@NonNull Rect wholeAnimationBounds) {
final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
- final Rect targetBounds = target.localBounds;
- if (targetBounds.left == wholeAnimationBounds.left
- && targetBounds.right != wholeAnimationBounds.right) {
- // This is the left split of the whole animation window.
- return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target,
- true /* isLeftHalf */, wholeAnimationBounds.width());
- } else if (targetBounds.left != wholeAnimationBounds.left
- && targetBounds.right == wholeAnimationBounds.right) {
- // This is the right split of the whole animation window.
- return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target,
- false /* isLeftHalf */, wholeAnimationBounds.width());
- }
- // Open/close window that fills the whole animation.
- return new TaskFragmentAnimationAdapter(animation, target);
+ return new TaskFragmentAnimationAdapter(animation, target, target.leash,
+ wholeAnimationBounds);
}
@NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index 97d42391b..ef5ea56 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -195,7 +195,10 @@
? com.android.internal.R.anim.task_fragment_open_enter
: com.android.internal.R.anim.task_fragment_open_exit);
}
- animation.initialize(target.localBounds.width(), target.localBounds.height(),
+ // Use the whole animation bounds instead of the change bounds, so that when multiple change
+ // targets are opening at the same time, the animation applied to each will be the same.
+ // Otherwise, we may see gap between the activities that are launching together.
+ animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
wholeAnimationBounds.width(), wholeAnimationBounds.height());
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
return animation;
@@ -215,7 +218,10 @@
? com.android.internal.R.anim.task_fragment_close_enter
: com.android.internal.R.anim.task_fragment_close_exit);
}
- animation.initialize(target.localBounds.width(), target.localBounds.height(),
+ // Use the whole animation bounds instead of the change bounds, so that when multiple change
+ // targets are closing at the same time, the animation applied to each will be the same.
+ // Otherwise, we may see gap between the activities that are finishing together.
+ animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
wholeAnimationBounds.width(), wholeAnimationBounds.height());
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
return animation;
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index e9a1721..2c766d8 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index cc4db93..591e347 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.activityembedding;
-import static android.graphics.Matrix.MSCALE_X;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
@@ -42,31 +41,45 @@
*/
private static final int LAYER_NO_OVERRIDE = -1;
+ @NonNull
final Animation mAnimation;
+ @NonNull
final TransitionInfo.Change mChange;
+ @NonNull
final SurfaceControl mLeash;
+ /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
+ @NonNull
+ private final Rect mWholeAnimationBounds = new Rect();
+ @NonNull
final Transformation mTransformation = new Transformation();
+ @NonNull
final float[] mMatrix = new float[9];
+ @NonNull
final float[] mVecs = new float[4];
+ @NonNull
final Rect mRect = new Rect();
private boolean mIsFirstFrame = true;
private int mOverrideLayer = LAYER_NO_OVERRIDE;
ActivityEmbeddingAnimationAdapter(@NonNull Animation animation,
@NonNull TransitionInfo.Change change) {
- this(animation, change, change.getLeash());
+ this(animation, change, change.getLeash(), change.getEndAbsBounds());
}
/**
* @param leash the surface to animate, which is not necessary the same as
- * {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example.
+ * {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example.
+ * @param wholeAnimationBounds area in absolute coordinate that the animation surface shouldn't
+ * go beyond.
*/
ActivityEmbeddingAnimationAdapter(@NonNull Animation animation,
- @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash) {
+ @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash,
+ @NonNull Rect wholeAnimationBounds) {
mAnimation = animation;
mChange = change;
mLeash = leash;
+ mWholeAnimationBounds.set(wholeAnimationBounds);
}
/**
@@ -96,23 +109,31 @@
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ // Update the surface position and alpha.
final Point offset = mChange.getEndRelOffset();
mTransformation.getMatrix().postTranslate(offset.x, offset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
- // Get current animation position.
+
+ // Get current surface bounds in absolute coordinate.
+ // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
final int positionX = Math.round(mMatrix[MTRANS_X]);
final int positionY = Math.round(mMatrix[MTRANS_Y]);
- // The exiting surface starts at position: Change#getEndRelOffset() and moves with
- // positionX varying. Offset our crop region by the amount we have slided so crop
- // regions stays exactly on the original container in split.
- final int cropOffsetX = offset.x - positionX;
- final int cropOffsetY = offset.y - positionY;
- final Rect cropRect = new Rect();
- cropRect.set(mChange.getEndAbsBounds());
- // Because window crop uses absolute position.
- cropRect.offsetTo(0, 0);
- cropRect.offset(cropOffsetX, cropOffsetY);
+ final Rect cropRect = new Rect(mChange.getEndAbsBounds());
+ cropRect.offset(positionX - offset.x, positionY - offset.y);
+
+ // Store the current offset of the surface top left from (0,0) in absolute coordinate.
+ final int offsetX = cropRect.left;
+ final int offsetY = cropRect.top;
+
+ // Intersect to make sure the animation happens within the whole animation bounds.
+ if (!cropRect.intersect(mWholeAnimationBounds)) {
+ // Hide the surface when it is outside of the animation area.
+ t.setAlpha(mLeash, 0);
+ }
+
+ // cropRect is in absolute coordinate, so we need to translate it to surface top left.
+ cropRect.offset(-offsetX, -offsetY);
t.setCrop(mLeash, cropRect);
}
@@ -127,53 +148,6 @@
}
/**
- * Should be used when the {@link TransitionInfo.Change} is in split with others, and wants to
- * animate together as one. This adapter will offset the animation leash to make the animate of
- * two windows look like a single window.
- */
- static class SplitAdapter extends ActivityEmbeddingAnimationAdapter {
- private final boolean mIsLeftHalf;
- private final int mWholeAnimationWidth;
-
- /**
- * @param isLeftHalf whether this is the left half of the animation.
- * @param wholeAnimationWidth the whole animation windows width.
- */
- SplitAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change,
- boolean isLeftHalf, int wholeAnimationWidth) {
- super(animation, change);
- mIsLeftHalf = isLeftHalf;
- mWholeAnimationWidth = wholeAnimationWidth;
- if (wholeAnimationWidth == 0) {
- throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth");
- }
- }
-
- @Override
- void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
- final Point offset = mChange.getEndRelOffset();
- float posX = offset.x;
- final float posY = offset.y;
- // This window is half of the whole animation window. Offset left/right to make it
- // look as one with the other half.
- mTransformation.getMatrix().getValues(mMatrix);
- final int changeWidth = mChange.getEndAbsBounds().width();
- final float scaleX = mMatrix[MSCALE_X];
- final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2;
- final float curOffset = changeWidth * (1 - scaleX) / 2;
- final float offsetDiff = totalOffset - curOffset;
- if (mIsLeftHalf) {
- posX += offsetDiff;
- } else {
- posX -= offsetDiff;
- }
- mTransformation.getMatrix().postTranslate(posX, posY);
- t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
- t.setAlpha(mLeash, mTransformation.getAlpha());
- }
- }
-
- /**
* Should be used for the animation of the snapshot of a {@link TransitionInfo.Change} that has
* size change.
*/
@@ -181,7 +155,7 @@
SnapshotAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change,
@NonNull SurfaceControl snapshotLeash) {
- super(animation, change, snapshotLeash);
+ super(animation, change, snapshotLeash, change.getEndAbsBounds());
}
@Override
@@ -196,7 +170,9 @@
void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
super.onAnimationEnd(t);
// Remove the screenshot leash after animation is finished.
- t.remove(mLeash);
+ if (mLeash.isValid()) {
+ t.remove(mLeash);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 7e0795d..d88cc00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -22,9 +22,9 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
+import android.util.ArraySet;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.animation.Animation;
@@ -40,6 +40,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import java.util.function.BiFunction;
/** To run the ActivityEmbedding animations. */
@@ -169,15 +170,12 @@
final Rect openingWholeScreenBounds = new Rect();
final Rect closingWholeScreenBounds = new Rect();
for (TransitionInfo.Change change : info.getChanges()) {
- final Rect bounds = new Rect(change.getEndAbsBounds());
- final Point offset = change.getEndRelOffset();
- bounds.offsetTo(offset.x, offset.y);
if (Transitions.isOpeningType(change.getMode())) {
openingChanges.add(change);
- openingWholeScreenBounds.union(bounds);
+ openingWholeScreenBounds.union(change.getEndAbsBounds());
} else {
closingChanges.add(change);
- closingWholeScreenBounds.union(bounds);
+ closingWholeScreenBounds.union(change.getEndAbsBounds());
}
}
@@ -210,60 +208,73 @@
@NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider,
@NonNull Rect wholeAnimationBounds) {
final Animation animation = animationProvider.apply(change, wholeAnimationBounds);
- final Rect bounds = new Rect(change.getEndAbsBounds());
- final Point offset = change.getEndRelOffset();
- bounds.offsetTo(offset.x, offset.y);
- if (bounds.left == wholeAnimationBounds.left
- && bounds.right != wholeAnimationBounds.right) {
- // This is the left split of the whole animation window.
- return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change,
- true /* isLeftHalf */, wholeAnimationBounds.width());
- } else if (bounds.left != wholeAnimationBounds.left
- && bounds.right == wholeAnimationBounds.right) {
- // This is the right split of the whole animation window.
- return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change,
- false /* isLeftHalf */, wholeAnimationBounds.width());
- }
- // Open/close window that fills the whole animation.
- return new ActivityEmbeddingAnimationAdapter(animation, change);
+ return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
+ wholeAnimationBounds);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
+ final Set<TransitionInfo.Change> handledChanges = new ArraySet<>();
+
+ // For the first iteration, we prepare the animation for the change type windows. This is
+ // needed because there may be window that is reparented while resizing. In such case, we
+ // will do the following:
+ // 1. Capture a screenshot from the Activity surface.
+ // 2. Attach the screenshot surface to the top of TaskFragment (Activity's parent) surface.
+ // 3. Animate the TaskFragment using Activity Change info (start/end bounds).
+ // This is because the TaskFragment surface/change won't contain the Activity's before its
+ // reparent.
for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getMode() == TRANSIT_CHANGE
- && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
- // This is the window with bounds change.
- final WindowContainerToken parentToken = change.getParent();
- final Rect parentBounds;
- if (parentToken != null) {
- TransitionInfo.Change parentChange = info.getChange(parentToken);
- parentBounds = parentChange != null
- ? parentChange.getEndAbsBounds()
- : change.getEndAbsBounds();
- } else {
- parentBounds = change.getEndAbsBounds();
- }
- final Animation[] animations =
- mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds);
- // Adapter for the starting screenshot leash.
- final SurfaceControl screenshotLeash = createScreenshot(change, startTransaction);
- if (screenshotLeash != null) {
- // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd
- adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter(
- animations[0], change, screenshotLeash));
- } else {
- Log.e(TAG, "Failed to take screenshot for change=" + change);
- }
- // Adapter for the ending bounds changed leash.
- adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter(
- animations[1], change));
+ if (change.getMode() != TRANSIT_CHANGE
+ || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
continue;
}
- // These are the other windows that don't have bounds change in the same transition.
+ // This is the window with bounds change.
+ handledChanges.add(change);
+ final WindowContainerToken parentToken = change.getParent();
+ TransitionInfo.Change boundsAnimationChange = change;
+ if (parentToken != null) {
+ // When the parent window is also included in the transition as an opening window,
+ // we would like to animate the parent window instead.
+ final TransitionInfo.Change parentChange = info.getChange(parentToken);
+ if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) {
+ // We won't create a separate animation for the parent, but to animate the
+ // parent for the child resizing.
+ handledChanges.add(parentChange);
+ boundsAnimationChange = parentChange;
+ }
+ }
+
+ final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
+ boundsAnimationChange.getEndAbsBounds());
+
+ // Create a screenshot based on change, but attach it to the top of the
+ // boundsAnimationChange.
+ final SurfaceControl screenshotLeash = getOrCreateScreenshot(change,
+ boundsAnimationChange, startTransaction);
+ if (screenshotLeash != null) {
+ // Adapter for the starting screenshot leash.
+ // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd
+ adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter(
+ animations[0], change, screenshotLeash));
+ } else {
+ Log.e(TAG, "Failed to take screenshot for change=" + change);
+ }
+ // Adapter for the ending bounds changed leash.
+ adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter(
+ animations[1], boundsAnimationChange));
+ }
+
+ // Handle the other windows that don't have bounds change in the same transition.
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (handledChanges.contains(change)) {
+ // Skip windows that we have already handled in the previous iteration.
+ continue;
+ }
+
final Animation animation;
if (!TransitionInfo.isIndependent(change, info)) {
// No-op if it will be covered by the changing parent window.
@@ -278,13 +289,27 @@
return adapters;
}
- /** Takes a screenshot of the given {@link TransitionInfo.Change} surface. */
+ /**
+ * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one.
+ * The screenshot leash should be attached to the {@code animationChange} surface which we will
+ * animate later.
+ */
@Nullable
- private SurfaceControl createScreenshot(@NonNull TransitionInfo.Change change,
- @NonNull SurfaceControl.Transaction startTransaction) {
- final Rect cropBounds = new Rect(change.getStartAbsBounds());
+ private SurfaceControl getOrCreateScreenshot(@NonNull TransitionInfo.Change screenshotChange,
+ @NonNull TransitionInfo.Change animationChange,
+ @NonNull SurfaceControl.Transaction t) {
+ final SurfaceControl screenshotLeash = screenshotChange.getSnapshot();
+ if (screenshotLeash != null) {
+ // If WM Core has already taken a screenshot, make sure it is reparented to the
+ // animation leash.
+ t.reparent(screenshotLeash, animationChange.getLeash());
+ return screenshotLeash;
+ }
+
+ // If WM Core hasn't taken a screenshot, take a screenshot now.
+ final Rect cropBounds = new Rect(screenshotChange.getStartAbsBounds());
cropBounds.offsetTo(0, 0);
- return ScreenshotUtils.takeScreenshot(startTransaction, change.getLeash(), cropBounds,
- Integer.MAX_VALUE);
+ return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(),
+ animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 6f06f28..ad0dddf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -185,8 +185,10 @@
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? R.anim.task_fragment_open_enter
: R.anim.task_fragment_open_exit);
- final Rect bounds = change.getEndAbsBounds();
- animation.initialize(bounds.width(), bounds.height(),
+ // Use the whole animation bounds instead of the change bounds, so that when multiple change
+ // targets are opening at the same time, the animation applied to each will be the same.
+ // Otherwise, we may see gap between the activities that are launching together.
+ animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
wholeAnimationBounds.width(), wholeAnimationBounds.height());
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
return animation;
@@ -203,8 +205,10 @@
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? R.anim.task_fragment_close_enter
: R.anim.task_fragment_close_exit);
- final Rect bounds = change.getEndAbsBounds();
- animation.initialize(bounds.width(), bounds.height(),
+ // Use the whole animation bounds instead of the change bounds, so that when multiple change
+ // targets are closing at the same time, the animation applied to each will be the same.
+ // Otherwise, we may see gap between the activities that are finishing together.
+ animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
wholeAnimationBounds.width(), wholeAnimationBounds.height());
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
return animation;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
index c4bd73b..2a1bf0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
@@ -28,7 +28,7 @@
public class ScreenshotUtils {
/**
- * Take a screenshot of the specified SurfaceControl.
+ * Takes a screenshot of the specified SurfaceControl.
*
* @param sc the SurfaceControl to take a screenshot of
* @param crop the crop to use when capturing the screenshot
@@ -49,11 +49,14 @@
SurfaceControl mScreenshot = null;
SurfaceControl.Transaction mTransaction;
SurfaceControl mSurfaceControl;
+ SurfaceControl mParentSurfaceControl;
int mLayer;
- BufferConsumer(SurfaceControl.Transaction t, SurfaceControl sc, int layer) {
+ BufferConsumer(SurfaceControl.Transaction t, SurfaceControl sc, SurfaceControl parentSc,
+ int layer) {
mTransaction = t;
mSurfaceControl = sc;
+ mParentSurfaceControl = parentSc;
mLayer = layer;
}
@@ -72,7 +75,7 @@
mTransaction.setBuffer(mScreenshot, buffer.getHardwareBuffer());
mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace());
- mTransaction.reparent(mScreenshot, mSurfaceControl);
+ mTransaction.reparent(mScreenshot, mParentSurfaceControl);
mTransaction.setLayer(mScreenshot, mLayer);
mTransaction.show(mScreenshot);
mTransaction.apply();
@@ -80,7 +83,7 @@
}
/**
- * Take a screenshot of the specified SurfaceControl.
+ * Takes a screenshot of the specified SurfaceControl.
*
* @param t the transaction used to set changes on the resulting screenshot.
* @param sc the SurfaceControl to take a screenshot of
@@ -91,7 +94,23 @@
*/
public static SurfaceControl takeScreenshot(SurfaceControl.Transaction t, SurfaceControl sc,
Rect crop, int layer) {
- BufferConsumer consumer = new BufferConsumer(t, sc, layer);
+ return takeScreenshot(t, sc, sc /* parentSc */, crop, layer);
+ }
+
+ /**
+ * Takes a screenshot of the specified SurfaceControl.
+ *
+ * @param t the transaction used to set changes on the resulting screenshot.
+ * @param sc the SurfaceControl to take a screenshot of
+ * @param parentSc the SurfaceControl to attach the screenshot to.
+ * @param crop the crop to use when capturing the screenshot
+ * @param layer the layer to place the screenshot
+ *
+ * @return A SurfaceControl where the screenshot will be attached, or null if failed.
+ */
+ public static SurfaceControl takeScreenshot(SurfaceControl.Transaction t, SurfaceControl sc,
+ SurfaceControl parentSc, Rect crop, int layer) {
+ BufferConsumer consumer = new BufferConsumer(t, sc, parentSc, layer);
captureLayer(sc, crop, consumer);
return consumer.mScreenshot;
}
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 4c85d20..419e62d 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
@@ -561,6 +561,10 @@
if (from == to) {
// No animation run, still callback to stop resizing.
mSplitLayoutHandler.onLayoutSizeChanged(this);
+
+ if (flingFinishedCallback != null) {
+ flingFinishedCallback.run();
+ }
InteractionJankMonitorUtils.endTracing(
CUJ_SPLIT_SCREEN_RESIZE);
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 3714fe7..ecdafa9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -21,6 +21,7 @@
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import com.android.internal.logging.InstanceId;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.window.RemoteTransition;
@@ -67,34 +68,35 @@
* Starts a shortcut in a stage.
*/
oneway void startShortcut(String packageName, String shortcutId, int position,
- in Bundle options, in UserHandle user) = 8;
+ in Bundle options, in UserHandle user, in InstanceId instanceId) = 8;
/**
* Starts an activity in a stage.
*/
oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int position,
- in Bundle options) = 9;
+ in Bundle options, in InstanceId instanceId) = 9;
/**
* Starts tasks simultaneously in one transition.
*/
oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteTransition remoteTransition) = 10;
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 10;
/**
* Version of startTasks using legacy transition system.
*/
oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
int sideTaskId, in Bundle sideOptions, int sidePosition,
- float splitRatio, in RemoteAnimationAdapter adapter) = 11;
+ float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
/**
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions,
- int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter) = 12;
+ int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter,
+ in InstanceId instanceId) = 12;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
@@ -115,5 +117,5 @@
*/
oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, int taskId,
in Bundle mainOptions, in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteAnimationAdapter adapter) = 15;
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
}
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 9206afb..025e559 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
@@ -387,6 +387,17 @@
}
}
+ /**
+ * See {@link #startShortcut(String, String, int, Bundle, UserHandle)}
+ * @param instanceId to be used by {@link SplitscreenEventLogger}
+ */
+ public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
+ @Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) {
+ mStageCoordinator.getLogger().enterRequested(instanceId);
+ startShortcut(packageName, shortcutId, position, options, user);
+ }
+
+ @Override
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -415,7 +426,6 @@
0 /* duration */, 0 /* statusBarTransitionDelay */);
ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
-
try {
LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
@@ -425,6 +435,17 @@
}
}
+ /**
+ * See {@link #startIntent(PendingIntent, Intent, int, Bundle)}
+ * @param instanceId to be used by {@link SplitscreenEventLogger}
+ */
+ public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
+ @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
+ mStageCoordinator.getLogger().enterRequested(instanceId);
+ startIntent(intent, fillInIntent, position, options);
+ }
+
+ @Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
if (fillInIntent == null) {
@@ -446,7 +467,6 @@
mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
return;
}
-
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
@@ -772,60 +792,64 @@
@Override
public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- float splitRatio, RemoteAnimationAdapter adapter) {
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
- splitRatio, adapter));
+ splitRatio, adapter, instanceId));
}
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
- int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+ int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
pendingIntent, fillInIntent, taskId, mainOptions, sideOptions,
- sidePosition, splitRatio, adapter));
+ sidePosition, splitRatio, adapter, instanceId));
}
@Override
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startShortcutAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
shortcutInfo, taskId, mainOptions, sideOptions, sidePosition,
- splitRatio, adapter));
+ splitRatio, adapter, instanceId));
}
@Override
public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions,
@SplitPosition int sidePosition, float splitRatio,
- @Nullable RemoteTransition remoteTransition) {
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
- sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition));
+ sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition,
+ instanceId));
}
@Override
public void startShortcut(String packageName, String shortcutId, int position,
- @Nullable Bundle options, UserHandle user) {
+ @Nullable Bundle options, UserHandle user, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
(controller) -> {
- controller.startShortcut(packageName, shortcutId, position, options, user);
+ controller.startShortcut(packageName, shortcutId, position, options, user,
+ instanceId);
});
}
@Override
public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
- @Nullable Bundle options) {
+ @Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
(controller) -> {
- controller.startIntent(intent, fillInIntent, position, options);
+ controller.startIntent(intent, fillInIntent, position, options, instanceId);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 3e7a100..626ccb1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.splitscreen;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
@@ -59,7 +59,7 @@
// Drag info
private @SplitPosition int mDragEnterPosition;
- private InstanceId mDragEnterSessionId;
+ private InstanceId mEnterSessionId;
// For deduping async events
private int mLastMainStagePosition = -1;
@@ -82,9 +82,17 @@
/**
* May be called before logEnter() to indicate that the session was started from a drag.
*/
- public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
+ public void enterRequestedByDrag(@SplitPosition int position, InstanceId enterSessionId) {
mDragEnterPosition = position;
- mDragEnterSessionId = dragSessionId;
+ enterRequested(enterSessionId);
+ }
+
+ /**
+ * May be called before logEnter() to indicate that the session was started from launcher.
+ * This specifically is for all the scenarios where split started without a drag interaction
+ */
+ public void enterRequested(InstanceId enterSessionId) {
+ mEnterSessionId = enterSessionId;
}
/**
@@ -97,7 +105,7 @@
mLoggerSessionId = mIdSequence.newInstanceId();
int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
- : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+ : SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
mainStageUid);
updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
@@ -112,7 +120,7 @@
mLastMainStageUid,
mLastSideStagePosition,
mLastSideStageUid,
- mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
+ mEnterSessionId != null ? mEnterSessionId.getId() : 0,
mLoggerSessionId.getId());
}
@@ -176,7 +184,7 @@
// Reset states
mLoggerSessionId = null;
mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
- mDragEnterSessionId = null;
+ mEnterSessionId = null;
mLastMainStagePosition = -1;
mLastMainStageUid = -1;
mLastSideStagePosition = -1;
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 8ed16ce..946dfde 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
@@ -69,6 +69,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
@@ -87,6 +88,7 @@
import android.util.Slog;
import android.view.Choreographer;
import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -405,6 +407,10 @@
return result;
}
+ SplitscreenEventLogger getLogger() {
+ return mLogger;
+ }
+
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
@@ -497,7 +503,7 @@
/** Starts 2 tasks in one transition. */
void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
@Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
- @Nullable RemoteTransition remoteTransition) {
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mainOptions = mainOptions != null ? mainOptions : new Bundle();
sideOptions = sideOptions != null ? sideOptions : new Bundle();
@@ -526,46 +532,57 @@
mSplitTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
+ setEnterInstanceId(instanceId);
}
/** Starts 2 tasks in one legacy transition. */
void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- float splitRatio, RemoteAnimationAdapter adapter) {
+ float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (sideOptions == null) sideOptions = new Bundle();
addActivityOptions(sideOptions, mSideStage);
wct.startTask(sideTaskId, sideOptions);
- startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter);
+ startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter,
+ instanceId);
}
/** Start an intent and a task ordered by {@code intentFirst}. */
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (sideOptions == null) sideOptions = new Bundle();
addActivityOptions(sideOptions, mSideStage);
wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter);
+ startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ instanceId);
}
void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (sideOptions == null) sideOptions = new Bundle();
addActivityOptions(sideOptions, mSideStage);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, sideOptions);
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter);
+ startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ instanceId);
}
- private void startWithLegacyTransition(WindowContainerTransaction sideWct, int mainTaskId,
+ /**
+ * @param instanceId if {@code null}, will not log. Otherwise it will be used in
+ * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+ */
+ private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter) {
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
mSplitLayout.setDivideRatio(splitRatio);
@@ -574,59 +591,56 @@
mShouldUpdateRecents = false;
mIsDividerRemoteAnimating = true;
- LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
+ prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct);
+
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback,
- SurfaceControl.Transaction t) {
- if (apps == null || apps.length == 0) {
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- setDividerVisibility(true, t);
- t.apply();
- onRemoteAnimationFinished(apps);
- try {
- adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- return;
- }
-
- // Wrap the divider bar into non-apps target to animate together.
- nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
- getDividerBarLegacyTarget());
-
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- setDividerVisibility(true, t);
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- // Reset the surface position of the opening app to prevent double-offset.
- t.setPosition(apps[i].leash, 0, 0);
- }
- }
- t.apply();
-
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
IRemoteAnimationFinishedCallback wrapCallback =
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() throws RemoteException {
- onRemoteAnimationFinished(apps);
+ onRemoteAnimationFinishedOrCancelled(false /* cancel */, evictWct);
finishedCallback.onAnimationFinished();
}
};
Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
try {
- adapter.getRunner().onAnimationStart(
- transit, apps, wallpapers, nonApps, wrapCallback);
+ adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
+ ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+ getDividerBarLegacyTarget()), wrapCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ onRemoteAnimationFinishedOrCancelled(true /* cancel */, evictWct);
+ try {
+ adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
} catch (RemoteException e) {
Slog.e(TAG, "Error starting remote animation", e);
}
}
};
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+ wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (mainOptions == null) {
+ mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+ } else {
+ ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+ mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ mainOptions = mainActivityOptions.toBundle();
+ }
+
setSideStagePosition(sidePosition, wct);
if (!mMainStage.isActive()) {
mMainStage.activate(wct, false /* reparent */);
@@ -634,34 +648,40 @@
if (mainOptions == null) mainOptions = new Bundle();
addActivityOptions(mainOptions, mMainStage);
- wct.startTask(mainTaskId, mainOptions);
- wct.merge(sideWct, true);
-
updateWindowBounds(mSplitLayout, wct);
+ wct.startTask(mainTaskId, mainOptions);
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
- mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ setDividerVisibility(true, t);
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ });
+
+ setEnterInstanceId(instanceId);
}
- private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
+ private void setEnterInstanceId(InstanceId instanceId) {
+ if (instanceId != null) {
+ mLogger.enterRequested(instanceId);
+ }
+ }
+
+ private void onRemoteAnimationFinishedOrCancelled(boolean cancel,
+ WindowContainerTransaction evictWct) {
mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
- if (apps == null || apps.length == 0) return;
-
- // If any stage has no child after finished animation, that side of the split will display
- // nothing. This might happen if starting the same app on the both sides while not
- // supporting multi-instance. Exit the split screen and expand that app to full screen.
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
- ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
- return;
+ // If any stage has no child after animation finished, it means that split will display
+ // nothing, such status will happen if task and intent is same app but not support
+ // multi-instance, we should exit split and expand that app as full screen.
+ if (!cancel && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+ mMainExecutor.execute(() ->
+ exitSplitScreen(mMainStage.getChildCount() == 0
+ ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ } else {
+ mSyncQueue.queue(evictWct);
}
-
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
- prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
- mSyncQueue.queue(evictWct);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index cbdac61..3152e65 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -16,20 +16,6 @@
package com.android.settingslib.bluetooth;
-import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
-import static android.bluetooth.BluetoothAdapter.STATE_CONNECTING;
-import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED;
-import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTING;
-import static android.bluetooth.BluetoothAdapter.STATE_OFF;
-import static android.bluetooth.BluetoothAdapter.STATE_ON;
-import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF;
-import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
/**
* BluetoothCallback provides a callback interface for the settings
@@ -47,7 +33,7 @@
* {@link android.bluetooth.BluetoothAdapter#STATE_ON},
* {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}.
*/
- default void onBluetoothStateChanged(@AdapterState int bluetoothState) {}
+ default void onBluetoothStateChanged(int bluetoothState) {}
/**
* It will be called when the local Bluetooth adapter has started
@@ -103,9 +89,7 @@
* {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED},
* {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTING}.
*/
- default void onConnectionStateChanged(
- @Nullable CachedBluetoothDevice cachedDevice,
- @ConnectionState int state) {}
+ default void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {}
/**
* It will be called when device been set as active for {@code bluetoothProfile}
@@ -140,10 +124,8 @@
* {@link android.bluetooth.BluetoothProfile#STATE_DISCONNECTING}.
* @param bluetoothProfile the BluetoothProfile id.
*/
- default void onProfileConnectionStateChanged(
- CachedBluetoothDevice cachedDevice,
- @ConnectionState int state,
- int bluetoothProfile) {
+ default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
+ int state, int bluetoothProfile) {
}
/**
@@ -158,22 +140,4 @@
*/
default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
}
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "STATE_" }, value = {
- STATE_DISCONNECTED,
- STATE_CONNECTING,
- STATE_CONNECTED,
- STATE_DISCONNECTING,
- })
- @interface ConnectionState {}
-
- @IntDef(prefix = { "STATE_" }, value = {
- STATE_OFF,
- STATE_TURNING_ON,
- STATE_ON,
- STATE_TURNING_OFF,
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface AdapterState {}
}
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8089bb1..e0264c3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -520,7 +520,7 @@
<dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen>
<dimen name="qs_tile_margin_top_bottom">4dp</dimen>
<dimen name="qs_brightness_margin_top">8dp</dimen>
- <dimen name="qs_brightness_margin_bottom">24dp</dimen>
+ <dimen name="qs_brightness_margin_bottom">16dp</dimen>
<dimen name="qqs_layout_margin_top">16dp</dimen>
<dimen name="qqs_layout_padding_bottom">24dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 919b71b..f82e7db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -55,6 +55,8 @@
val bouncerIsOrWillShow: Boolean,
val faceAuthenticated: Boolean,
val faceDisabled: Boolean,
+ val faceLockedOut: Boolean,
+ val fpLockedOut: Boolean,
val goingToSleep: Boolean,
val keyguardAwakeExcludingBouncerShowing: Boolean,
val keyguardGoingAway: Boolean,
@@ -65,7 +67,7 @@
val scanningAllowedByStrongAuth: Boolean,
val secureCameraLaunched: Boolean,
val switchingUser: Boolean,
- val udfpsBouncerShowing: Boolean
+ val udfpsBouncerShowing: Boolean,
) : KeyguardListenModel()
/**
* Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock].
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 35ee3f7..763b29e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2626,6 +2626,7 @@
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
final boolean onlyFaceEnrolled = isOnlyFaceEnrolled();
+ final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout;
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -2642,7 +2643,7 @@
&& strongAuthAllowsScanning && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
&& !faceAuthenticated
- && !fpLockedout;
+ && !fpOrFaceIsLockedOut;
// Aggregate relevant fields for debug logging.
maybeLogListenerModelData(
@@ -2657,6 +2658,8 @@
mBouncerIsOrWillBeShowing,
faceAuthenticated,
faceDisabledForUser,
+ isFaceLockedOut(),
+ fpLockedout,
mGoingToSleep,
awakeKeyguardExcludingBouncerShowing,
mKeyguardGoingAway,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
deleted file mode 100644
index b5da7b6..0000000
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bluetooth
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.BluetoothLog
-import javax.inject.Inject
-
-/** Helper class for logging bluetooth events. */
-@SysUISingleton
-class BluetoothLogger @Inject constructor(@BluetoothLog private val logBuffer: LogBuffer) {
- fun logActiveDeviceChanged(address: String, profileId: Int) =
- logBuffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = address
- int1 = profileId
- },
- { "ActiveDeviceChanged. address=$str1 profileId=$int1" }
- )
-
- fun logDeviceConnectionStateChanged(address: String?, state: String) =
- logBuffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = address
- str2 = state
- },
- { "DeviceConnectionStateChanged. address=$str1 state=$str2" }
- )
-
- fun logAclConnectionStateChanged(address: String, state: String) =
- logBuffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = address
- str2 = state
- },
- { "AclConnectionStateChanged. address=$str1 state=$str2" }
- )
-
- fun logProfileConnectionStateChanged(address: String, state: String, profileId: Int) =
- logBuffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = address
- str2 = state
- int1 = profileId
- },
- { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
- )
-
- fun logStateChange(state: String) =
- logBuffer.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = state },
- { "BluetoothStateChanged. state=$str1" }
- )
-
- fun logBondStateChange(address: String, state: Int) =
- logBuffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = address
- int1 = state
- },
- { "DeviceBondStateChanged. address=$str1 state=$int1" }
- )
-
- fun logDeviceAdded(address: String) =
- logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceAdded. address=$str1" })
-
- fun logDeviceDeleted(address: String) =
- logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceDeleted. address=$str1" })
-
- fun logDeviceAttributesChanged() =
- logBuffer.log(TAG, LogLevel.DEBUG, {}, { "DeviceAttributesChanged." })
-}
-
-private const val TAG = "BluetoothLog"
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 659f286..a3dc779 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -601,7 +601,7 @@
private final class BluetoothCallbackHandler implements BluetoothCallback {
@Override
- public void onBluetoothStateChanged(@BluetoothCallback.AdapterState int bluetoothState) {
+ public void onBluetoothStateChanged(int bluetoothState) {
mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED,
bluetoothState, 0).sendToTarget();
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt
deleted file mode 100644
index 4887b6a..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.log.dagger
-
-import javax.inject.Qualifier
-
-/** A [com.android.systemui.log.LogBuffer] for bluetooth. */
-@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BluetoothLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
index b551125..323ee21 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -1,9 +1,4 @@
package com.android.systemui.log.dagger
-import javax.inject.Qualifier
-
/** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
annotation class KeyguardUpdateMonitorLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index eeba6b3..c2a8764 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -305,14 +305,4 @@
public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
return factory.create("KeyguardUpdateMonitorLog", 200);
}
-
- /**
- * Provides a {@link LogBuffer} for bluetooth-related logs.
- */
- @Provides
- @SysUISingleton
- @BluetoothLog
- public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) {
- return factory.create("BluetoothLog", 50);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 920f463..e1289a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -99,7 +99,7 @@
// slider, as well as animating the alpha of the QS tile layout (as we are tracking QQS tiles)
@Nullable
private TouchAnimator mFirstPageAnimator;
- // TranslationX animator for QQS/QS tiles
+ // TranslationX animator for QQS/QS tiles. Only used on the first page!
private TouchAnimator mTranslationXAnimator;
// TranslationY animator for QS tiles (and their components) in the first page
private TouchAnimator mTranslationYAnimator;
@@ -107,13 +107,14 @@
private TouchAnimator mQQSTranslationYAnimator;
// Animates alpha of permanent views (QS tile layout, QQS tiles) when not in first page
private TouchAnimator mNonfirstPageAlphaAnimator;
- // TranslatesY the QS Tile layout using QS.getHeightDiff()
- private TouchAnimator mQSTileLayoutTranslatorAnimator;
// This animates fading of media player
private TouchAnimator mAllPagesDelayedAnimator;
- // Animator for brightness slider(s)
+ // Brightness slider translation driver, uses mQSExpansionPathInterpolator.yInterpolator
@Nullable
- private TouchAnimator mBrightnessAnimator;
+ private TouchAnimator mBrightnessTranslationAnimator;
+ // Brightness slider opacity driver. Uses linear interpolator.
+ @Nullable
+ private TouchAnimator mBrightnessOpacityAnimator;
// Animator for Footer actions in QQS
private TouchAnimator mQQSFooterActionsAnimator;
// Height animator for QQS tiles (height changing from QQS size to QS size)
@@ -137,7 +138,6 @@
private final QSTileHost mHost;
private final Executor mExecutor;
private boolean mShowCollapsedOnKeyguard;
- private boolean mTranslateWhileExpanding;
private int mQQSTop;
private int[] mTmpLoc1 = new int[2];
@@ -298,13 +298,6 @@
QSTileLayout tileLayout = mQsPanelController.getTileLayout();
mAllViews.add((View) tileLayout);
- int heightDiff = mQs.getHeightDiff();
- if (!mTranslateWhileExpanding) {
- heightDiff *= SHORT_PARALLAX_AMOUNT;
- }
- mQSTileLayoutTranslatorAnimator = new Builder()
- .addFloat(tileLayout, "translationY", heightDiff, 0)
- .build();
mLastQQSTileHeight = 0;
@@ -407,12 +400,7 @@
mAnimatedQsViews.add(tileView);
mAllViews.add(quickTileView);
mAllViews.add(quickTileView.getSecondaryLabel());
- } else if (isIconInAnimatedRow(count)) {
-
- firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0);
-
- mAllViews.add(tileIcon);
- } else {
+ } else if (!isIconInAnimatedRow(count)) {
// Pretend there's a corresponding QQS tile (for the position) that we are
// expanding from.
SideLabelTileLayout qqsLayout =
@@ -442,7 +430,7 @@
}
}
- animateBrightnessSlider(firstPageBuilder);
+ animateBrightnessSlider();
mFirstPageAnimator = firstPageBuilder
// Fade in the tiles/labels as we reach the final position.
@@ -568,7 +556,9 @@
return new Pair<>(animator, builder.build());
}
- private void animateBrightnessSlider(Builder firstPageBuilder) {
+ private void animateBrightnessSlider() {
+ mBrightnessTranslationAnimator = null;
+ mBrightnessOpacityAnimator = null;
View qsBrightness = mQsPanelController.getBrightnessView();
View qqsBrightness = mQuickQSPanelController.getBrightnessView();
if (qqsBrightness != null && qqsBrightness.getVisibility() == View.VISIBLE) {
@@ -576,25 +566,45 @@
mAnimatedQsViews.add(qsBrightness);
mAllViews.add(qqsBrightness);
int translationY = getRelativeTranslationY(qsBrightness, qqsBrightness);
- mBrightnessAnimator = new Builder()
+ mBrightnessTranslationAnimator = new Builder()
// we need to animate qs brightness even if animation will not be visible,
// as we might start from sliderScaleY set to 0.3 if device was in collapsed QS
// portrait orientation before
.addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
.addFloat(qqsBrightness, "translationY", 0, translationY)
+ .setInterpolator(mQSExpansionPathInterpolator.getYInterpolator())
.build();
} else if (qsBrightness != null) {
- firstPageBuilder.addFloat(qsBrightness, "translationY",
- qsBrightness.getMeasuredHeight() * 0.5f, 0);
- mBrightnessAnimator = new Builder()
+ // The brightness slider's visible bottom edge must maintain a constant margin from the
+ // QS tiles during transition. Thus the slider must (1) perform the same vertical
+ // translation as the tiles, and (2) compensate for the slider scaling.
+
+ // For (1), compute the distance via the vertical distance between QQS and QS tile
+ // layout top.
+ View quickSettingsRootView = mQs.getView();
+ View qsTileLayout = (View) mQsPanelController.getTileLayout();
+ View qqsTileLayout = (View) mQuickQSPanelController.getTileLayout();
+ getRelativePosition(mTmpLoc1, qsTileLayout, quickSettingsRootView);
+ getRelativePosition(mTmpLoc2, qqsTileLayout, quickSettingsRootView);
+ int tileMovement = mTmpLoc2[1] - mTmpLoc1[1];
+
+ // For (2), the slider scales to the vertical center, so compensate with half the
+ // height at full collapse.
+ float scaleCompensation = qsBrightness.getMeasuredHeight() * 0.5f;
+ mBrightnessTranslationAnimator = new Builder()
+ .addFloat(qsBrightness, "translationY", scaleCompensation + tileMovement, 0)
+ .addFloat(qsBrightness, "sliderScaleY", 0, 1)
+ .setInterpolator(mQSExpansionPathInterpolator.getYInterpolator())
+ .build();
+
+ // While the slider's position and unfurl is animated throughouth the motion, the
+ // fade in happens independently.
+ mBrightnessOpacityAnimator = new Builder()
.addFloat(qsBrightness, "alpha", 0, 1)
- .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
- .setInterpolator(Interpolators.ALPHA_IN)
- .setStartDelay(0.3f)
+ .setStartDelay(0.2f)
+ .setEndDelay(1 - 0.5f)
.build();
mAllViews.add(qsBrightness);
- } else {
- mBrightnessAnimator = null;
}
}
@@ -676,11 +686,13 @@
if (mQQSTileHeightAnimator != null) {
mQQSTileHeightAnimator.setPosition(position);
}
- mQSTileLayoutTranslatorAnimator.setPosition(position);
mQQSTranslationYAnimator.setPosition(position);
mAllPagesDelayedAnimator.setPosition(position);
- if (mBrightnessAnimator != null) {
- mBrightnessAnimator.setPosition(position);
+ if (mBrightnessOpacityAnimator != null) {
+ mBrightnessOpacityAnimator.setPosition(position);
+ }
+ if (mBrightnessTranslationAnimator != null) {
+ mBrightnessTranslationAnimator.setPosition(position);
}
if (mQQSFooterActionsAnimator != null) {
mQQSFooterActionsAnimator.setPosition(position);
@@ -774,13 +786,6 @@
setCurrentPosition();
};
- /**
- * True whe QS will be pulled from the top, false when it will be clipped.
- */
- public void setTranslateWhileExpanding(boolean shouldTranslate) {
- mTranslateWhileExpanding = shouldTranslate;
- }
-
private static class HeightExpansionAnimator {
private final List<View> mViews = new ArrayList<>();
private final ValueAnimator mAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 90fa94c..7b27cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -573,7 +573,6 @@
@Override
public void setInSplitShade(boolean inSplitShade) {
mInSplitShade = inSplitShade;
- mQSAnimator.setTranslateWhileExpanding(inSplitShade);
updateShowCollapsedOnKeyguard();
updateQsState();
}
@@ -669,7 +668,11 @@
mQSPanelController.setRevealExpansion(expansion);
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
- mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff);
+
+ float qsScrollViewTranslation =
+ onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
+ mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
+
if (fullyCollapsed) {
mQSPanelScrollView.setScrollY(0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index ba26cfa..df81c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1369,6 +1369,8 @@
}
ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
+ LinearLayout actionListMarginTarget = layout.findViewById(
+ com.android.internal.R.id.notification_action_list_margin_target);
if (bubbleButton == null || actionContainer == null) {
return;
}
@@ -1393,6 +1395,16 @@
bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener());
bubbleButton.setVisibility(VISIBLE);
actionContainer.setVisibility(VISIBLE);
+ // Set notification_action_list_margin_target's bottom margin to 0 when showing bubble
+ if (actionListMarginTarget != null) {
+ ViewGroup.LayoutParams lp = actionListMarginTarget.getLayoutParams();
+ if (lp instanceof ViewGroup.MarginLayoutParams) {
+ final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
+ if (mlp.bottomMargin > 0) {
+ mlp.setMargins(mlp.leftMargin, mlp.topMargin, mlp.rightMargin, 0);
+ }
+ }
+ }
} else {
bubbleButton.setVisibility(GONE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 1bbacca..fe43137 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -23,7 +23,6 @@
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
import android.os.Handler;
@@ -683,10 +682,7 @@
final boolean fingerprintLockout = biometricSourceType == BiometricSourceType.FINGERPRINT
&& (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
|| msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT);
- final boolean faceLockout = biometricSourceType == BiometricSourceType.FACE
- && (msgId == FaceManager.FACE_ERROR_LOCKOUT
- || msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT);
- if (fingerprintLockout || faceLockout) {
+ if (fingerprintLockout) {
startWakeAndUnlock(MODE_SHOW_BOUNCER);
UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index c7ea3c6..e7fa6d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -27,6 +27,7 @@
import android.os.Message;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Log;
import androidx.annotation.NonNull;
@@ -36,7 +37,6 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.systemui.bluetooth.BluetoothLogger;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -57,9 +57,9 @@
public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
private static final String TAG = "BluetoothController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final DumpManager mDumpManager;
- private final BluetoothLogger mLogger;
private final LocalBluetoothManager mLocalBluetoothManager;
private final UserManager mUserManager;
private final int mCurrentUser;
@@ -70,7 +70,6 @@
private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>();
private boolean mEnabled;
- @ConnectionState
private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
private boolean mAudioProfileOnly;
private boolean mIsActive;
@@ -84,12 +83,10 @@
public BluetoothControllerImpl(
Context context,
DumpManager dumpManager,
- BluetoothLogger logger,
@Background Looper bgLooper,
@Main Looper mainLooper,
@Nullable LocalBluetoothManager localBluetoothManager) {
mDumpManager = dumpManager;
- mLogger = logger;
mLocalBluetoothManager = localBluetoothManager;
mBgHandler = new Handler(bgLooper);
mHandler = new H(mainLooper);
@@ -119,7 +116,7 @@
return;
}
pw.print(" mEnabled="); pw.println(mEnabled);
- pw.print(" mConnectionState="); pw.println(connectionStateToString(mConnectionState));
+ pw.print(" mConnectionState="); pw.println(stateToString(mConnectionState));
pw.print(" mAudioProfileOnly="); pw.println(mAudioProfileOnly);
pw.print(" mIsActive="); pw.println(mIsActive);
pw.print(" mConnectedDevices="); pw.println(getConnectedDevices());
@@ -130,7 +127,7 @@
}
}
- private static String connectionStateToString(@ConnectionState int state) {
+ private static String stateToString(int state) {
switch (state) {
case BluetoothAdapter.STATE_CONNECTED:
return "CONNECTED";
@@ -323,8 +320,8 @@
}
@Override
- public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
- mLogger.logStateChange(BluetoothAdapter.nameForState(bluetoothState));
+ public void onBluetoothStateChanged(int bluetoothState) {
+ if (DEBUG) Log.d(TAG, "BluetoothStateChanged=" + stateToString(bluetoothState));
mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
|| bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
mState = bluetoothState;
@@ -334,7 +331,7 @@
@Override
public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
- mLogger.logDeviceAdded(cachedDevice.getAddress());
+ if (DEBUG) Log.d(TAG, "DeviceAdded=" + cachedDevice.getAddress());
cachedDevice.registerCallback(this);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -342,7 +339,7 @@
@Override
public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
- mLogger.logDeviceDeleted(cachedDevice.getAddress());
+ if (DEBUG) Log.d(TAG, "DeviceDeleted=" + cachedDevice.getAddress());
mCachedState.remove(cachedDevice);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -350,7 +347,7 @@
@Override
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
- mLogger.logBondStateChange(cachedDevice.getAddress(), bondState);
+ if (DEBUG) Log.d(TAG, "DeviceBondStateChanged=" + cachedDevice.getAddress());
mCachedState.remove(cachedDevice);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
@@ -358,29 +355,29 @@
@Override
public void onDeviceAttributesChanged() {
- mLogger.logDeviceAttributesChanged();
+ if (DEBUG) Log.d(TAG, "DeviceAttributesChanged");
updateConnected();
mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
@Override
- public void onConnectionStateChanged(
- @Nullable CachedBluetoothDevice cachedDevice,
- @ConnectionState int state) {
- String address = cachedDevice == null ? null : cachedDevice.getAddress();
- mLogger.logDeviceConnectionStateChanged(address, connectionStateToString(state));
+ public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ if (DEBUG) {
+ Log.d(TAG, "ConnectionStateChanged=" + cachedDevice.getAddress() + " "
+ + stateToString(state));
+ }
mCachedState.remove(cachedDevice);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
@Override
- public void onProfileConnectionStateChanged(
- CachedBluetoothDevice cachedDevice,
- @ConnectionState int state,
- int bluetoothProfile) {
- mLogger.logProfileConnectionStateChanged(
- cachedDevice.getAddress(), connectionStateToString(state), bluetoothProfile);
+ public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
+ int state, int bluetoothProfile) {
+ if (DEBUG) {
+ Log.d(TAG, "ProfileConnectionStateChanged=" + cachedDevice.getAddress() + " "
+ + stateToString(state) + " profileId=" + bluetoothProfile);
+ }
mCachedState.remove(cachedDevice);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
@@ -388,15 +385,20 @@
@Override
public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- mLogger.logActiveDeviceChanged(activeDevice.getAddress(), bluetoothProfile);
+ if (DEBUG) {
+ Log.d(TAG, "ActiveDeviceChanged=" + activeDevice.getAddress()
+ + " profileId=" + bluetoothProfile);
+ }
updateActive();
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
@Override
public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
- mLogger.logAclConnectionStateChanged(
- cachedDevice.getAddress(), connectionStateToString(state));
+ if (DEBUG) {
+ Log.d(TAG, "ACLConnectionStateChanged=" + cachedDevice.getAddress() + " "
+ + stateToString(state));
+ }
mCachedState.remove(cachedDevice);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 0bf038d..2714cf4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -90,6 +90,8 @@
onlyFaceEnrolled = false,
faceAuthenticated = false,
faceDisabled = false,
+ faceLockedOut = false,
+ fpLockedOut = false,
goingToSleep = false,
keyguardAwakeExcludingBouncerShowing = false,
keyguardGoingAway = false,
@@ -99,5 +101,5 @@
scanningAllowedByStrongAuth = false,
secureCameraLaunched = false,
switchingUser = false,
- udfpsBouncerShowing = false
+ udfpsBouncerShowing = false,
)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d73799c..0275152 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -19,6 +19,7 @@
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -750,8 +751,7 @@
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
- mKeyguardUpdateMonitor.mFaceAuthenticationCallback
- .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
+ faceAuthLockedOut();
verify(mLockPatternUtils, never()).requireStrongAuth(anyInt(), anyInt());
}
@@ -763,7 +763,7 @@
mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
- .onAuthenticationError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
+ .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
verify(mLockPatternUtils).requireStrongAuth(anyInt(), anyInt());
}
@@ -774,10 +774,9 @@
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
- mKeyguardUpdateMonitor.mFaceAuthenticationCallback
- .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
+ faceAuthLockedOut();
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
- .onAuthenticationError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
+ .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
verify(mLockPatternUtils).requireStrongAuth(anyInt(), anyInt());
}
@@ -1216,7 +1215,7 @@
public void testShouldListenForFace_whenFpIsLockedOut_returnsFalse() throws RemoteException {
// Face auth should run when the following is true.
keyguardNotGoingAway();
- bouncerFullyVisibleAndNotGoingToSleep();
+ occludingAppRequestsFaceAuth();
currentUserIsPrimary();
strongAuthNotRequired();
biometricsEnabledForCurrentUser();
@@ -1224,6 +1223,7 @@
biometricsNotDisabledThroughDevicePolicyManager();
userNotCurrentlySwitching();
mTestableLooper.processAllMessages();
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
// Fingerprint is locked out.
fingerprintErrorLockedOut();
@@ -1499,6 +1499,27 @@
}
@Test
+ public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
+ throws RemoteException {
+ // Preconditions for face auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+ mTestableLooper.processAllMessages();
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ // Face is locked out.
+ faceAuthLockedOut();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
public void testBouncerVisibility_whenBothFingerprintAndFaceIsEnrolled_stopsFaceAuth()
throws RemoteException {
// Both fingerprint and face are enrolled by default
@@ -1586,6 +1607,11 @@
verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
}
+ private void faceAuthLockedOut() {
+ mKeyguardUpdateMonitor.mFaceAuthenticationCallback
+ .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
+ }
+
private void faceAuthEnabled() {
// this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the
// face manager mock wire-up in setup()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index cf5e878..3dd36d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -41,7 +41,6 @@
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bluetooth.BluetoothLogger;
import com.android.systemui.dump.DumpManager;
import org.junit.Before;
@@ -82,7 +81,6 @@
mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
mMockDumpManager,
- mock(BluetoothLogger.class),
mTestableLooper.getLooper(),
mTestableLooper.getLooper(),
mMockBluetoothManager);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 31562c73..a7d3729 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -131,6 +131,8 @@
// Configuration object for determining thresholds to change brightness dynamically
private final HysteresisLevels mAmbientBrightnessThresholds;
private final HysteresisLevels mScreenBrightnessThresholds;
+ private final HysteresisLevels mAmbientBrightnessThresholdsIdle;
+ private final HysteresisLevels mScreenBrightnessThresholdsIdle;
private boolean mLoggingEnabled;
@@ -242,7 +244,9 @@
float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds, Context context,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong) {
@@ -251,7 +255,8 @@
lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
- ambientBrightnessThresholds, screenBrightnessThresholds, context,
+ ambientBrightnessThresholds, screenBrightnessThresholds,
+ ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context,
hbmController, brightnessThrottler, idleModeBrightnessMapper,
ambientLightHorizonShort, ambientLightHorizonLong
);
@@ -265,7 +270,9 @@
float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds, Context context,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong) {
@@ -289,7 +296,9 @@
mAmbientLightHorizonShort = ambientLightHorizonShort;
mWeightingIntercept = ambientLightHorizonLong;
mAmbientBrightnessThresholds = ambientBrightnessThresholds;
+ mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle;
mScreenBrightnessThresholds = screenBrightnessThresholds;
+ mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle;
mShortTermModelValid = true;
mShortTermModelAnchor = -1;
mHandler = new AutomaticBrightnessHandler(looper);
@@ -585,6 +594,8 @@
pw.println();
mAmbientBrightnessThresholds.dump(pw);
mScreenBrightnessThresholds.dump(pw);
+ mScreenBrightnessThresholdsIdle.dump(pw);
+ mScreenBrightnessThresholdsIdle.dump(pw);
}
private String configStateToString(int state) {
@@ -679,8 +690,17 @@
lux = 0;
}
mAmbientLux = lux;
- mAmbientBrighteningThreshold = mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
- mAmbientDarkeningThreshold = mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
+ if (isInIdleMode()) {
+ mAmbientBrighteningThreshold =
+ mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux);
+ mAmbientDarkeningThreshold =
+ mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux);
+ } else {
+ mAmbientBrighteningThreshold =
+ mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
+ mAmbientDarkeningThreshold =
+ mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
+ }
mHbmController.onAmbientLuxChange(mAmbientLux);
// If the short term model was invalidated and the change is drastic enough, reset it.
@@ -902,10 +922,20 @@
mPreThresholdBrightness = mScreenAutoBrightness;
}
mScreenAutoBrightness = newScreenAutoBrightness;
- mScreenBrighteningThreshold = clampScreenBrightness(
- mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness));
- mScreenDarkeningThreshold = clampScreenBrightness(
- mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness));
+ if (isInIdleMode()) {
+ mScreenBrighteningThreshold = clampScreenBrightness(
+ mScreenBrightnessThresholdsIdle.getBrighteningThreshold(
+ newScreenAutoBrightness));
+ mScreenDarkeningThreshold = clampScreenBrightness(
+ mScreenBrightnessThresholdsIdle.getDarkeningThreshold(
+ newScreenAutoBrightness));
+ } else {
+ mScreenBrighteningThreshold = clampScreenBrightness(
+ mScreenBrightnessThresholds.getBrighteningThreshold(
+ newScreenAutoBrightness));
+ mScreenDarkeningThreshold = clampScreenBrightness(
+ mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness));
+ }
if (sendUpdate) {
mCallbacks.updateBrightness();
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 12b2f47..4165186 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -188,8 +188,8 @@
* <ambientLightHorizonLong>10001</ambientLightHorizonLong>
* <ambientLightHorizonShort>2001</ambientLightHorizonShort>
*
- * <displayBrightnessChangeThresholds>
- * <brighteningThresholds>
+ * <displayBrightnessChangeThresholds> // Thresholds for screen changes
+ * <brighteningThresholds> // Thresholds for active mode brightness changes.
* <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten.
* </brighteningThresholds>
* <darkeningThresholds>
@@ -197,8 +197,8 @@
* </darkeningThresholds>
* </displayBrightnessChangeThresholds>
*
- * <ambientBrightnessChangeThresholds>
- * <brighteningThresholds>
+ * <ambientBrightnessChangeThresholds> // Thresholds for lux changes
+ * <brighteningThresholds> // Thresholds for active mode brightness changes.
* <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten.
* </brighteningThresholds>
* <darkeningThresholds>
@@ -206,6 +206,24 @@
* </darkeningThresholds>
* </ambientBrightnessChangeThresholds>
*
+ * <displayBrightnessChangeThresholdsIdle> // Thresholds for screen changes in idle mode
+ * <brighteningThresholds> // Thresholds for idle mode brightness changes.
+ * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten.
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * <minimum>0.002</minimum> // Minimum change needed in screen brightness to darken.
+ * </darkeningThresholds>
+ * </displayBrightnessChangeThresholdsIdle>
+ *
+ * <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
+ * <brighteningThresholds> // Thresholds for idle mode brightness changes.
+ * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten.
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * <minimum>0.004</minimum> // Minimum change needed in ambient brightness to darken.
+ * </darkeningThresholds>
+ * </ambientBrightnessChangeThresholdsIdle>
+ *
* </displayConfiguration>
* }
* </pre>
@@ -319,9 +337,13 @@
private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS;
private float mScreenBrighteningMinThreshold = 0.0f; // Retain behaviour as though there is
- private float mScreenDarkeningMinThreshold = 0.0f; // no minimum threshold for change in
- private float mAmbientLuxBrighteningMinThreshold = 0.0f; // screen brightness or ambient
- private float mAmbientLuxDarkeningMinThreshold = 0.0f; // brightness.
+ private float mScreenBrighteningMinThresholdIdle = 0.0f; // no minimum threshold for change in
+ private float mScreenDarkeningMinThreshold = 0.0f; // screen brightness or ambient
+ private float mScreenDarkeningMinThresholdIdle = 0.0f; // brightness.
+ private float mAmbientLuxBrighteningMinThreshold = 0.0f;
+ private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
+ private float mAmbientLuxDarkeningMinThreshold = 0.0f;
+ private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
private Spline mBrightnessToBacklightSpline;
private Spline mBacklightToBrightnessSpline;
private Spline mBacklightToNitsSpline;
@@ -625,22 +647,76 @@
return mAmbientHorizonShort;
}
+ /**
+ * The minimum value for the screen brightness increase to actually occur.
+ * @return float value in brightness scale of 0 - 1.
+ */
public float getScreenBrighteningMinThreshold() {
return mScreenBrighteningMinThreshold;
}
+ /**
+ * The minimum value for the screen brightness decrease to actually occur.
+ * @return float value in brightness scale of 0 - 1.
+ */
public float getScreenDarkeningMinThreshold() {
return mScreenDarkeningMinThreshold;
}
+ /**
+ * The minimum value for the screen brightness increase to actually occur while in idle screen
+ * brightness mode.
+ * @return float value in brightness scale of 0 - 1.
+ */
+ public float getScreenBrighteningMinThresholdIdle() {
+ return mScreenBrighteningMinThresholdIdle;
+ }
+
+ /**
+ * The minimum value for the screen brightness decrease to actually occur while in idle screen
+ * brightness mode.
+ * @return float value in brightness scale of 0 - 1.
+ */
+ public float getScreenDarkeningMinThresholdIdle() {
+ return mScreenDarkeningMinThresholdIdle;
+ }
+
+ /**
+ * The minimum value for the ambient lux increase for a screen brightness change to actually
+ * occur.
+ * @return float value in brightness scale of 0 - 1.
+ */
public float getAmbientLuxBrighteningMinThreshold() {
return mAmbientLuxBrighteningMinThreshold;
}
+ /**
+ * The minimum value for the ambient lux decrease for a screen brightness change to actually
+ * occur.
+ * @return float value in brightness scale of 0 - 1.
+ */
public float getAmbientLuxDarkeningMinThreshold() {
return mAmbientLuxDarkeningMinThreshold;
}
+ /**
+ * The minimum value for the ambient lux increase for a screen brightness change to actually
+ * occur while in idle screen brightness mode.
+ * @return float value in brightness scale of 0 - 1.
+ */
+ public float getAmbientLuxBrighteningMinThresholdIdle() {
+ return mAmbientLuxBrighteningMinThresholdIdle;
+ }
+
+ /**
+ * The minimum value for the ambient lux decrease for a screen brightness change to actually
+ * occur while in idle screen brightness mode.
+ * @return float value in brightness scale of 0 - 1.
+ */
+ public float getAmbientLuxDarkeningMinThresholdIdle() {
+ return mAmbientLuxDarkeningMinThresholdIdle;
+ }
+
SensorData getAmbientLightSensor() {
return mAmbientLightSensor;
}
@@ -745,9 +821,14 @@
+ ", mAmbientHorizonLong=" + mAmbientHorizonLong
+ ", mAmbientHorizonShort=" + mAmbientHorizonShort
+ ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
+ + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
+ ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
+ + ", mScreenBrighteningMinThresholdIdle=" + mScreenBrighteningMinThresholdIdle
+ ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold
+ + ", mAmbientLuxDarkeningMinThresholdIdle=" + mAmbientLuxDarkeningMinThresholdIdle
+ ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
+ + ", mAmbientLuxBrighteningMinThresholdIdle="
+ + mAmbientLuxBrighteningMinThresholdIdle
+ ", mAmbientLightSensor=" + mAmbientLightSensor
+ ", mProximitySensor=" + mProximitySensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -1376,24 +1457,34 @@
private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
+ Thresholds displayBrightnessThresholdsIdle =
+ config.getDisplayBrightnessChangeThresholdsIdle();
+ Thresholds ambientBrightnessThresholdsIdle =
+ config.getAmbientBrightnessChangeThresholdsIdle();
+ loadDisplayBrightnessThresholds(displayBrightnessThresholds);
+ loadAmbientBrightnessThresholds(ambientBrightnessThresholds);
+ loadIdleDisplayBrightnessThresholds(displayBrightnessThresholdsIdle);
+ loadIdleAmbientBrightnessThresholds(ambientBrightnessThresholdsIdle);
+ }
+
+ private void loadDisplayBrightnessThresholds(Thresholds displayBrightnessThresholds) {
if (displayBrightnessThresholds != null) {
BrightnessThresholds brighteningScreen =
displayBrightnessThresholds.getBrighteningThresholds();
BrightnessThresholds darkeningScreen =
displayBrightnessThresholds.getDarkeningThresholds();
- final BigDecimal screenBrighteningThreshold = brighteningScreen.getMinimum();
- final BigDecimal screenDarkeningThreshold = darkeningScreen.getMinimum();
-
- if (screenBrighteningThreshold != null) {
- mScreenBrighteningMinThreshold = screenBrighteningThreshold.floatValue();
+ if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
+ mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
}
- if (screenDarkeningThreshold != null) {
- mScreenDarkeningMinThreshold = screenDarkeningThreshold.floatValue();
+ if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
+ mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
}
}
+ }
+ private void loadAmbientBrightnessThresholds(Thresholds ambientBrightnessThresholds) {
if (ambientBrightnessThresholds != null) {
BrightnessThresholds brighteningAmbientLux =
ambientBrightnessThresholds.getBrighteningThresholds();
@@ -1412,6 +1503,44 @@
}
}
+ private void loadIdleDisplayBrightnessThresholds(Thresholds idleDisplayBrightnessThresholds) {
+ if (idleDisplayBrightnessThresholds != null) {
+ BrightnessThresholds brighteningScreenIdle =
+ idleDisplayBrightnessThresholds.getBrighteningThresholds();
+ BrightnessThresholds darkeningScreenIdle =
+ idleDisplayBrightnessThresholds.getDarkeningThresholds();
+
+ if (brighteningScreenIdle != null
+ && brighteningScreenIdle.getMinimum() != null) {
+ mScreenBrighteningMinThresholdIdle =
+ brighteningScreenIdle.getMinimum().floatValue();
+ }
+ if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
+ mScreenDarkeningMinThresholdIdle =
+ darkeningScreenIdle.getMinimum().floatValue();
+ }
+ }
+ }
+
+ private void loadIdleAmbientBrightnessThresholds(Thresholds idleAmbientBrightnessThresholds) {
+ if (idleAmbientBrightnessThresholds != null) {
+ BrightnessThresholds brighteningAmbientLuxIdle =
+ idleAmbientBrightnessThresholds.getBrighteningThresholds();
+ BrightnessThresholds darkeningAmbientLuxIdle =
+ idleAmbientBrightnessThresholds.getDarkeningThresholds();
+
+ if (brighteningAmbientLuxIdle != null
+ && brighteningAmbientLuxIdle.getMinimum() != null) {
+ mAmbientLuxBrighteningMinThresholdIdle =
+ brighteningAmbientLuxIdle.getMinimum().floatValue();
+ }
+ if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
+ mAmbientLuxDarkeningMinThresholdIdle =
+ darkeningAmbientLuxIdle.getMinimum().floatValue();
+ }
+ }
+ }
+
private boolean thermalStatusIsValid(ThermalStatus value) {
if (value == null) {
return false;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2deb056..8b5c311 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -985,6 +985,25 @@
screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
screenDarkeningMinThreshold, screenBrighteningMinThreshold);
+ // Idle screen thresholds
+ float screenDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+ float screenBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+ HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+ screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
+ screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+
+ // Idle ambient thresholds
+ float ambientDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
+ float ambientBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+ HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
+ ambientBrighteningThresholds, ambientDarkeningThresholds,
+ ambientThresholdLevels, ambientDarkeningMinThresholdIdle,
+ ambientBrighteningMinThresholdIdle);
+
long brighteningLightDebounce = mDisplayDeviceConfig
.getAutoBrightnessBrighteningLightDebounce();
long darkeningLightDebounce = mDisplayDeviceConfig
@@ -1020,7 +1039,8 @@
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
- ambientBrightnessThresholds, screenBrightnessThresholds, mContext,
+ ambientBrightnessThresholds, screenBrightnessThresholds,
+ ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong());
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 7a932ce..3413489 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -18,15 +18,12 @@
import android.util.Slog;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.io.PrintWriter;
import java.util.Arrays;
/**
* A helper class for handling access to illuminance hysteresis level values.
*/
-@VisibleForTesting
public class HysteresisLevels {
private static final String TAG = "HysteresisLevels";
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2a21928..efb2cb7 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -411,11 +411,8 @@
// For a new display, we need to initialize the default mode ID.
if (mDefaultModeId == INVALID_MODE_ID) {
- mDefaultModeId = mSystemPreferredModeId != INVALID_MODE_ID
- ? mSystemPreferredModeId : activeRecord.mMode.getModeId();
- mDefaultModeGroup = mSystemPreferredModeId != INVALID_MODE_ID
- ? preferredSfDisplayMode.group
- : mActiveSfDisplayMode.group;
+ mDefaultModeId = activeRecord.mMode.getModeId();
+ mDefaultModeGroup = mActiveSfDisplayMode.group;
} else if (modesAdded && activeModeChanged) {
Slog.d(TAG, "New display modes are added and the active mode has changed, "
+ "use active mode as default mode.");
@@ -897,6 +894,13 @@
public void setUserPreferredDisplayModeLocked(Display.Mode mode) {
final int oldModeId = getPreferredModeId();
mUserPreferredMode = mode;
+ // When clearing the user preferred mode we need to also reset the default mode. This is
+ // used by DisplayModeDirector to determine the default resolution, so if we don't clear
+ // it then the resolution won't reset to what it would've been prior to setting a user
+ // preferred display mode.
+ if (mode == null && mSystemPreferredModeId != INVALID_MODE_ID) {
+ mDefaultModeId = mSystemPreferredModeId;
+ }
if (mode != null && (mode.isRefreshRateSet() || mode.isResolutionSet())) {
Display.Mode matchingSupportedMode;
matchingSupportedMode = findMode(mode.getPhysicalWidth(),
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6538ee3..4c0a017 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1522,8 +1522,17 @@
this.task = newTask;
if (shouldStartChangeTransition(newParent, oldParent)) {
- // Animate change transition on TaskFragment level to get the correct window crop.
- newParent.initializeChangeTransition(getBounds(), getSurfaceControl());
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ // For Shell transition, call #initializeChangeTransition directly to take the
+ // screenshot at the Activity level. And Shell will be in charge of handling the
+ // surface reparent and crop.
+ initializeChangeTransition(getBounds());
+ } else {
+ // For legacy app transition, we want to take a screenshot of the Activity surface,
+ // but animate the change transition on TaskFragment level to get the correct window
+ // crop.
+ newParent.initializeChangeTransition(getBounds(), getSurfaceControl());
+ }
}
super.onParentChanged(newParent, oldParent);
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 1fd66fc..d497d8c 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -141,15 +141,16 @@
return false;
} else {
- mResultActivities.add(r);
if (r.resultTo != null) {
// If this activity is sending a reply to a previous activity, we can't do
// anything with it now until we reach the start of the reply chain.
// NOTE: that we are assuming the result is always to the previous activity,
// which is almost always the case but we really shouldn't count on.
+ mResultActivities.add(r);
return false;
} else if (mTargetTaskFound && allowTaskReparenting && mTargetTask.affinity != null
&& mTargetTask.affinity.equals(r.taskAffinity)) {
+ mResultActivities.add(r);
// This activity has an affinity for our task. Either remove it if we are
// clearing or move it over to our task. Note that we currently punt on the case
// where we are resetting a task that is not at the top but who has activities
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 6b05d8f..267cff6 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -97,6 +97,16 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <!-- Set of thresholds that dictate the change needed for screen brightness
+ adaptations while in idle mode -->
+ <xs:element type="thresholds" name="displayBrightnessChangeThresholdsIdle" minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Set of thresholds that dictate the change needed for ambient brightness
+ adaptations while in idle mode -->
+ <xs:element type="thresholds" name="ambientBrightnessChangeThresholdsIdle" minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -319,13 +329,13 @@
<!-- Thresholds for brightness changes. -->
<xs:complexType name="thresholds">
<xs:sequence>
- <!-- Brightening thresholds. -->
+ <!-- Brightening thresholds for active screen brightness mode. -->
<xs:element name="brighteningThresholds" type="brightnessThresholds" minOccurs="0"
maxOccurs="1" >
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
- <!-- Darkening thresholds. -->
+ <!-- Darkening thresholds for active screen brightness mode. -->
<xs:element name="darkeningThresholds" type="brightnessThresholds" minOccurs="0"
maxOccurs="1" >
<xs:annotation name="nonnull"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index fb7a920..f8bff75 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -61,11 +61,13 @@
public class DisplayConfiguration {
ctor public DisplayConfiguration();
method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
+ method public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholdsIdle();
method public final java.math.BigInteger getAmbientLightHorizonLong();
method public final java.math.BigInteger getAmbientLightHorizonShort();
method public com.android.server.display.config.AutoBrightness getAutoBrightness();
method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
+ method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
method public final com.android.server.display.config.SensorDetails getLightSensor();
method public final com.android.server.display.config.SensorDetails getProxSensor();
@@ -80,11 +82,13 @@
method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling();
method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
+ method public final void setAmbientBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
method public void setAutoBrightness(com.android.server.display.config.AutoBrightness);
method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
+ method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
method public final void setProxSensor(com.android.server.display.config.SensorDetails);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 9c615d1..ed369c0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -764,11 +764,13 @@
@Test
public void testGetSystemPreferredDisplayMode() throws Exception {
SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f);
- // preferred mode
+ // system preferred mode
SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f);
+ // user preferred mode
+ SurfaceControl.DisplayMode displayMode3 = createFakeDisplayMode(2, 1920, 1080, 30f);
SurfaceControl.DisplayMode[] modes =
- new SurfaceControl.DisplayMode[]{displayMode1, displayMode2};
+ new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, displayMode3};
FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1);
setUpDisplay(display);
updateAvailableDisplays();
@@ -780,24 +782,43 @@
DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
0).getDisplayDeviceInfoLocked();
-
assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-
Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode1)).isTrue();
+
+ // Set the user preferred display mode
+ mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
+ new Display.Mode(
+ displayMode3.width, displayMode3.height, displayMode3.refreshRate));
+ updateAvailableDisplays();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ displayDeviceInfo = mListener.addedDisplays.get(
+ 0).getDisplayDeviceInfoLocked();
+ defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode3)).isTrue();
+
+ // clear the user preferred mode
+ mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(null);
+ updateAvailableDisplays();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ displayDeviceInfo = mListener.addedDisplays.get(
+ 0).getDisplayDeviceInfoLocked();
+ defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
assertThat(matches(defaultMode, displayMode2)).isTrue();
- // Change the display and add new preferred mode
- SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(2, 2340, 1080, 60f);
- modes = new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, addedDisplayInfo};
+ // Change the display and add new system preferred mode
+ SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3, 2340, 1080, 20f);
+ modes = new SurfaceControl.DisplayMode[]{
+ displayMode1, displayMode2, displayMode3, addedDisplayInfo};
display.dynamicInfo.supportedDisplayModes = modes;
- display.dynamicInfo.preferredBootDisplayMode = 2;
+ display.dynamicInfo.preferredBootDisplayMode = 3;
setUpDisplay(display);
mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
assertTrue(mListener.traversalRequested);
assertThat(mListener.addedDisplays.size()).isEqualTo(1);
- assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(3);
DisplayDevice displayDevice = mListener.changedDisplays.get(0);
displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 9d82f1a..8280fc6 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -82,6 +82,8 @@
@Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
@Mock HysteresisLevels mAmbientBrightnessThresholds;
@Mock HysteresisLevels mScreenBrightnessThresholds;
+ @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
+ @Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
@Mock Handler mNoOpHandler;
@Mock HighBrightnessModeController mHbmController;
@Mock BrightnessThrottler mBrightnessThrottler;
@@ -129,6 +131,7 @@
INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
+ mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG
);
@@ -314,8 +317,9 @@
// Now let's do the same for idle mode
mController.switchToIdleMode();
- // Called once for init, and once when switching
- verify(mBrightnessMappingStrategy, times(2)).isForIdleMode();
+ // Called once for init, and once when switching,
+ // setAmbientLux() is called twice and once in updateAutoBrightness()
+ verify(mBrightnessMappingStrategy, times(5)).isForIdleMode();
// Ensure, after switching, original BMS is not used anymore
verifyNoMoreInteractions(mBrightnessMappingStrategy);