Merge "Fix the overrides file corruption due to shutdown"
diff --git a/core/api/current.txt b/core/api/current.txt
index 4124ede..a1f6352 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -31579,12 +31579,15 @@
method public int readInt();
method public void readIntArray(@NonNull int[]);
method public void readList(@NonNull java.util.List, @Nullable ClassLoader);
+ method public <T> void readList(@NonNull java.util.List<? super T>, @Nullable ClassLoader, @NonNull Class<T>);
method public long readLong();
method public void readLongArray(@NonNull long[]);
method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
+ method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
+ method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
method @Nullable public android.os.PersistableBundle readPersistableBundle();
method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1ab6e97..bd43ab5 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1780,6 +1780,12 @@
}
}
try {
+ ActivityThread thread = ActivityThread.currentActivityThread();
+ Instrumentation instrumentation = thread.getInstrumentation();
+ if (instrumentation.isInstrumenting()
+ && ((flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
+ flags = flags | Context.RECEIVER_EXPORTED;
+ }
final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index a6e77023..4ecd15e 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -66,7 +66,17 @@
public abstract class AbstractInputMethodService extends WindowProviderService
implements KeyEvent.Callback {
private InputMethod mInputMethod;
-
+
+ /**
+ * Keep the strong reference to {@link InputMethodServiceInternal} to ensure that it will not be
+ * garbage-collected until {@link AbstractInputMethodService} gets garbage-collected.
+ *
+ * <p>This is necessary because {@link RemoteInputConnection} internally uses
+ * {@link java.lang.ref.WeakReference} to hold {@link InputMethodServiceInternal}.</p>
+ */
+ @Nullable
+ private InputMethodServiceInternal mInputMethodServiceInternal;
+
final KeyEvent.DispatcherState mDispatcherState
= new KeyEvent.DispatcherState();
@@ -225,7 +235,10 @@
if (mInputMethod == null) {
mInputMethod = onCreateInputMethodInterface();
}
- return new IInputMethodWrapper(createInputMethodServiceInternal(), mInputMethod);
+ if (mInputMethodServiceInternal == null) {
+ mInputMethodServiceInternal = createInputMethodServiceInternal();
+ }
+ return new IInputMethodWrapper(mInputMethodServiceInternal, mInputMethod);
}
/**
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 64d54b8..7ce8d72 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -1048,7 +1048,7 @@
*/
char getChar(String key, char defaultValue) {
unparcel();
- Object o = getValue(key);
+ Object o = mMap.get(key);
if (o == null) {
return defaultValue;
}
@@ -1451,7 +1451,7 @@
@Nullable
short[] getShortArray(@Nullable String key) {
unparcel();
- Object o = getValue(key);
+ Object o = mMap.get(key);
if (o == null) {
return null;
}
@@ -1474,7 +1474,7 @@
@Nullable
char[] getCharArray(@Nullable String key) {
unparcel();
- Object o = getValue(key);
+ Object o = mMap.get(key);
if (o == null) {
return null;
}
@@ -1543,7 +1543,7 @@
@Nullable
float[] getFloatArray(@Nullable String key) {
unparcel();
- Object o = getValue(key);
+ Object o = mMap.get(key);
if (o == null) {
return null;
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 47c7f2e..bd36772 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -285,6 +285,10 @@
private static final int VAL_SIZE = 26;
private static final int VAL_SIZEF = 27;
private static final int VAL_DOUBLEARRAY = 28;
+ private static final int VAL_CHAR = 29;
+ private static final int VAL_SHORTARRAY = 30;
+ private static final int VAL_CHARARRAY = 31;
+ private static final int VAL_FLOATARRAY = 32;
// The initial int32 in a Binder call's reply Parcel header:
// Keep these in sync with libbinder's binder/Status.h.
@@ -1356,6 +1360,46 @@
}
}
+ /** @hide */
+ public void writeShortArray(@Nullable short[] val) {
+ if (val != null) {
+ int n = val.length;
+ writeInt(n);
+ for (int i = 0; i < n; i++) {
+ writeInt(val[i]);
+ }
+ } else {
+ writeInt(-1);
+ }
+ }
+
+ /** @hide */
+ @Nullable
+ public short[] createShortArray() {
+ int n = readInt();
+ if (n >= 0 && n <= (dataAvail() >> 2)) {
+ short[] val = new short[n];
+ for (int i = 0; i < n; i++) {
+ val[i] = (short) readInt();
+ }
+ return val;
+ } else {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public void readShortArray(@NonNull short[] val) {
+ int n = readInt();
+ if (n == val.length) {
+ for (int i = 0; i < n; i++) {
+ val[i] = (short) readInt();
+ }
+ } else {
+ throw new RuntimeException("bad array lengths");
+ }
+ }
+
public final void writeCharArray(@Nullable char[] val) {
if (val != null) {
int N = val.length;
@@ -2021,6 +2065,14 @@
return VAL_SIZE;
} else if (v instanceof double[]) {
return VAL_DOUBLEARRAY;
+ } else if (v instanceof Character) {
+ return VAL_CHAR;
+ } else if (v instanceof short[]) {
+ return VAL_SHORTARRAY;
+ } else if (v instanceof char[]) {
+ return VAL_CHARARRAY;
+ } else if (v instanceof float[]) {
+ return VAL_FLOATARRAY;
} else {
Class<?> clazz = v.getClass();
if (clazz.isArray() && clazz.getComponentType() == Object.class) {
@@ -2123,6 +2175,18 @@
case VAL_DOUBLEARRAY:
writeDoubleArray((double[]) v);
break;
+ case VAL_CHAR:
+ writeInt((Character) v);
+ break;
+ case VAL_SHORTARRAY:
+ writeShortArray((short[]) v);
+ break;
+ case VAL_CHARARRAY:
+ writeCharArray((char[]) v);
+ break;
+ case VAL_FLOATARRAY:
+ writeFloatArray((float[]) v);
+ break;
case VAL_OBJECTARRAY:
writeArray((Object[]) v);
break;
@@ -2807,7 +2871,20 @@
*/
public final void readList(@NonNull List outVal, @Nullable ClassLoader loader) {
int N = readInt();
- readListInternal(outVal, N, loader);
+ readListInternal(outVal, N, loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readList(List, ClassLoader)} but accepts {@code clazz} parameter as
+ * the type required for each item. If the item to be deserialized is not an instance
+ * of that class or any of its children class
+ * a {@link BadParcelableException} will be thrown.
+ */
+ public <T> void readList(@NonNull List<? super T> outVal,
+ @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ Objects.requireNonNull(clazz);
+ int n = readInt();
+ readListInternal(outVal, n, loader, clazz);
}
/**
@@ -3006,7 +3083,7 @@
return null;
}
ArrayList l = new ArrayList(N);
- readListInternal(l, N, loader);
+ readListInternal(l, N, loader, /* clazz */ null);
return l;
}
@@ -3406,12 +3483,21 @@
*/
@Nullable
public final Object readValue(@Nullable ClassLoader loader) {
+ return readValue(loader, /* clazz */ null);
+ }
+
+
+ /**
+ * @param clazz The type of the object expected or {@code null} for performing no checks.
+ */
+ @Nullable
+ private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
int type = readInt();
- final Object object;
+ final T object;
if (isLengthPrefixed(type)) {
int length = readInt();
int start = dataPosition();
- object = readValue(type, loader);
+ object = readValue(type, loader, clazz);
int actual = dataPosition() - start;
if (actual != length) {
Slog.wtfStack(TAG,
@@ -3419,7 +3505,7 @@
+ " consumed " + actual + " bytes, but " + length + " expected.");
}
} else {
- object = readValue(type, loader);
+ object = readValue(type, loader, clazz);
}
return object;
}
@@ -3456,7 +3542,7 @@
setDataPosition(MathUtils.addOrThrow(dataPosition(), length));
return new LazyValue(this, start, length, type, loader);
} else {
- return readValue(type, loader);
+ return readValue(type, loader, /* clazz */ null);
}
}
@@ -3588,114 +3674,175 @@
/**
* Reads a value from the parcel of type {@code type}. Does NOT read the int representing the
* type first.
+ * @param clazz The type of the object expected or {@code null} for performing no checks.
*/
+ @SuppressWarnings("unchecked")
@Nullable
- private Object readValue(int type, @Nullable ClassLoader loader) {
+ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ final Object object;
switch (type) {
- case VAL_NULL:
- return null;
+ case VAL_NULL:
+ object = null;
+ break;
- case VAL_STRING:
- return readString();
+ case VAL_STRING:
+ object = readString();
+ break;
- case VAL_INTEGER:
- return readInt();
+ case VAL_INTEGER:
+ object = readInt();
+ break;
- case VAL_MAP:
- return readHashMap(loader);
+ case VAL_MAP:
+ object = readHashMap(loader);
+ break;
- case VAL_PARCELABLE:
- return readParcelable(loader);
+ case VAL_PARCELABLE:
+ object = readParcelableInternal(loader, clazz);
+ break;
- case VAL_SHORT:
- return (short) readInt();
+ case VAL_SHORT:
+ object = (short) readInt();
+ break;
- case VAL_LONG:
- return readLong();
+ case VAL_LONG:
+ object = readLong();
+ break;
- case VAL_FLOAT:
- return readFloat();
+ case VAL_FLOAT:
+ object = readFloat();
+ break;
- case VAL_DOUBLE:
- return readDouble();
+ case VAL_DOUBLE:
+ object = readDouble();
+ break;
- case VAL_BOOLEAN:
- return readInt() == 1;
+ case VAL_BOOLEAN:
+ object = readInt() == 1;
+ break;
- case VAL_CHARSEQUENCE:
- return readCharSequence();
+ case VAL_CHARSEQUENCE:
+ object = readCharSequence();
+ break;
- case VAL_LIST:
- return readArrayList(loader);
+ case VAL_LIST:
+ object = readArrayList(loader);
+ break;
- case VAL_BOOLEANARRAY:
- return createBooleanArray();
+ case VAL_BOOLEANARRAY:
+ object = createBooleanArray();
+ break;
- case VAL_BYTEARRAY:
- return createByteArray();
+ case VAL_BYTEARRAY:
+ object = createByteArray();
+ break;
- case VAL_STRINGARRAY:
- return readStringArray();
+ case VAL_STRINGARRAY:
+ object = readStringArray();
+ break;
- case VAL_CHARSEQUENCEARRAY:
- return readCharSequenceArray();
+ case VAL_CHARSEQUENCEARRAY:
+ object = readCharSequenceArray();
+ break;
- case VAL_IBINDER:
- return readStrongBinder();
+ case VAL_IBINDER:
+ object = readStrongBinder();
+ break;
- case VAL_OBJECTARRAY:
- return readArray(loader);
+ case VAL_OBJECTARRAY:
+ object = readArray(loader);
+ break;
- case VAL_INTARRAY:
- return createIntArray();
+ case VAL_INTARRAY:
+ object = createIntArray();
+ break;
- case VAL_LONGARRAY:
- return createLongArray();
+ case VAL_LONGARRAY:
+ object = createLongArray();
+ break;
- case VAL_BYTE:
- return readByte();
+ case VAL_BYTE:
+ object = readByte();
+ break;
- case VAL_SERIALIZABLE:
- return readSerializable(loader);
+ case VAL_SERIALIZABLE:
+ object = readSerializable(loader);
+ break;
- case VAL_PARCELABLEARRAY:
- return readParcelableArray(loader);
+ case VAL_PARCELABLEARRAY:
+ object = readParcelableArray(loader);
+ break;
- case VAL_SPARSEARRAY:
- return readSparseArray(loader);
+ case VAL_SPARSEARRAY:
+ object = readSparseArray(loader);
+ break;
- case VAL_SPARSEBOOLEANARRAY:
- return readSparseBooleanArray();
+ case VAL_SPARSEBOOLEANARRAY:
+ object = readSparseBooleanArray();
+ break;
- case VAL_BUNDLE:
- return readBundle(loader); // loading will be deferred
+ case VAL_BUNDLE:
+ object = readBundle(loader); // loading will be deferred
+ break;
- case VAL_PERSISTABLEBUNDLE:
- return readPersistableBundle(loader);
+ case VAL_PERSISTABLEBUNDLE:
+ object = readPersistableBundle(loader);
+ break;
- case VAL_SIZE:
- return readSize();
+ case VAL_SIZE:
+ object = readSize();
+ break;
- case VAL_SIZEF:
- return readSizeF();
+ case VAL_SIZEF:
+ object = readSizeF();
+ break;
- case VAL_DOUBLEARRAY:
- return createDoubleArray();
+ case VAL_DOUBLEARRAY:
+ object = createDoubleArray();
+ break;
- default:
- int off = dataPosition() - 4;
- throw new RuntimeException(
- "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);
+ case VAL_CHAR:
+ object = (char) readInt();
+ break;
+
+ case VAL_SHORTARRAY:
+ object = createShortArray();
+ break;
+
+ case VAL_CHARARRAY:
+ object = createCharArray();
+ break;
+
+ case VAL_FLOATARRAY:
+ object = createFloatArray();
+ break;
+
+ default:
+ int off = dataPosition() - 4;
+ throw new RuntimeException(
+ "Parcel " + this + ": Unmarshalling unknown type code " + type
+ + " at offset " + off);
}
+ if (clazz != null && !clazz.isInstance(object)) {
+ throw new BadParcelableException("Unparcelled object " + object
+ + " is not an instance of required class " + clazz.getName()
+ + " provided in the parameter");
+ }
+ return (T) object;
}
private boolean isLengthPrefixed(int type) {
+ // In general, we want custom types and containers of custom types to be length-prefixed,
+ // this allows clients (eg. Bundle) to skip their content during deserialization. The
+ // exception to this is Bundle, since Bundle is already length-prefixed and already copies
+ // the correspondent section of the parcel internally.
switch (type) {
+ case VAL_MAP:
case VAL_PARCELABLE:
- case VAL_PARCELABLEARRAY:
case VAL_LIST:
case VAL_SPARSEARRAY:
- case VAL_BUNDLE:
+ case VAL_PARCELABLEARRAY:
+ case VAL_OBJECTARRAY:
case VAL_SERIALIZABLE:
return true;
default:
@@ -3714,17 +3861,42 @@
* @throws BadParcelableException Throws BadParcelableException if there
* was an error trying to instantiate the Parcelable.
*/
- @SuppressWarnings("unchecked")
@Nullable
public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {
- Parcelable.Creator<?> creator = readParcelableCreator(loader);
+ return readParcelableInternal(loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readParcelable(ClassLoader)} but accepts {@code clazz} parameter as the type
+ * required for each item. If the item to be deserialized is not an instance of that class or
+ * any of its children classes a {@link BadParcelableException} will be thrown.
+ */
+ @Nullable
+ public <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader,
+ @NonNull Class<T> clazz) {
+ Objects.requireNonNull(clazz);
+ return readParcelableInternal(loader, clazz);
+ }
+
+ /**
+ *
+ * @param clazz The type of the parcelable expected or {@code null} for performing no checks.
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private <T> T readParcelableInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ if (clazz != null && !Parcelable.class.isAssignableFrom(clazz)) {
+ throw new BadParcelableException("About to unparcel a parcelable object "
+ + " but class required " + clazz.getName() + " is not Parcelable");
+ }
+ Parcelable.Creator<?> creator = readParcelableCreatorInternal(loader, clazz);
if (creator == null) {
return null;
}
if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
- Parcelable.ClassLoaderCreator<?> classLoaderCreator =
- (Parcelable.ClassLoaderCreator<?>) creator;
- return (T) classLoaderCreator.createFromParcel(this, loader);
+ Parcelable.ClassLoaderCreator<?> classLoaderCreator =
+ (Parcelable.ClassLoaderCreator<?>) creator;
+ return (T) classLoaderCreator.createFromParcel(this, loader);
}
return (T) creator.createFromParcel(this);
}
@@ -3758,6 +3930,28 @@
*/
@Nullable
public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
+ return readParcelableCreatorInternal(loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readParcelableCreator(ClassLoader)} but accepts {@code clazz} parameter
+ * as the required type. If the item to be deserialized is not an instance of that class
+ * or any of its children classes a {@link BadParcelableException} will be thrown.
+ */
+ @Nullable
+ public <T> Parcelable.Creator<T> readParcelableCreator(
+ @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ Objects.requireNonNull(clazz);
+ return readParcelableCreatorInternal(loader, clazz);
+ }
+
+ /**
+ * @param clazz The type of the parcelable expected or {@code null} for performing no checks.
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private <T> Parcelable.Creator<T> readParcelableCreatorInternal(
+ @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
String name = readString();
if (name == null) {
return null;
@@ -3773,7 +3967,15 @@
creator = map.get(name);
}
if (creator != null) {
- return creator;
+ if (clazz != null) {
+ Class<?> parcelableClass = creator.getClass().getEnclosingClass();
+ if (!clazz.isAssignableFrom(parcelableClass)) {
+ throw new BadParcelableException("Parcelable creator " + name + " is not "
+ + "a subclass of required class " + clazz.getName()
+ + " provided in the parameter");
+ }
+ }
+ return (Parcelable.Creator<T>) creator;
}
try {
@@ -3789,6 +3991,14 @@
throw new BadParcelableException("Parcelable protocol requires subclassing "
+ "from Parcelable on class " + name);
}
+ if (clazz != null) {
+ if (!clazz.isAssignableFrom(parcelableClass)) {
+ throw new BadParcelableException("Parcelable creator " + name + " is not "
+ + "a subclass of required class " + clazz.getName()
+ + " provided in the parameter");
+ }
+ }
+
Field f = parcelableClass.getField("CREATOR");
if ((f.getModifiers() & Modifier.STATIC) == 0) {
throw new BadParcelableException("Parcelable protocol requires "
@@ -3826,7 +4036,7 @@
map.put(name, creator);
}
- return creator;
+ return (Parcelable.Creator<T>) creator;
}
/**
@@ -4076,13 +4286,21 @@
return result;
}
- private void readListInternal(@NonNull List outVal, int N,
+ private void readListInternal(@NonNull List outVal, int n,
@Nullable ClassLoader loader) {
- while (N > 0) {
- Object value = readValue(loader);
+ readListInternal(outVal, n, loader, null);
+ }
+
+ /**
+ * @param clazz The type of the object expected or {@code null} for performing no checks.
+ */
+ private <T> void readListInternal(@NonNull List<? super T> outVal, int n,
+ @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ while (n > 0) {
+ T value = readValue(loader, clazz);
//Log.d(TAG, "Unmarshalling value=" + value);
outVal.add(value);
- N--;
+ n--;
}
}
@@ -4161,6 +4379,10 @@
case VAL_SIZE: return "VAL_SIZE";
case VAL_SIZEF: return "VAL_SIZEF";
case VAL_DOUBLEARRAY: return "VAL_DOUBLEARRAY";
+ case VAL_CHAR: return "VAL_CHAR";
+ case VAL_SHORTARRAY: return "VAL_SHORTARRAY";
+ case VAL_CHARARRAY: return "VAL_CHARARRAY";
+ case VAL_FLOATARRAY: return "VAL_FLOATARRAY";
case VAL_OBJECTARRAY: return "VAL_OBJECTARRAY";
case VAL_SERIALIZABLE: return "VAL_SERIALIZABLE";
default: return "UNKNOWN(" + type + ")";
diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java
index bd3115fc5..2143bc1 100644
--- a/core/java/com/android/internal/os/ProcLocksReader.java
+++ b/core/java/com/android/internal/os/ProcLocksReader.java
@@ -18,8 +18,6 @@
import com.android.internal.util.ProcFileReader;
-import libcore.io.IoUtils;
-
import java.io.FileInputStream;
import java.io.IOException;
@@ -36,6 +34,7 @@
*/
public class ProcLocksReader {
private final String mPath;
+ private ProcFileReader mReader = null;
public ProcLocksReader() {
mPath = "/proc/locks";
@@ -46,44 +45,54 @@
}
/**
- * Checks if a process corresponding to a specific pid owns any file locks.
- * @param pid The process ID for which we want to know the existence of file locks.
- * @return true If the process holds any file locks, false otherwise.
- * @throws IOException if /proc/locks can't be accessed.
+ * This interface is for AMS to run callback function on every processes one by one
+ * that hold file locks blocking other processes.
*/
- public boolean hasFileLocks(int pid) throws Exception {
- ProcFileReader reader = null;
+ public interface ProcLocksReaderCallback {
+ /**
+ * Call the callback function of handleBlockingFileLocks().
+ * @param pid Each process that hold file locks blocking other processes.
+ */
+ void onBlockingFileLock(int pid);
+ }
+
+ /**
+ * Checks if a process corresponding to a specific pid owns any file locks.
+ * @param callback Callback function, accepting pid as the input parameter.
+ * @throws IOException if /proc/locks can't be accessed or correctly parsed.
+ */
+ public void handleBlockingFileLocks(ProcLocksReaderCallback callback) throws IOException {
long last = -1;
long id; // ordinal position of the lock in the list
- int owner; // the PID of the process that owns the lock
+ int owner = -1; // the PID of the process that owns the lock
+ int pid = -1; // the PID of the process blocking others
- try {
- reader = new ProcFileReader(new FileInputStream(mPath));
-
- while (reader.hasMoreData()) {
- id = reader.nextLong(true); // lock id
- if (id == last) {
- reader.finishLine(); // blocked lock
- continue;
- }
-
- reader.nextIgnored(); // lock type: POSIX?
- reader.nextIgnored(); // lock type: MANDATORY?
- reader.nextIgnored(); // lock type: RW?
-
- owner = reader.nextInt(); // pid
- if (owner == pid) {
- return true;
- }
- reader.finishLine();
- last = id;
- }
- } catch (IOException e) {
- // TODO: let ProcFileReader log the failed line
- throw new Exception("Exception parsing /proc/locks");
- } finally {
- IoUtils.closeQuietly(reader);
+ if (mReader == null) {
+ mReader = new ProcFileReader(new FileInputStream(mPath));
+ } else {
+ mReader.rewind();
}
- return false;
+
+ while (mReader.hasMoreData()) {
+ id = mReader.nextLong(true); // lock id
+ if (id == last) {
+ mReader.finishLine(); // blocked lock
+ if (pid < 0) {
+ pid = owner; // get pid from the previous line
+ callback.onBlockingFileLock(pid);
+ }
+ continue;
+ } else {
+ pid = -1; // a new lock
+ }
+
+ mReader.nextIgnored(); // lock type: POSIX?
+ mReader.nextIgnored(); // lock type: MANDATORY?
+ mReader.nextIgnored(); // lock type: RW?
+
+ owner = mReader.nextInt(); // pid
+ mReader.finishLine();
+ last = id;
+ }
}
}
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
index 0dd8ad8..b726d5d 100644
--- a/core/java/com/android/internal/util/ProcFileReader.java
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -17,6 +17,7 @@
package com.android.internal.util;
import java.io.Closeable;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ProtocolException;
@@ -47,6 +48,9 @@
public ProcFileReader(InputStream stream, int bufferSize) throws IOException {
mStream = stream;
mBuffer = new byte[bufferSize];
+ if (stream.markSupported()) {
+ mStream.mark(0);
+ }
// read enough to answer hasMoreData
fillBuf();
@@ -257,6 +261,24 @@
}
}
+ /**
+ * Reset file position and internal buffer
+ * @throws IOException
+ */
+ public void rewind() throws IOException {
+ if (mStream instanceof FileInputStream) {
+ ((FileInputStream) mStream).getChannel().position(0);
+ } else if (mStream.markSupported()) {
+ mStream.reset();
+ } else {
+ throw new IOException("The InputStream is NOT markable");
+ }
+
+ mTail = 0;
+ mLineFinished = false;
+ fillBuf();
+ }
+
@Override
public void close() throws IOException {
mStream.close();
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c6c4e69..f1a961d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4925,16 +4925,15 @@
device orientation. -->
<bool name="config_letterboxIsReachabilityEnabled">false</bool>
- <!-- Default horizonal position of a center of the letterboxed app window when reachability is
- enabled and an app is fullscreen in landscape device orientation.
- 0 corresponds to the left side of the screen and 1 to the right side. If given value < 0.0
- or > 1, it is ignored and right positionis used (1.0). The position multiplier is changed
- to a symmetrical value computed as (1 - current multiplier) after each double tap in the
- letterbox area. -->
- <item name="config_letterboxDefaultPositionMultiplierForReachability"
- format="float" type="dimen">
- 0.9
- </item>
+ <!-- Default horizonal position of the letterboxed app window when reachability is
+ enabled and an app is fullscreen in landscape device orientation. When reachability is
+ enabled, the position can change between left, center and right. This config defines the
+ default one:
+ - Option 0 - Left.
+ - Option 1 - Center.
+ - Option 2 - Right.
+ If given value is outside of this range, the option 1 (center) is assummed. -->
+ <integer name="config_letterboxDefaultPositionForReachability">1</integer>
<!-- If true, hide the display cutout with display area -->
<bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4ad87da..0587474 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4265,7 +4265,7 @@
<java-symbol type="color" name="config_letterboxBackgroundColor" />
<java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
<java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
- <java-symbol type="dimen" name="config_letterboxDefaultPositionMultiplierForReachability" />
+ <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index d800c2c..b34554c 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -16,7 +16,6 @@
package com.android.internal.os;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -33,11 +32,14 @@
import java.io.File;
import java.nio.file.Files;
+import java.util.ArrayList;
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class ProcLocksReaderTest {
+public class ProcLocksReaderTest implements
+ ProcLocksReader.ProcLocksReaderCallback {
private File mProcDirectory;
+ private ArrayList<Integer> mPids = new ArrayList<>();
@Before
public void setUp() {
@@ -55,14 +57,12 @@
String simpleLocks =
"1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
"2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n";
- assertFalse(runHasFileLocks(simpleLocks, 18402));
- assertFalse(runHasFileLocks(simpleLocks, 18404));
- assertTrue(runHasFileLocks(simpleLocks, 18403));
- assertTrue(runHasFileLocks(simpleLocks, 18292));
+ runHandleBlockingFileLocks(simpleLocks);
+ assertTrue(mPids.isEmpty());
}
@Test
- public void testRunBlockedLocks() throws Exception {
+ public void testRunBlockingLocks() throws Exception {
String blockedLocks =
"1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
"2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" +
@@ -70,21 +70,43 @@
"2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" +
"3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" +
"4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
- assertFalse(runHasFileLocks(blockedLocks, 18402));
- assertFalse(runHasFileLocks(blockedLocks, 18404));
- assertTrue(runHasFileLocks(blockedLocks, 18403));
- assertTrue(runHasFileLocks(blockedLocks, 18292));
-
- assertFalse(runHasFileLocks(blockedLocks, 18291));
- assertFalse(runHasFileLocks(blockedLocks, 18293));
- assertTrue(runHasFileLocks(blockedLocks, 3888));
+ runHandleBlockingFileLocks(blockedLocks);
+ assertTrue(mPids.remove(0).equals(18292));
+ assertTrue(mPids.isEmpty());
}
- private boolean runHasFileLocks(String fileContents, int pid) throws Exception {
+ @Test
+ public void testRunMultipleBlockingLocks() throws Exception {
+ String blockedLocks =
+ "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
+ "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" +
+ "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" +
+ "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" +
+ "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" +
+ "4: FLOCK ADVISORY WRITE 3840 fe:01:5111809 0 EOF\n" +
+ "4: -> FLOCK ADVISORY WRITE 3841 fe:01:5111809 0 EOF\n" +
+ "5: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
+ runHandleBlockingFileLocks(blockedLocks);
+ assertTrue(mPids.remove(0).equals(18292));
+ assertTrue(mPids.remove(0).equals(3840));
+ assertTrue(mPids.isEmpty());
+ }
+
+ private void runHandleBlockingFileLocks(String fileContents) throws Exception {
File tempFile = File.createTempFile("locks", null, mProcDirectory);
Files.write(tempFile.toPath(), fileContents.getBytes());
- boolean result = new ProcLocksReader(tempFile.toString()).hasFileLocks(pid);
+ mPids.clear();
+ new ProcLocksReader(tempFile.toString()).handleBlockingFileLocks(this);
Files.delete(tempFile.toPath());
- return result;
+ }
+
+ /**
+ * Call the callback function of handleBlockingFileLocks().
+ *
+ * @param pid Each process that hold file locks blocking other processes.
+ */
+ @Override
+ public void onBlockingFileLock(int pid) {
+ mPids.add(pid);
}
}
diff --git a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
index 0532628..b932760 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
@@ -19,8 +19,11 @@
import android.test.AndroidTestCase;
import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
/**
* Tests for {@link ProcFileReader}.
@@ -206,6 +209,41 @@
assertFalse(reader.hasMoreData());
}
+ public void testRewind() throws Exception {
+ final ProcFileReader reader = buildReader("abc\n");
+
+ assertEquals("abc", reader.nextString());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+
+ reader.rewind();
+ assertTrue(reader.hasMoreData());
+
+ assertEquals("abc", reader.nextString());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+
+ public void testRewindFileInputStream() throws Exception {
+ File tempFile = File.createTempFile("procfile", null, null);
+ Files.write(tempFile.toPath(), "abc\n".getBytes(StandardCharsets.US_ASCII));
+ final ProcFileReader reader = new ProcFileReader(new FileInputStream(tempFile));
+
+ assertEquals("abc", reader.nextString());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+
+ reader.rewind();
+ assertTrue(reader.hasMoreData());
+
+ assertEquals("abc", reader.nextString());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+
+ Files.delete(tempFile.toPath());
+ }
+
private static ProcFileReader buildReader(String string) throws IOException {
return buildReader(string, 2048);
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 791aeb7..909ca39 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3505,6 +3505,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1805116444": {
+ "message": "We don't support remote animation for Task with multiple TaskFragmentOrganizers.",
+ "level": "ERROR",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"1810019902": {
"message": "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
"level": "DEBUG",
diff --git a/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml b/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml
new file mode 100644
index 0000000..08c66a2
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondaryVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 6fd83c5..6e89fb0 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -17,8 +17,7 @@
*/
-->
-<resources
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Keyguard PIN pad styles -->
<style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
@@ -59,11 +58,11 @@
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="NumPadKey.Delete">
- <item name="android:colorControlNormal">?androidprv:attr/colorAccentSecondaryVariant</item>
+ <item name="android:colorControlNormal">@color/numpad_key_color_secondary</item>
<item name="android:src">@drawable/ic_backspace_24dp</item>
</style>
<style name="NumPadKey.Enter">
- <item name="android:colorControlNormal">?androidprv:attr/colorAccentSecondaryVariant</item>
+ <item name="android:colorControlNormal">@color/numpad_key_color_secondary</item>
<item name="android:src">@drawable/ic_keyboard_tab_36dp</item>
</style>
<style name="Widget.TextView.NumPadKey.Klondike"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f82eb11..a58e12f 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -29,12 +29,6 @@
<include layout="@layout/communal_host_view"
android:visibility="gone"/>
- <FrameLayout
- android:id="@+id/big_clock_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone" />
-
<ViewStub
android:id="@+id/keyguard_qs_user_switch_stub"
android:layout="@layout/keyguard_qs_user_switch"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 71d2a73..fa616921 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -20,6 +20,7 @@
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -34,9 +35,13 @@
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.view.Display;
import android.view.InsetsVisibilities;
import android.view.View;
import android.view.WindowInsetsController.Behavior;
@@ -55,7 +60,8 @@
@Singleton
public class TaskbarDelegate implements CommandQueue.Callbacks,
- OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener {
+ OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
+ ComponentCallbacks {
private final EdgeBackGestureHandler mEdgeBackGestureHandler;
@@ -71,11 +77,16 @@
private int mDisabledFlags;
private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING;
private @Behavior int mBehavior;
+ private final Context mContext;
+ private final DisplayManager mDisplayManager;
+ private Context mWindowContext;
@Inject
public TaskbarDelegate(Context context) {
mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
.create(context);
+ mContext = context;
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
}
public void setOverviewProxyService(CommandQueue commandQueue,
@@ -97,6 +108,10 @@
mNavigationModeController.removeListener(this);
mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
mEdgeBackGestureHandler.onNavBarDetached();
+ if (mWindowContext != null) {
+ mWindowContext.unregisterComponentCallbacks(this);
+ mWindowContext = null;
+ }
}
public void init(int displayId) {
@@ -107,6 +122,10 @@
mNavigationModeController.addListener(this));
mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener);
mEdgeBackGestureHandler.onNavBarAttached();
+ // Initialize component callback
+ Display display = mDisplayManager.getDisplay(displayId);
+ mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
+ mWindowContext.registerComponentCallbacks(this);
// Set initial state for any listeners
updateSysuiFlags();
}
@@ -193,4 +212,12 @@
private boolean allowSystemGestureIgnoringBarVisibility() {
return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ mEdgeBackGestureHandler.onConfigurationChanged(configuration);
+ }
+
+ @Override
+ public void onLowMemory() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index b053b5d..d7f3225 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -45,6 +45,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
import java.lang.RuntimeException
@@ -67,6 +68,7 @@
private val contentResolver: ContentResolver,
private val configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
+ private val deviceProvisionedController: DeviceProvisionedController,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Main private val handler: Handler,
@@ -95,6 +97,55 @@
}
}
+ private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
+ execution.assertIsMainThread()
+ val filteredTargets = targets.filter(::filterSmartspaceTarget)
+ plugin?.onTargetsAvailable(filteredTargets)
+ }
+
+ private val userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val settingsObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val configChangeListener = object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ execution.assertIsMainThread()
+ updateTextColorFromWallpaper()
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ execution.assertIsMainThread()
+ smartspaceViews.forEach { it.setDozeAmount(eased) }
+ }
+ }
+
+ private val deviceProvisionedListener =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onDeviceProvisionedChanged() {
+ connectSession()
+ }
+
+ override fun onUserSetupChanged() {
+ connectSession()
+ }
+ }
+
+ init {
+ deviceProvisionedController.addCallback(deviceProvisionedListener)
+ }
+
fun isEnabled(): Boolean {
execution.assertIsMainThread()
@@ -145,10 +196,20 @@
if (plugin == null || session != null) {
return
}
- val session = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, "lockscreen").build())
- session.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ // Only connect after the device is fully provisioned to avoid connection caching
+ // issues
+ if (!deviceProvisionedController.isDeviceProvisioned() ||
+ !deviceProvisionedController.isCurrentUserSetup()) {
+ return
+ }
+
+ val newSession = smartspaceManager.createSmartspaceSession(
+ SmartspaceConfig.Builder(context, "lockscreen").build())
+ newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ this.session = newSession
+
+ deviceProvisionedController.removeCallback(deviceProvisionedListener)
userTracker.addCallback(userTrackerCallback, uiExecutor)
contentResolver.registerContentObserver(
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
@@ -159,8 +220,6 @@
configurationController.addCallback(configChangeListener)
statusBarStateController.addCallback(statusBarStateListener)
- this.session = session
-
reloadSmartspace()
}
@@ -197,43 +256,6 @@
plugin?.unregisterListener(listener)
}
- private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
- execution.assertIsMainThread()
- val filteredTargets = targets.filter(::filterSmartspaceTarget)
- plugin?.onTargetsAvailable(filteredTargets)
- }
-
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
-
- override fun onProfilesChanged(profiles: List<UserInfo>) {
- }
- }
-
- private val settingsObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
- }
-
- private val configChangeListener = object : ConfigurationController.ConfigurationListener {
- override fun onThemeChanged() {
- execution.assertIsMainThread()
- updateTextColorFromWallpaper()
- }
- }
-
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- execution.assertIsMainThread()
- smartspaceViews.forEach { it.setDozeAmount(eased) }
- }
- }
-
private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
return when (t.userHandle) {
userTracker.userHandle -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index db713b2..2e7f4b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -323,7 +323,6 @@
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
- private ViewGroup mBigClockContainer;
@VisibleForTesting QS mQs;
private FrameLayout mQsFrame;
@Nullable
@@ -886,7 +885,6 @@
private void onFinishInflate() {
loadDimens();
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
- mBigClockContainer = mView.findViewById(R.id.big_clock_container);
mCommunalView = mView.findViewById(R.id.communal_host);
FrameLayout userAvatarContainer = null;
@@ -1193,7 +1191,6 @@
R.layout.keyguard_user_switcher /* layoutId */,
showKeyguardUserSwitcher /* enabled */);
- mBigClockContainer.removeAllViews();
updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
keyguardUserSwitcherView, mView.findViewById(R.id.idle_host_view), mCommunalView);
@@ -2326,7 +2323,6 @@
if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == KEYGUARD) {
updateKeyguardBottomAreaAlpha();
positionClockAndNotifications();
- updateBigClockAlpha();
}
if (mAccessibilityManager.isEnabled()) {
@@ -3108,20 +3104,6 @@
mLockIconViewController.setAlpha(alpha);
}
- /**
- * Custom clock fades away when user drags up to unlock or pulls down quick settings.
- *
- * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See
- * {@link #updateKeyguardBottomAreaAlpha}.
- */
- private void updateBigClockAlpha() {
- float expansionAlpha = MathUtils.map(
- isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
- getExpandedFraction());
- float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
- mBigClockContainer.setAlpha(alpha);
- }
-
@Override
protected void onExpandingStarted() {
super.onExpandingStarted();
@@ -3479,7 +3461,6 @@
}
mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
updateKeyguardBottomAreaAlpha();
- updateBigClockAlpha();
updateStatusBarIcons();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 575e01cf..c8fec02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
import com.android.systemui.util.concurrency.FakeExecution
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -89,6 +91,8 @@
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@Mock
+ private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock
private lateinit var handler: Handler
@Mock
@@ -106,12 +110,15 @@
private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
@Captor
private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
+ @Captor
+ private lateinit var deviceProvisionedCaptor: ArgumentCaptor<DeviceProvisionedListener>
private lateinit var sessionListener: OnTargetsAvailableListener
private lateinit var userListener: UserTracker.Callback
private lateinit var settingsObserver: ContentObserver
private lateinit var configChangeListener: ConfigurationListener
private lateinit var statusBarStateListener: StateListener
+ private lateinit var deviceProvisionedListener: DeviceProvisionedListener
private lateinit var smartspaceView: SmartspaceView
@@ -145,6 +152,8 @@
`when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
`when`(userTracker.userProfiles).thenReturn(userList)
`when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
setActiveUser(userHandlePrimary)
setAllowPrivateNotifications(userHandlePrimary, true)
@@ -162,11 +171,15 @@
contentResolver,
configurationController,
statusBarStateController,
+ deviceProvisionedController,
execution,
executor,
handler,
Optional.of(plugin)
)
+
+ verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor))
+ deviceProvisionedListener = deviceProvisionedCaptor.value
}
@Test(expected = RuntimeException::class)
@@ -181,6 +194,27 @@
}
@Test
+ fun connectOnlyAfterDeviceIsProvisioned() {
+ // GIVEN an unprovisioned device and an attempt to connect
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
+
+ // WHEN a connection attempt is made
+ controller.buildAndConnectView(fakeParent)
+
+ // THEN no session is created
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+ // WHEN it does become provisioned
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+ deviceProvisionedListener.onUserSetupChanged()
+
+ // THEN the session is created
+ verify(smartspaceManager).createSmartspaceSession(any())
+ }
+
+ @Test
fun testListenersAreRegistered() {
// GIVEN a listener is added after a session is created
connectSession()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 4dec9dc..1de4647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -170,8 +170,6 @@
private KeyguardBottomAreaView mQsFrame;
private KeyguardStatusView mKeyguardStatusView;
@Mock
- private ViewGroup mBigClockContainer;
- @Mock
private NotificationIconAreaController mNotificationAreaController;
@Mock
private HeadsUpManagerPhone mHeadsUpManager;
@@ -393,7 +391,6 @@
when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
- when(mView.findViewById(R.id.big_clock_container)).thenReturn(mBigClockContainer);
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
diff --git a/proto/src/critical_event_log.proto b/proto/src/critical_event_log.proto
new file mode 100644
index 0000000..cb05a71
--- /dev/null
+++ b/proto/src/critical_event_log.proto
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package com.android.server.am;
+
+option java_multiple_files = true;
+
+// Output proto containing recent critical events for inclusion in logs such as ANR files.
+// Do not change the field names since this data is dumped to ANR files in textproto format.
+message CriticalEventLogProto {
+ // Timestamp when the log snapshot was generated.
+ // Required.
+ optional int64 timestamp_ms = 1;
+
+ // Max age of events that are included in this proto.
+ // Required.
+ optional int32 window_ms = 2;
+
+ // Max number of events in the log.
+ // Note: if the number of events is equal to the capacity then it is likely the actual time range
+ // covered by the log is shorter than window_ms.
+ // Required.
+ optional int32 capacity = 3;
+
+ // Recent critical events.
+ repeated CriticalEventProto events = 4;
+}
+
+// On-disk storage of events.
+message CriticalEventLogStorageProto {
+ repeated CriticalEventProto events = 1;
+}
+
+// A "critical" event such as an ANR or watchdog.
+// Do not change the field names since this data is dumped to ANR files in textproto format.
+message CriticalEventProto {
+ // Timestamp of the event.
+ // Required.
+ optional int64 timestamp_ms = 1;
+
+ // Required.
+ oneof event {
+ Watchdog watchdog = 2;
+ HalfWatchdog half_watchdog = 3;
+ }
+
+ message Watchdog {
+ // The watchdog subject.
+ // Required.
+ optional string subject = 1;
+
+ // Unique identifier of the watchdog.
+ // Can be used to join with other data for this watchdog such as stack dumps & perfetto traces.
+ // Generated in {@link com.android.server.Watchdog#run}.
+ // Required.
+ optional string uuid = 2;
+ }
+
+ message HalfWatchdog {
+ // The half-watchdog subject.
+ // Required.
+ optional string subject = 1;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index 364e733..53b6605 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -192,7 +192,7 @@
final ArrayList<Integer> pids = new ArrayList<>();
pids.add(Process.myPid());
ActivityManagerService.dumpStackTraces(pids, null, null,
- Watchdog.getInterestingNativePids(), null, null);
+ Watchdog.getInterestingNativePids(), null, null, null);
}
@Override
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index fcd049f..46efa3c 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -45,6 +45,7 @@
import com.android.internal.os.ZygoteConnectionConstants;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.ActivityManagerService;
+import com.android.server.am.CriticalEventLog;
import com.android.server.am.TraceErrorLogger;
import com.android.server.wm.SurfaceAnimationThread;
@@ -661,10 +662,15 @@
} // END synchronized (mLock)
if (doWaitedHalfDump) {
+ // Get critical event log before logging the half watchdog so that it doesn't
+ // occur in the log.
+ String criticalEvents = CriticalEventLog.getInstance().logLinesForAnrFile();
+ CriticalEventLog.getInstance().logHalfWatchdog(subject);
+
// We've waited half the deadlock-detection interval. Pull a stack
// trace and wait another half.
ActivityManagerService.dumpStackTraces(pids, null, null,
- getInterestingNativePids(), null, subject);
+ getInterestingNativePids(), null, subject, criticalEvents);
continue;
}
@@ -673,12 +679,9 @@
// Then kill this process so that the system will restart.
EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
- final UUID errorId;
+ final UUID errorId = mTraceErrorLogger.generateErrorId();
if (mTraceErrorLogger.isAddErrorIdEnabled()) {
- errorId = mTraceErrorLogger.generateErrorId();
mTraceErrorLogger.addErrorIdToTrace("system_server", errorId);
- } else {
- errorId = null;
}
// Log the atom as early as possible since it is used as a mechanism to trigger
@@ -686,6 +689,11 @@
// point in time when the Watchdog happens as possible.
FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject);
+ // Get critical event log before logging the watchdog so that it doesn't occur in the
+ // log.
+ String criticalEvents = CriticalEventLog.getInstance().logLinesForAnrFile();
+ CriticalEventLog.getInstance().logWatchdog(subject, errorId);
+
long anrTime = SystemClock.uptimeMillis();
StringBuilder report = new StringBuilder();
report.append(MemoryPressureUtil.currentPsiState());
@@ -693,7 +701,7 @@
StringWriter tracesFileException = new StringWriter();
final File stack = ActivityManagerService.dumpStackTraces(
pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids(),
- tracesFileException, subject);
+ tracesFileException, subject, criticalEvents);
// Give some extra time to make sure the stack traces get written.
// The system's been hanging for a minute, another second or two won't hurt much.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index da6eeb6..6a63254 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2394,6 +2394,7 @@
private void start() {
removeAllProcessGroups();
+ CriticalEventLog.init();
mBatteryStatsService.publish();
mAppOpsService.publish();
Slog.d("AppOps", "AppOpsService published");
@@ -3199,7 +3200,7 @@
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile) {
return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
- logExceptionCreatingFile, null, null);
+ logExceptionCreatingFile, null, null, null);
}
/**
@@ -3209,13 +3210,14 @@
* @param nativePids optional list of native pids to dump stack crawls
* @param logExceptionCreatingFile optional writer to which we log errors creating the file
* @param subject optional line related to the error
+ * @param criticalEventSection optional lines containing recent critical events.
*/
public static File dumpStackTraces(ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
- String subject) {
+ String subject, String criticalEventSection) {
return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
- logExceptionCreatingFile, null, subject);
+ logExceptionCreatingFile, null, subject, criticalEventSection);
}
/**
@@ -3225,7 +3227,7 @@
/* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
- long[] firstPidOffsets, String subject) {
+ long[] firstPidOffsets, String subject, String criticalEventSection) {
ArrayList<Integer> extraPids = null;
Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids);
@@ -3277,12 +3279,17 @@
return null;
}
- if (subject != null) {
+ if (subject != null || criticalEventSection != null) {
try (FileOutputStream fos = new FileOutputStream(tracesFile, true)) {
- String header = "Subject: " + subject + "\n";
- fos.write(header.getBytes(StandardCharsets.UTF_8));
+ if (subject != null) {
+ String header = "Subject: " + subject + "\n\n";
+ fos.write(header.getBytes(StandardCharsets.UTF_8));
+ }
+ if (criticalEventSection != null) {
+ fos.write(criticalEventSection.getBytes(StandardCharsets.UTF_8));
+ }
} catch (IOException e) {
- Slog.w(TAG, "Exception writing subject to ANR dump file:", e);
+ Slog.w(TAG, "Exception writing to ANR dump file:", e);
}
}
@@ -11830,8 +11837,8 @@
restart = app.onCleanupApplicationRecordLSP(mProcessStats, allowRestart,
fromBinderDied || app.isolated /* unlinkDeath */);
- // Cancel pending frozen task if there is any.
- mOomAdjuster.mCachedAppOptimizer.unscheduleFreezeAppLSP(app);
+ // Cancel pending frozen task and clean up frozen record if there is any.
+ mOomAdjuster.mCachedAppOptimizer.onCleanupApplicationRecordLocked(app);
}
mAppProfiler.onCleanupApplicationRecordLocked(app);
skipCurrentReceiverLocked(app);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 583fb89..0c94fbb 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -38,6 +38,7 @@
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -173,6 +174,10 @@
private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
new ArrayList<ProcessRecord>();
+ @GuardedBy("mProcLock")
+ private final SparseArray<ProcessRecord> mFrozenProcesses =
+ new SparseArray<>();
+
private final ActivityManagerService mAm;
private final ActivityManagerGlobalLock mProcLock;
@@ -424,6 +429,15 @@
pw.println(" " + KEY_USE_FREEZER + "=" + mUseFreezer);
pw.println(" " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate);
pw.println(" " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout);
+ synchronized (mProcLock) {
+ int size = mFrozenProcesses.size();
+ pw.println(" Apps frozen: " + size);
+ for (int i = 0; i < size; i++) {
+ ProcessRecord app = mFrozenProcesses.valueAt(i);
+ pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime()
+ + ": " + app.getPid() + " " + app.processName);
+ }
+ }
if (DEBUG_COMPACTION) {
for (Map.Entry<Integer, LastCompactionStats> entry
: mLastCompactionStats.entrySet()) {
@@ -1001,6 +1015,7 @@
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(false);
+ mFrozenProcesses.delete(pid);
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to unfreeze " + pid + " " + app.processName
+ ". This might cause inconsistency or UI hangs.");
@@ -1021,7 +1036,7 @@
* To be called when the given app is killed.
*/
@GuardedBy({"mAm", "mProcLock"})
- void unscheduleFreezeAppLSP(ProcessRecord app) {
+ void onCleanupApplicationRecordLocked(ProcessRecord app) {
if (mUseFreezer) {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
if (opt.isPendingFreeze()) {
@@ -1029,6 +1044,8 @@
mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
opt.setPendingFreeze(false);
}
+
+ mFrozenProcesses.delete(app.getPid());
}
}
@@ -1293,7 +1310,8 @@
}
}
- private final class FreezeHandler extends Handler {
+ private final class FreezeHandler extends Handler implements
+ ProcLocksReader.ProcLocksReaderCallback {
private FreezeHandler() {
super(mCachedAppOptimizerThread.getLooper());
}
@@ -1336,20 +1354,6 @@
opt.setPendingFreeze(false);
- try {
- // pre-check for locks to avoid unnecessary freeze/unfreeze operations
- if (mProcLocksReader.hasFileLocks(pid)) {
- if (DEBUG_FREEZER) {
- Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
- }
- return;
- }
- } catch (Exception e) {
- Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
- + "): " + e);
- return;
- }
-
synchronized (mProcLock) {
pid = proc.getPid();
if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ
@@ -1403,6 +1407,7 @@
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(true);
+ mFrozenProcesses.put(pid, proc);
} catch (Exception e) {
Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
}
@@ -1450,16 +1455,13 @@
}
try {
- // post-check to prevent races
- if (mProcLocksReader.hasFileLocks(pid)) {
- if (DEBUG_FREEZER) {
- Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
- }
- unfreezeAppLSP(proc);
- }
+ // post-check to prevent deadlock
+ mProcLocksReader.handleBlockingFileLocks(this);
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
- unfreezeAppLSP(proc);
+ synchronized (mProcLock) {
+ unfreezeAppLSP(proc);
+ }
}
}
@@ -1477,6 +1479,21 @@
frozenDuration);
}
}
+
+ @GuardedBy({"mAm"})
+ @Override
+ public void onBlockingFileLock(int pid) {
+ if (DEBUG_FREEZER) {
+ Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock");
+ }
+ synchronized (mProcLock) {
+ ProcessRecord app = mFrozenProcesses.get(pid);
+ if (app != null) {
+ Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
+ unfreezeAppLSP(app);
+ }
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/CriticalEventLog.java b/services/core/java/com/android/server/am/CriticalEventLog.java
new file mode 100644
index 0000000..6b69559
--- /dev/null
+++ b/services/core/java/com/android/server/am/CriticalEventLog.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Slog;
+
+import com.android.framework.protobuf.nano.MessageNanoPrinter;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
+import com.android.server.am.nano.CriticalEventLogProto;
+import com.android.server.am.nano.CriticalEventLogStorageProto;
+import com.android.server.am.nano.CriticalEventProto;
+import com.android.server.am.nano.CriticalEventProto.HalfWatchdog;
+import com.android.server.am.nano.CriticalEventProto.Watchdog;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * Log of recent critical events such as Watchdogs.
+ *
+ * For use in ANR reports to show recent events that may help to debug the ANR. In particular,
+ * the presence of recent critical events signal that the device was already in a had state.
+ *
+ * This class needs to be thread safe since it's used as a singleton.
+ */
+public class CriticalEventLog {
+ private static final String TAG = CriticalEventLog.class.getSimpleName();
+
+ private static CriticalEventLog sInstance;
+
+ /** Name of the file the log is saved to. */
+ @VisibleForTesting
+ static final String FILENAME = "critical_event_log.pb";
+
+ /** Timestamp when the log was last saved (or attempted to be saved) to disk. */
+ private long mLastSaveAttemptMs = 0;
+
+ /** File the log is saved to. */
+ private final File mLogFile;
+
+ /** Ring buffer containing the log events. */
+ private final ThreadSafeRingBuffer<CriticalEventProto> mEvents;
+
+ /** Max age of events to include in the output log proto. */
+ private final int mWindowMs;
+
+ /** Minimum time between consecutive saves of the log to disk. */
+ private final long mMinTimeBetweenSavesMs;
+
+ /** Whether to load and save the log synchronously with no delay. Only set to true in tests. */
+ private final boolean mLoadAndSaveImmediately;
+
+ private final Handler mHandler;
+
+ private final Runnable mSaveRunnable = this::saveLogToFileNow;
+
+ @VisibleForTesting
+ CriticalEventLog(String logDir, int capacity, int windowMs, long minTimeBetweenSavesMs,
+ boolean loadAndSaveImmediately, ILogLoader logLoader) {
+ mLogFile = Paths.get(logDir, FILENAME).toFile();
+ mWindowMs = windowMs;
+ mMinTimeBetweenSavesMs = minTimeBetweenSavesMs;
+ mLoadAndSaveImmediately = loadAndSaveImmediately;
+
+ mEvents = new ThreadSafeRingBuffer<>(CriticalEventProto.class, capacity);
+
+ HandlerThread thread = new HandlerThread("CriticalEventLogIO");
+ thread.start();
+ mHandler = new Handler(thread.getLooper());
+
+ final Runnable loadEvents = () -> logLoader.load(mLogFile, mEvents);
+ if (!mLoadAndSaveImmediately) {
+ mHandler.post(loadEvents);
+ } else {
+ loadEvents.run();
+ }
+ }
+
+ /** Returns a new instance with production settings. */
+ private CriticalEventLog() {
+ this(
+ /* logDir= */"/data/misc/critical-events",
+ /* capacity= */ 20,
+ /* windowMs= */ (int) Duration.ofMinutes(5).toMillis(),
+ /* minTimeBetweenSavesMs= */ Duration.ofSeconds(2).toMillis(),
+ /* loadAndSaveImmediately= */ false,
+ new LogLoader());
+ }
+
+ /** Returns the singleton instance. */
+ public static CriticalEventLog getInstance() {
+ if (sInstance == null) {
+ sInstance = new CriticalEventLog();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Ensures the singleton instance has been instantiated.
+ *
+ * Use this to eagerly instantiate the log (which loads the previous events from disk).
+ * Otherwise this will occur lazily when the first event is logged, by which time the device may
+ * be under load.
+ */
+ public static void init() {
+ getInstance();
+ }
+
+ @VisibleForTesting
+ protected long getWallTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ /** Logs a watchdog. */
+ public void logWatchdog(String subject, UUID uuid) {
+ Watchdog watchdog = new Watchdog();
+ watchdog.subject = subject;
+ watchdog.uuid = uuid.toString();
+ CriticalEventProto event = new CriticalEventProto();
+ event.setWatchdog(watchdog);
+ log(event);
+ }
+
+ /** Logs a half-watchdog. */
+ public void logHalfWatchdog(String subject) {
+ HalfWatchdog halfWatchdog = new HalfWatchdog();
+ halfWatchdog.subject = subject;
+ CriticalEventProto event = new CriticalEventProto();
+ event.setHalfWatchdog(halfWatchdog);
+ log(event);
+ }
+
+ private void log(CriticalEventProto event) {
+ event.timestampMs = getWallTimeMillis();
+ mEvents.append(event);
+ saveLogToFile();
+ }
+
+ /**
+ * Returns recent critical events in text format to include in logs such as ANR files.
+ *
+ * Includes all events in the ring buffer with age less than or equal to {@code mWindowMs}.
+ */
+ public String logLinesForAnrFile() {
+ return new StringBuilder()
+ .append("--- CriticalEventLog ---\n")
+ .append(MessageNanoPrinter.print(getRecentEvents()))
+ .append('\n').toString();
+ }
+
+ /**
+ * Returns a proto containing recent critical events.
+ *
+ * Includes all events in the ring buffer with age less than or equal to {@code mWindowMs}.
+ */
+ @VisibleForTesting
+ protected CriticalEventLogProto getRecentEvents() {
+ CriticalEventLogProto log = new CriticalEventLogProto();
+ log.timestampMs = getWallTimeMillis();
+ log.windowMs = mWindowMs;
+ log.capacity = mEvents.capacity();
+ log.events = recentEventsWithMinTimestamp(log.timestampMs - mWindowMs);
+
+ return log;
+ }
+
+ /**
+ * Returns the most recent logged events, starting with the first event that has a timestamp
+ * greater than or equal to {@code minTimestampMs}.
+ *
+ * If no events have a timestamp greater than or equal to {@code minTimestampMs}, returns an
+ * empty array.
+ */
+ private CriticalEventProto[] recentEventsWithMinTimestamp(long minTimestampMs) {
+ // allEvents are in insertion order, i.e. in order of when the relevant log___() function
+ // was called.
+ // This means that if the system clock changed (e.g. a NITZ update) allEvents may not be
+ // strictly ordered by timestamp. In this case we are permissive and start with the
+ // first event that has a timestamp in the desired range.
+ CriticalEventProto[] allEvents = mEvents.toArray();
+ for (int i = 0; i < allEvents.length; i++) {
+ if (allEvents[i].timestampMs >= minTimestampMs) {
+ return Arrays.copyOfRange(allEvents, i, allEvents.length);
+ }
+ }
+ return new CriticalEventProto[]{};
+ }
+
+ private void saveLogToFile() {
+ if (mLoadAndSaveImmediately) {
+ saveLogToFileNow();
+ return;
+ }
+ if (mHandler.hasCallbacks(mSaveRunnable)) {
+ // An earlier save is already scheduled so don't need to schedule an additional one.
+ return;
+ }
+
+ if (!mHandler.postDelayed(mSaveRunnable, saveDelayMs())) {
+ Slog.w(TAG, "Error scheduling save");
+ }
+ }
+
+ /**
+ * Returns the delay in milliseconds when scheduling a save on the handler thread.
+ *
+ * Returns a value in the range [0, {@code minTimeBetweenSavesMs}] such that the time between
+ * consecutive saves does not exceed {@code minTimeBetweenSavesMs}.
+ *
+ * This means that if the last save occurred a long time ago, or if no previous saves
+ * have occurred then the returned delay will be zero.
+ */
+ @VisibleForTesting
+ protected long saveDelayMs() {
+ final long nowMs = getWallTimeMillis();
+ return Math.max(0,
+ mLastSaveAttemptMs + mMinTimeBetweenSavesMs - nowMs);
+ }
+
+ @VisibleForTesting
+ protected void saveLogToFileNow() {
+ mLastSaveAttemptMs = getWallTimeMillis();
+
+ File logDir = mLogFile.getParentFile();
+ if (!logDir.exists()) {
+ if (!logDir.mkdir()) {
+ Slog.e(TAG, "Error creating log directory: " + logDir.getPath());
+ return;
+ }
+ }
+
+ if (!mLogFile.exists()) {
+ try {
+ mLogFile.createNewFile();
+ } catch (IOException e) {
+ Slog.e(TAG, "Error creating log file", e);
+ return;
+ }
+ }
+
+ CriticalEventLogStorageProto logProto = new CriticalEventLogStorageProto();
+ logProto.events = mEvents.toArray();
+
+ final byte[] bytes = CriticalEventLogStorageProto.toByteArray(logProto);
+ try (FileOutputStream stream = new FileOutputStream(mLogFile, false)) {
+ stream.write(bytes);
+ } catch (IOException e) {
+ Slog.e(TAG, "Error saving log to disk.", e);
+ }
+ }
+
+ @VisibleForTesting
+ protected static class ThreadSafeRingBuffer<T> {
+ private final int mCapacity;
+ private final RingBuffer<T> mBuffer;
+
+ ThreadSafeRingBuffer(Class<T> clazz, int capacity) {
+ this.mCapacity = capacity;
+ this.mBuffer = new RingBuffer<>(clazz, capacity);
+ }
+
+ synchronized void append(T t) {
+ mBuffer.append(t);
+ }
+
+ synchronized T[] toArray() {
+ return mBuffer.toArray();
+ }
+
+ int capacity() {
+ return mCapacity;
+ }
+ }
+
+ /** Loads log events from disk into a ring buffer. */
+ protected interface ILogLoader {
+ void load(File logFile, ThreadSafeRingBuffer<CriticalEventProto> buffer);
+ }
+
+ /** Loads log events from disk into a ring buffer. */
+ static class LogLoader implements ILogLoader {
+ @Override
+ public void load(File logFile,
+ ThreadSafeRingBuffer<CriticalEventProto> buffer) {
+ for (CriticalEventProto event : loadLogFromFile(logFile).events) {
+ buffer.append(event);
+ }
+ }
+
+ private static CriticalEventLogStorageProto loadLogFromFile(File logFile) {
+ if (!logFile.exists()) {
+ Slog.i(TAG, "No log found, returning empty log proto.");
+ return new CriticalEventLogStorageProto();
+ }
+
+ try {
+ return CriticalEventLogStorageProto.parseFrom(
+ Files.readAllBytes(logFile.toPath()));
+ } catch (IOException e) {
+ Slog.e(TAG, "Error reading log from disk.", e);
+ return new CriticalEventLogStorageProto();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index a32fe02..5fac879 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -400,9 +400,10 @@
StringWriter tracesFileException = new StringWriter();
// To hold the start and end offset to the ANR trace file respectively.
final long[] offsets = new long[2];
+ final String criticalEventLog = CriticalEventLog.getInstance().logLinesForAnrFile();
File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
- nativePids, tracesFileException, offsets, annotation);
+ nativePids, tracesFileException, offsets, annotation, criticalEventLog);
if (isMonitorCpuUsage()) {
mService.updateCpuStatsNow();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 87c27c9..045ee8a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -826,7 +826,7 @@
Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, -1);
final int width = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, -1);
- Display.Mode mode = new Display.Mode(height, width, refreshRate);
+ Display.Mode mode = new Display.Mode(width, height, refreshRate);
mUserPreferredMode = isResolutionAndRefreshRateValid(mode) ? mode : null;
}
diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
index 5d23fa4..da40ce59 100644
--- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -38,9 +38,9 @@
final class HotplugDetectionAction extends HdmiCecFeatureAction {
private static final String TAG = "HotPlugDetectionAction";
- private static final int POLLING_INTERVAL_MS_FOR_TV = 5000;
+ public static final int POLLING_INTERVAL_MS_FOR_TV = 5000;
public static final int POLLING_INTERVAL_MS_FOR_PLAYBACK = 60000;
- private static final int TIMEOUT_COUNT = 3;
+ public static final int TIMEOUT_COUNT = 3;
private static final int AVR_COUNT_MAX = 3;
// State in which waits for next polling
@@ -155,8 +155,9 @@
}
private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
- BitSet currentInfos = infoListToBitSet(
- localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false), audioOnly);
+ List<HdmiDeviceInfo> deviceInfoList =
+ localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
+ BitSet currentInfos = infoListToBitSet(deviceInfoList, audioOnly, false);
BitSet polledResult = addressListToBitSet(ackedAddress);
// At first, check removed devices.
@@ -183,7 +184,8 @@
}
// Next, check added devices.
- BitSet added = complement(polledResult, currentInfos);
+ BitSet currentInfosWithPhysicalAddress = infoListToBitSet(deviceInfoList, audioOnly, true);
+ BitSet added = complement(polledResult, currentInfosWithPhysicalAddress);
index = -1;
while ((index = added.nextSetBit(index + 1)) != -1) {
Slog.v(TAG, "Add device by hot-plug detection:" + index);
@@ -191,14 +193,15 @@
}
}
- private static BitSet infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly) {
+ private static BitSet infoListToBitSet(
+ List<HdmiDeviceInfo> infoList, boolean audioOnly, boolean requirePhysicalAddress) {
BitSet set = new BitSet(NUM_OF_ADDRESS);
for (HdmiDeviceInfo info : infoList) {
- if (audioOnly) {
- if (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
- set.set(info.getLogicalAddress());
- }
- } else {
+ boolean audioOnlyConditionMet = !audioOnly
+ || (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ boolean requirePhysicalAddressConditionMet = !requirePhysicalAddress
+ || (info.getPhysicalAddress() != HdmiDeviceInfo.PATH_INVALID);
+ if (audioOnlyConditionMet && requirePhysicalAddressConditionMet) {
set.set(info.getLogicalAddress());
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 168686c..c1fcf71 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1411,9 +1411,8 @@
this.task = newTask;
if (shouldStartChangeTransition(newParent, oldParent)) {
- // The new parent and old parent may be in different position. Need to offset the
- // animation surface to keep it in its original position.
- initializeChangeTransition(getBounds(), newParent.getBounds());
+ // Animate change transition on TaskFragment level to get the correct window crop.
+ newParent.initializeChangeTransition(getBounds(), getSurfaceControl());
}
super.onParentChanged(newParent, oldParent);
@@ -1482,7 +1481,7 @@
// The starting window should keep covering its task when the activity is
// reparented to a task fragment that may not fill the task bounds.
associateStartingDataWithTask();
- overrideConfigurationPropagation(mStartingWindow, task);
+ attachStartingSurfaceToAssociatedTask();
}
mImeInsetsFrozenUntilStartInput = false;
}
@@ -2383,13 +2382,20 @@
}
void attachStartingWindow(@NonNull WindowState startingWindow) {
+ startingWindow.mStartingData = mStartingData;
mStartingWindow = startingWindow;
if (mStartingData != null && mStartingData.mAssociatedTask != null) {
- // Associate the configuration of starting window with the task.
- overrideConfigurationPropagation(startingWindow, mStartingData.mAssociatedTask);
+ attachStartingSurfaceToAssociatedTask();
}
}
+ private void attachStartingSurfaceToAssociatedTask() {
+ // Associate the configuration of starting window with the task.
+ overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask);
+ getSyncTransaction().reparent(mStartingWindow.mSurfaceControl,
+ mStartingData.mAssociatedTask.mSurfaceControl);
+ }
+
private void associateStartingDataWithTask() {
mStartingData.mAssociatedTask = task;
task.forAllActivities(r -> {
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 892db9c..bf66107 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -31,6 +31,7 @@
import android.view.InputApplicationHandle;
import com.android.server.am.ActivityManagerService;
+import com.android.server.am.CriticalEventLog;
import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow;
import java.io.File;
@@ -222,9 +223,10 @@
}
}
+ String criticalEvents = CriticalEventLog.getInstance().logLinesForAnrFile();
final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
null /* processCpuTracker */, null /* lastPids */, nativePids,
- null /* logExceptionCreatingFile */);
+ null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents);
if (tracesFile != null) {
tracesFile.renameTo(new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index a97ca6c..1a2bf9a 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -343,9 +343,6 @@
switch (changingType) {
case TYPE_TASK:
return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
- case TYPE_ACTIVITY:
- // ActivityRecord is put in a change transition only when it is reparented
- // to an organized TaskFragment. See ActivityRecord#shouldStartChangeTransition.
case TYPE_TASK_FRAGMENT:
return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
default:
@@ -533,40 +530,81 @@
*
* @return {@code true} if the transition is overridden.
*/
- @VisibleForTesting
- boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
+ private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
ArraySet<Integer> activityTypes) {
final ArrayList<WindowContainer> allWindows = new ArrayList<>();
allWindows.addAll(mDisplayContent.mClosingApps);
allWindows.addAll(mDisplayContent.mOpeningApps);
allWindows.addAll(mDisplayContent.mChangingContainers);
- // Find the common TaskFragmentOrganizer of all windows.
- ITaskFragmentOrganizer organizer = null;
+ // It should only animated by the organizer if all windows are below the same leaf Task.
+ Task leafTask = null;
for (int i = allWindows.size() - 1; i >= 0; i--) {
final ActivityRecord r = getAppFromContainer(allWindows.get(i));
if (r == null) {
return false;
}
+ // The activity may be a child of embedded Task, but we want to find the owner Task.
+ // As a result, find the organized TaskFragment first.
final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment();
- final ITaskFragmentOrganizer curOrganizer = organizedTaskFragment != null
- ? organizedTaskFragment.getTaskFragmentOrganizer()
- : null;
- if (curOrganizer == null) {
- // All windows must below an organized TaskFragment.
+ // There are also cases where the Task contains non-embedded activity, such as launching
+ // split TaskFragments from a non-embedded activity.
+ // The hierarchy may looks like this:
+ // - Task
+ // - Activity
+ // - TaskFragment
+ // - Activity
+ // - TaskFragment
+ // - Activity
+ // We also want to have the organizer handle the transition for such case.
+ final Task task = organizedTaskFragment != null
+ ? organizedTaskFragment.getTask()
+ : r.getTask();
+ if (task == null) {
return false;
}
- if (organizer == null) {
- organizer = curOrganizer;
- } else if (!organizer.asBinder().equals(curOrganizer.asBinder())) {
- // They must be controlled by the same organizer.
+ // We don't want the organizer to handle transition of other non-embedded Task.
+ if (leafTask != null && leafTask != task) {
return false;
}
+ final ActivityRecord rootActivity = task.getRootActivity();
+ // We don't want the organizer to handle transition when the whole app is closing.
+ if (rootActivity == null) {
+ return false;
+ }
+ // We don't want the organizer to handle transition of non-embedded activity of other
+ // app.
+ if (r.getUid() != rootActivity.getUid() && !r.isEmbedded()) {
+ return false;
+ }
+ leafTask = task;
+ }
+ if (leafTask == null) {
+ return false;
}
- final RemoteAnimationDefinition definition = organizer != null
+ // We don't support remote animation for Task with multiple TaskFragmentOrganizers.
+ final ITaskFragmentOrganizer[] organizer = new ITaskFragmentOrganizer[1];
+ final boolean hasMultipleOrganizers = leafTask.forAllLeafTaskFragments(taskFragment -> {
+ final ITaskFragmentOrganizer tfOrganizer = taskFragment.getTaskFragmentOrganizer();
+ if (tfOrganizer == null) {
+ return false;
+ }
+ if (organizer[0] != null && !organizer[0].asBinder().equals(tfOrganizer.asBinder())) {
+ return true;
+ }
+ organizer[0] = tfOrganizer;
+ return false;
+ });
+ if (hasMultipleOrganizers) {
+ ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for"
+ + " Task with multiple TaskFragmentOrganizers.");
+ return false;
+ }
+
+ final RemoteAnimationDefinition definition = organizer[0] != null
? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getRemoteAnimationDefinition(organizer)
+ .getRemoteAnimationDefinition(organizer[0])
: null;
final RemoteAnimationAdapter adapter = definition != null
? definition.getAdapter(transit, activityTypes)
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f2e6abc..fa4d27c 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -47,6 +47,8 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS;
+import static java.lang.Integer.MAX_VALUE;
+
import android.annotation.Nullable;
import android.graphics.Rect;
import android.graphics.Region;
@@ -580,10 +582,11 @@
if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
if (recentsAnimationController.updateInputConsumerForApp(
mRecentsAnimationInputConsumer.mWindowHandle)) {
- final WindowState highestLayerWindow =
- recentsAnimationController.getHighestLayerWindow();
- if (highestLayerWindow != null) {
- mRecentsAnimationInputConsumer.show(mInputTransaction, highestLayerWindow);
+ final DisplayArea targetDA =
+ recentsAnimationController.getTargetAppDisplayArea();
+ if (targetDA != null) {
+ mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA);
+ mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
mAddRecentsAnimationInputConsumerHandle = false;
}
}
@@ -596,7 +599,7 @@
rootTask.getSurfaceControl());
// We set the layer to z=MAX-1 so that it's always on top.
mPipInputConsumer.reparent(mInputTransaction, rootTask);
- mPipInputConsumer.show(mInputTransaction, Integer.MAX_VALUE - 1);
+ mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
mAddPipInputConsumerHandle = false;
}
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 45411a9..4b98013 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -36,6 +36,7 @@
import com.android.server.UiThread;
+import java.util.function.IntConsumer;
import java.util.function.Supplier;
/**
@@ -70,7 +71,7 @@
private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
// Reachability gestures.
- private final Runnable mDoubleTapCallback;
+ private final IntConsumer mDoubleTapCallback;
/**
* Constructs a Letterbox.
@@ -84,7 +85,7 @@
Supplier<Boolean> hasWallpaperBackgroundSupplier,
Supplier<Integer> blurRadiusSupplier,
Supplier<Float> darkScrimAlphaSupplier,
- Runnable doubleTapCallback) {
+ IntConsumer doubleTapCallback) {
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
mAreCornersRounded = areCornersRounded;
@@ -262,7 +263,7 @@
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP) {
- mDoubleTapCallback.run();
+ mDoubleTapCallback.accept((int) e.getX());
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 72fbfcc..cbb473c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -54,6 +54,27 @@
/** Using wallpaper as a background which can be blurred or dimmed with dark scrim. */
static final int LETTERBOX_BACKGROUND_WALLPAPER = 3;
+ /**
+ * Enum for Letterbox reachability position types.
+ *
+ * <p>Order from left to right is important since it's used in {@link
+ * #movePositionForReachabilityToNextRightStop} and {@link
+ * #movePositionForReachabilityToNextLeftStop}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({LETTERBOX_REACHABILITY_POSITION_LEFT, LETTERBOX_REACHABILITY_POSITION_CENTER,
+ LETTERBOX_REACHABILITY_POSITION_RIGHT})
+ @interface LetterboxReachabilityPosition {};
+
+ /** Letterboxed app window is aligned to the left side. */
+ static final int LETTERBOX_REACHABILITY_POSITION_LEFT = 0;
+
+ /** Letterboxed app window is positioned in the horizontal center. */
+ static final int LETTERBOX_REACHABILITY_POSITION_CENTER = 1;
+
+ /** Letterboxed app window is aligned to the right side. */
+ static final int LETTERBOX_REACHABILITY_POSITION_RIGHT = 2;
+
final Context mContext;
// Aspect ratio of letterbox for fixed orientation, values <=
@@ -85,25 +106,25 @@
// side of the screen and 1.0 to the right side.
private float mLetterboxHorizontalPositionMultiplier;
- // Default horizontal position of a center of the letterboxed app window when reachability is
- // enabled and an app is fullscreen in landscape device orientatio. 0 corresponds to the left
- // side of the screen and 1.0 to the right side.
- // It is used as a starting point for mLetterboxHorizontalMultiplierForReachability.
- private float mDefaultPositionMultiplierForReachability;
+ // Default horizontal position the letterboxed app window when reachability is enabled and
+ // an app is fullscreen in landscape device orientatio.
+ // It is used as a starting point for mLetterboxPositionForReachability.
+ @LetterboxReachabilityPosition
+ private int mDefaultPositionForReachability;
// Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
// device orientation.
private boolean mIsReachabilityEnabled;
- // Horizontal position of a center of the letterboxed app window. 0 corresponds to
- // the left side of the screen and 1 to the right side. Keep it global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app
- // window in response to a double tap gesture (see LetterboxUiController#handleDoubleTap).
- // Used in LetterboxUiController#getHorizontalPositionMultiplier which is called from
+ // Horizontal position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getHorizontalPositionMultiplier which is called from
// ActivityRecord#updateResolvedBoundsHorizontalPosition.
// TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
// Overview after changing position in another app.
- private volatile float mLetterboxHorizontalMultiplierForReachability;
+ @LetterboxReachabilityPosition
+ private volatile int mLetterboxPositionForReachability;
LetterboxConfiguration(Context systemUiContext) {
mContext = systemUiContext;
@@ -120,9 +141,8 @@
R.dimen.config_letterboxHorizontalPositionMultiplier);
mIsReachabilityEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsReachabilityEnabled);
- mDefaultPositionMultiplierForReachability = mContext.getResources().getFloat(
- R.dimen.config_letterboxDefaultPositionMultiplierForReachability);
- mLetterboxHorizontalMultiplierForReachability = mDefaultPositionMultiplierForReachability;
+ mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
+ mLetterboxPositionForReachability = mDefaultPositionForReachability;
}
/**
@@ -395,58 +415,90 @@
}
/*
- * Gets default horizontal position of a center of the letterboxed app window when reachability
- * is enabled specified in {@link
- * R.dimen.config_letterboxDefaultPositionMultiplierForReachability} or via an ADB command.
- * 0 corresponds to the left side of the screen and 1 to the right side. The returned value is
- * >= 0.0 and <= 1.0.
+ * Gets default horizontal position of the letterboxed app window when reachability is enabled.
+ * Specified in {@link R.integer.config_letterboxDefaultPositionForReachability} or via an ADB
+ * command.
*/
- float getDefaultPositionMultiplierForReachability() {
- return (mDefaultPositionMultiplierForReachability < 0.0f
- || mDefaultPositionMultiplierForReachability > 1.0f)
- // Default to a right position if invalid value is provided.
- ? 1.0f : mDefaultPositionMultiplierForReachability;
+ @LetterboxReachabilityPosition
+ int getDefaultPositionForReachability() {
+ return mDefaultPositionForReachability;
}
/**
- * Overrides default horizontal position of a center of the letterboxed app window when
- * reachability is enabled. If given value < 0.0 or > 1.0, then it and a value of {@link
- * R.dimen.config_letterboxDefaultPositionMultiplierForReachability} are ignored and the right
- * position (1.0) is used.
+ * Overrides default horizonal position of the letterboxed app window when reachability
+ * is enabled.
*/
- void setDefaultPositionMultiplierForReachability(float multiplier) {
- mDefaultPositionMultiplierForReachability = multiplier;
+ void setDefaultPositionForReachability(@LetterboxReachabilityPosition int position) {
+ mDefaultPositionForReachability = position;
}
/**
- * Resets default horizontal position of a center of the letterboxed app window when
- * reachability is enabled to {@link
- * R.dimen.config_letterboxDefaultPositionMultiplierForReachability}.
+ * Resets default horizontal position of the letterboxed app window when reachability is
+ * enabled to {@link R.integer.config_letterboxDefaultPositionForReachability}.
*/
- void resetDefaultPositionMultiplierForReachability() {
- mDefaultPositionMultiplierForReachability = mContext.getResources().getFloat(
- R.dimen.config_letterboxDefaultPositionMultiplierForReachability);
+ void resetDefaultPositionForReachability() {
+ mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
+ }
+
+ @LetterboxReachabilityPosition
+ private static int readLetterboxReachabilityPositionFromConfig(Context context) {
+ int position = context.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForReachability);
+ return position == LETTERBOX_REACHABILITY_POSITION_LEFT
+ || position == LETTERBOX_REACHABILITY_POSITION_CENTER
+ || position == LETTERBOX_REACHABILITY_POSITION_RIGHT
+ ? position : LETTERBOX_REACHABILITY_POSITION_CENTER;
}
/*
* Gets horizontal position of a center of the letterboxed app window when reachability
* is enabled specified. 0 corresponds to the left side of the screen and 1 to the right side.
*
- * <p>The position multiplier is changed to a symmetrical value computed as (1 - current
- * multiplier) after each double tap in the letterbox area.
+ * <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getHorizontalMultiplierForReachability() {
- return mLetterboxHorizontalMultiplierForReachability;
+ switch (mLetterboxPositionForReachability) {
+ case LETTERBOX_REACHABILITY_POSITION_LEFT:
+ return 0.0f;
+ case LETTERBOX_REACHABILITY_POSITION_CENTER:
+ return 0.5f;
+ case LETTERBOX_REACHABILITY_POSITION_RIGHT:
+ return 1.0f;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox position type: " + mLetterboxPositionForReachability);
+ }
+ }
+
+ /** Returns a string representing the given {@link LetterboxReachabilityPosition}. */
+ static String letterboxReachabilityPositionToString(
+ @LetterboxReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_REACHABILITY_POSITION_LEFT:
+ return "LETTERBOX_REACHABILITY_POSITION_LEFT";
+ case LETTERBOX_REACHABILITY_POSITION_CENTER:
+ return "LETTERBOX_REACHABILITY_POSITION_CENTER";
+ case LETTERBOX_REACHABILITY_POSITION_RIGHT:
+ return "LETTERBOX_REACHABILITY_POSITION_RIGHT";
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox position type: " + position);
+ }
}
/**
- * Changes horizontal position of a center of the letterboxed app window to the opposite
- * (1 - current multiplier) when reachability is enabled specified. 0 corresponds to the left
- * side of the screen and 1 to the right side.
+ * Changes letterbox position for reachability to the next available one on the right side.
*/
- void flipHorizontalMultiplierForReachability() {
- mLetterboxHorizontalMultiplierForReachability =
- 1.0f - mLetterboxHorizontalMultiplierForReachability;
+ void movePositionForReachabilityToNextRightStop() {
+ mLetterboxPositionForReachability = Math.min(
+ mLetterboxPositionForReachability + 1, LETTERBOX_REACHABILITY_POSITION_RIGHT);
+ }
+
+ /**
+ * Changes letterbox position for reachability to the next available one on the left side.
+ */
+ void movePositionForReachabilityToNextLeftStop() {
+ mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0);
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index cf2afc9..7d07357 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -65,6 +65,10 @@
private final LetterboxConfiguration mLetterboxConfiguration;
private final ActivityRecord mActivityRecord;
+ // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
+ // corners above the taskbar.
+ private float mExpandedTaskBarHeight;
+
private boolean mShowWallpaperForLetterboxBackground;
@Nullable
@@ -76,6 +80,8 @@
// is created in its constructor. It shouldn't be used in this constructor but it's safe
// to use it after since controller is only used in ActivityRecord.
mActivityRecord = activityRecord;
+ mExpandedTaskBarHeight =
+ getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
}
/** Cleans up {@link Letterbox} if it exists.*/
@@ -204,12 +210,23 @@
return mActivityRecord.mWmService.mContext.getResources();
}
- private void handleDoubleTap() {
+ private void handleDoubleTap(int x) {
if (!isReachabilityEnabled() || mActivityRecord.isInTransition()) {
return;
}
- mLetterboxConfiguration.flipHorizontalMultiplierForReachability();
+ if (mLetterbox.getInnerFrame().left <= x && mLetterbox.getInnerFrame().right >= x) {
+ // Only react to clicks at the sides of the letterboxed app window.
+ return;
+ }
+
+ if (mLetterbox.getInnerFrame().left > x) {
+ // Moving to the next stop on the left side of the app window: right > center > left.
+ mLetterboxConfiguration.movePositionForReachabilityToNextLeftStop();
+ } else if (mLetterbox.getInnerFrame().right < x) {
+ // Moving to the next stop on the right side of the app window: left > center > right.
+ mLetterboxConfiguration.movePositionForReachabilityToNextRightStop();
+ }
// TODO(197549949): Add animation for transition.
mActivityRecord.recomputeConfiguration();
@@ -314,12 +331,27 @@
final InsetsSource taskbarInsetsSource =
insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- Rect cropBounds = new Rect(mActivityRecord.getBounds());
- // Activity bounds are in screen coordinates while (0,0) for activity's surface control
- // is at the top left corner of an app window so offsetting bounds accordingly.
- cropBounds.offsetTo(0, 0);
- // Rounded cornerners should be displayed above the taskbar.
- cropBounds.bottom = Math.min(cropBounds.bottom, taskbarInsetsSource.getFrame().top);
+ Rect cropBounds = null;
+
+ // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
+ // an insets frame is equal to a navigation bar which shouldn't affect position of
+ // rounded corners since apps are expected to handle navigation bar inset.
+ // This condition checks whether the taskbar is visible.
+ if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ cropBounds = new Rect(mActivityRecord.getBounds());
+ // Activity bounds are in screen coordinates while (0,0) for activity's surface
+ // control is at the top left corner of an app window so offsetting bounds
+ // accordingly.
+ cropBounds.offsetTo(0, 0);
+ // Rounded cornerners should be displayed above the taskbar.
+ cropBounds.bottom =
+ Math.min(cropBounds.bottom, taskbarInsetsSource.getFrame().top);
+ if (mActivityRecord.inSizeCompatMode()
+ && mActivityRecord.getSizeCompatScale() < 1.0f) {
+ cropBounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
+ }
+ }
+
transaction
.setWindowCrop(windowSurface, cropBounds)
.setCornerRadius(windowSurface, getRoundedCorners(insetsState));
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 03ff06c..a663c62 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -1125,21 +1125,11 @@
return mTargetActivityRecord.findMainWindow();
}
- /**
- * Returns the window with the highest layer, or null if none is found.
- */
- public WindowState getHighestLayerWindow() {
- int highestLayer = Integer.MIN_VALUE;
- Task highestLayerTask = null;
- for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- TaskAnimationAdapter adapter = mPendingAnimations.get(i);
- int layer = adapter.mTask.getPrefixOrderIndex();
- if (layer > highestLayer) {
- highestLayer = layer;
- highestLayerTask = adapter.mTask;
- }
+ DisplayArea getTargetAppDisplayArea() {
+ if (mTargetActivityRecord == null) {
+ return null;
}
- return highestLayerTask.getTopMostActivity().getTopChild();
+ return mTargetActivityRecord.getDisplayArea();
}
boolean isAnimatingTask(Task task) {
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 9c4f6f5..89986ce 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -72,8 +72,11 @@
*
* @param startBounds The original bounds (on screen) of the surface we are snapshotting.
* @param relativePosition The related position of the snapshot surface to its parent.
+ * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a
+ * snapshot from the {@link #mAnimatable} surface.
*/
- void freeze(SurfaceControl.Transaction t, Rect startBounds, Point relativePosition) {
+ void freeze(SurfaceControl.Transaction t, Rect startBounds, Point relativePosition,
+ @Nullable SurfaceControl freezeTarget) {
mFreezeBounds.set(startBounds);
mLeash = SurfaceAnimator.createAnimationLeash(mAnimatable, mAnimatable.getSurfaceControl(),
@@ -82,7 +85,7 @@
mWmService.mTransactionFactory);
mAnimatable.onAnimationLeashCreated(t, mLeash);
- SurfaceControl freezeTarget = mAnimatable.getFreezeSnapshotTarget();
+ freezeTarget = freezeTarget != null ? freezeTarget : mAnimatable.getFreezeSnapshotTarget();
if (freezeTarget != null) {
SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = createSnapshotBuffer(
freezeTarget, startBounds);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c5362d3..d89d212 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -480,7 +480,6 @@
private Dimmer mDimmer = new Dimmer(this);
private final Rect mTmpDimBoundsRect = new Rect();
- private final Point mLastSurfaceSize = new Point();
/** @see #setCanAffectSystemUiFlags */
private boolean mCanAffectSystemUiFlags = true;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index fce279d..2b5a820 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -240,6 +240,8 @@
*/
private int mTaskFragmentOrganizerPid = ActivityRecord.INVALID_PID;
+ final Point mLastSurfaceSize = new Point();
+
private final Rect mTmpInsets = new Rect();
private final Rect mTmpBounds = new Rect();
private final Rect mTmpFullBounds = new Rect();
@@ -1654,6 +1656,7 @@
}
}
+ @Override
void onChildPositionChanged(WindowContainer child) {
super.onChildPositionChanged(child);
@@ -2049,14 +2052,58 @@
if (shouldStartChangeTransition(mTmpPrevBounds)) {
initializeChangeTransition(mTmpPrevBounds);
} else if (mTaskFragmentOrganizer != null) {
- // Update the surface position here instead of in the organizer so that we can make sure
+ // Update the surface here instead of in the organizer so that we can make sure
// it can be synced with the surface freezer.
- updateSurfacePosition(getSyncTransaction());
+ final SurfaceControl.Transaction t = getSyncTransaction();
+ updateSurfacePosition(t);
+ updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
}
sendTaskFragmentInfoChanged();
}
+ /** Updates the surface size so that the sub windows cannot be shown out of bounds. */
+ private void updateOrganizedTaskFragmentSurfaceSize(SurfaceControl.Transaction t,
+ boolean forceUpdate) {
+ if (mTaskFragmentOrganizer == null) {
+ // We only want to update for organized TaskFragment. Task will handle itself.
+ return;
+ }
+ if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
+ return;
+ }
+
+ final Rect bounds = getBounds();
+ final int width = bounds.width();
+ final int height = bounds.height();
+ if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+ return;
+ }
+ t.setWindowCrop(mSurfaceControl, width, height);
+ mLastSurfaceSize.set(width, height);
+ }
+
+ @Override
+ public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
+ super.onAnimationLeashCreated(t, leash);
+ // Reset surface bounds for animation. It will be taken care by the animation leash, and
+ // reset again onAnimationLeashLost.
+ if (mTaskFragmentOrganizer != null
+ && (mLastSurfaceSize.x != 0 || mLastSurfaceSize.y != 0)) {
+ t.setWindowCrop(mSurfaceControl, 0, 0);
+ mLastSurfaceSize.set(0, 0);
+ }
+ }
+
+ @Override
+ public void onAnimationLeashLost(SurfaceControl.Transaction t) {
+ super.onAnimationLeashLost(t);
+ // Update the surface bounds after animation.
+ if (mTaskFragmentOrganizer != null) {
+ updateOrganizedTaskFragmentSurfaceSize(t, true /* forceUpdate */);
+ }
+ }
+
/** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
private boolean shouldStartChangeTransition(Rect startBounds) {
if (mWmService.mDisableTransitionAnimation
@@ -2075,9 +2122,14 @@
@Override
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
- // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
- // emit the callbacks now.
- sendTaskFragmentAppeared();
+ if (mTaskFragmentOrganizer != null) {
+ final SurfaceControl.Transaction t = getSyncTransaction();
+ updateSurfacePosition(t);
+ updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
+ // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
+ // emit the callbacks now.
+ sendTaskFragmentAppeared();
+ }
}
void sendTaskFragmentInfoChanged() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fe3e560..be00487 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2624,23 +2624,27 @@
* For now, this will only be called for the following cases:
* 1. {@link Task} is changing windowing mode between fullscreen and freeform.
* 2. {@link TaskFragment} is organized and is changing window bounds.
- * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}.
+ * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}. (The
+ * transition will happen on the {@link TaskFragment} for this case).
*
- * This shouldn't be called on other {@link WindowContainer} unless there is a valid use case.
+ * This shouldn't be called on other {@link WindowContainer} unless there is a valid
+ * use case.
*
* @param startBounds The original bounds (on screen) of the surface we are snapshotting.
- * @param parentBounds The parent bounds (on screen) to calculate the animation surface
- * position.
+ * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a
+ * snapshot from {@link #getFreezeSnapshotTarget()}.
*/
- void initializeChangeTransition(Rect startBounds, Rect parentBounds) {
+ void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
mDisplayContent.mChangingContainers.add(this);
+ // Calculate the relative position in parent container.
+ final Rect parentBounds = getParent().getBounds();
mTmpPoint.set(startBounds.left - parentBounds.left, startBounds.top - parentBounds.top);
- mSurfaceFreezer.freeze(getSyncTransaction(), startBounds, mTmpPoint);
+ mSurfaceFreezer.freeze(getSyncTransaction(), startBounds, mTmpPoint, freezeTarget);
}
void initializeChangeTransition(Rect startBounds) {
- initializeChangeTransition(startBounds, getParent().getBounds());
+ initializeChangeTransition(startBounds, null /* freezeTarget */);
}
ArraySet<WindowContainer> getAnimationSources() {
@@ -3163,7 +3167,7 @@
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
void updateSurfacePosition(Transaction t) {
- if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) {
+ if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e6615e1..50c3d3c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1772,17 +1772,15 @@
final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
- if (type == TYPE_APPLICATION_STARTING && activity != null) {
- activity.attachStartingWindow(win);
- ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
- activity, win);
- }
-
boolean imMayMove = true;
win.mToken.addWindow(win);
displayPolicy.addWindowLw(win, attrs);
- if (type == TYPE_INPUT_METHOD) {
+ if (type == TYPE_APPLICATION_STARTING && activity != null) {
+ activity.attachStartingWindow(win);
+ ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
+ activity, win);
+ } else if (type == TYPE_INPUT_METHOD) {
displayContent.setInputMethodWindowLocked(win);
imMayMove = false;
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 47d7f03..0f8587c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -23,6 +23,9 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_RIGHT;
import android.content.res.Resources.NotFoundException;
import android.graphics.Color;
@@ -44,6 +47,7 @@
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
+import com.android.server.wm.LetterboxConfiguration.LetterboxReachabilityPosition;
import java.io.IOException;
import java.io.PrintWriter;
@@ -787,22 +791,33 @@
return 0;
}
- private int runSetLetterboxDefaultPositionMultiplierForReachability(PrintWriter pw)
+ private int runSetLetterboxDefaultPositionForReachability(PrintWriter pw)
throws RemoteException {
- final float multiplier;
+ @LetterboxReachabilityPosition final int position;
try {
String arg = getNextArgRequired();
- multiplier = Float.parseFloat(arg);
- } catch (NumberFormatException e) {
- getErrPrintWriter().println("Error: bad multiplier format " + e);
- return -1;
+ switch (arg) {
+ case "left":
+ position = LETTERBOX_REACHABILITY_POSITION_LEFT;
+ break;
+ case "center":
+ position = LETTERBOX_REACHABILITY_POSITION_CENTER;
+ break;
+ case "right":
+ position = LETTERBOX_REACHABILITY_POSITION_RIGHT;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'left', 'center' or 'right' are expected as an argument");
+ return -1;
+ }
} catch (IllegalArgumentException e) {
getErrPrintWriter().println(
- "Error: multiplier should be provided as an argument " + e);
+ "Error: 'left', 'center' or 'right' are expected as an argument" + e);
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setDefaultPositionMultiplierForReachability(multiplier);
+ mLetterboxConfiguration.setDefaultPositionForReachability(position);
}
return 0;
}
@@ -841,8 +856,8 @@
case "--isReachabilityEnabled":
runSetLetterboxIsReachabilityEnabled(pw);
break;
- case "--defaultPositionMultiplierReachability":
- runSetLetterboxDefaultPositionMultiplierForReachability(pw);
+ case "--defaultPositionForReachability":
+ runSetLetterboxDefaultPositionForReachability(pw);
break;
default:
getErrPrintWriter().println(
@@ -885,8 +900,8 @@
case "isReachabilityEnabled":
mLetterboxConfiguration.getIsReachabilityEnabled();
break;
- case "defaultPositionMultiplierForReachability":
- mLetterboxConfiguration.getDefaultPositionMultiplierForReachability();
+ case "defaultPositionForReachability":
+ mLetterboxConfiguration.getDefaultPositionForReachability();
break;
default:
getErrPrintWriter().println(
@@ -982,7 +997,7 @@
mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
mLetterboxConfiguration.resetIsReachabilityEnabled();
- mLetterboxConfiguration.resetDefaultPositionMultiplierForReachability();
+ mLetterboxConfiguration.resetDefaultPositionForReachability();
}
}
@@ -996,8 +1011,9 @@
+ mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
pw.println("Is reachability enabled: "
+ mLetterboxConfiguration.getIsReachabilityEnabled());
- pw.println("Default position multiplier for reachability: "
- + mLetterboxConfiguration.getDefaultPositionMultiplierForReachability());
+ pw.println("Default position for reachability: "
+ + LetterboxConfiguration.letterboxReachabilityPositionToString(
+ mLetterboxConfiguration.getDefaultPositionForReachability()));
pw.println("Background type: "
+ LetterboxConfiguration.letterboxBackgroundTypeToString(
@@ -1135,11 +1151,9 @@
pw.println(" --isReachabilityEnabled [true|1|false|0]");
pw.println(" Whether reachability repositioning is allowed for letterboxed");
pw.println(" fullscreen apps in landscape device orientation.");
- pw.println(" --defaultPositionMultiplierReachability multiplier");
- pw.println(" Default horizontal position of app window center when reachability is");
- pw.println(" enabled. If multiplier < 0.0 or > 1, both it and ");
- pw.println(" R.dimen.config_letterboxDefaultPositionMultiplierForReachability");
- pw.println(" are ignored and right position (1.0) is used.");
+ pw.println(" --defaultPositionForReachability [left|center|right]");
+ pw.println(" Default horizontal position of app window when reachability is.");
+ pw.println(" enabled.");
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|isReachabilityEnabled");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ed98b9d..9268d82 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -308,6 +308,8 @@
@NonNull WindowToken mToken;
// The same object as mToken if this is an app window and null for non-app windows.
ActivityRecord mActivityRecord;
+ /** Non-null if this is a starting window. */
+ StartingData mStartingData;
// mAttrs.flags is tested in animation without being locked. If the bits tested are ever
// modified they will need to be locked.
@@ -5485,6 +5487,10 @@
return mWillReplaceWindow;
}
+ private boolean isStartingWindowAssociatedToTask() {
+ return mStartingData != null && mStartingData.mAssociatedTask != null;
+ }
+
private void applyDims() {
if (!mAnimatingExit && mAppDied) {
mIsDimming = true;
@@ -5634,7 +5640,9 @@
outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x,
-parent.mWindowFrames.mFrame.top + mTmpPoint.y);
} else if (parentWindowContainer != null) {
- final Rect parentBounds = parentWindowContainer.getBounds();
+ final Rect parentBounds = isStartingWindowAssociatedToTask()
+ ? mStartingData.mAssociatedTask.getBounds()
+ : parentWindowContainer.getBounds();
outPoint.offset(-parentBounds.left, -parentBounds.top);
}
@@ -5717,6 +5725,11 @@
@Override
void assignLayer(Transaction t, int layer) {
+ if (isStartingWindowAssociatedToTask()) {
+ // The starting window should cover the task.
+ t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+ return;
+ }
// See comment in assignRelativeLayerForImeTargetChild
if (needsRelativeLayeringToIme()) {
getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
@@ -5729,6 +5742,24 @@
return mIsDimming;
}
+ @Override
+ protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
+ if (isStartingWindowAssociatedToTask()) {
+ // Its surface is already put in task. Don't reparent when transferring starting window
+ // across activities.
+ return;
+ }
+ super.reparentSurfaceControl(t, newParent);
+ }
+
+ @Override
+ public SurfaceControl getAnimationLeashParent() {
+ if (isStartingWindowAssociatedToTask()) {
+ return mStartingData.mAssociatedTask.mSurfaceControl;
+ }
+ return super.getAnimationLeashParent();
+ }
+
// TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
// then we can drop all negative layering on the windowing side and simply inherit
// the default implementation here.
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ef9248a..8848098 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -53,6 +53,7 @@
"testng",
"ub-uiautomator",
"platformprotosnano",
+ "framework-protos",
"hamcrest-library",
"servicestests-utils",
"service-appsearch",
diff --git a/services/tests/servicestests/src/com/android/server/am/CriticalEventLogTest.java b/services/tests/servicestests/src/com/android/server/am/CriticalEventLogTest.java
new file mode 100644
index 0000000..903d7f2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/CriticalEventLogTest.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.framework.protobuf.nano.MessageNano;
+import com.android.server.am.CriticalEventLog.ILogLoader;
+import com.android.server.am.CriticalEventLog.LogLoader;
+import com.android.server.am.nano.CriticalEventLogProto;
+import com.android.server.am.nano.CriticalEventLogStorageProto;
+import com.android.server.am.nano.CriticalEventProto;
+import com.android.server.am.nano.CriticalEventProto.HalfWatchdog;
+import com.android.server.am.nano.CriticalEventProto.Watchdog;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * Test class for {@link CriticalEventLog}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:CriticalEventLogTest
+ */
+public class CriticalEventLogTest {
+ /** Epoch time when the critical event log is instantiated. */
+ private static final long START_TIME_MS = 1577880000000L; // 2020-01-01 12:00:00.000 UTC
+
+ /** Max number of events to include in the log. */
+ private static final int BUFFER_CAPACITY = 5;
+
+ /** Max age of events to include in the output log proto. */
+ private static final Duration LOG_WINDOW = Duration.ofMinutes(5);
+
+ /** How long to wait between consecutive saves of the log to disk. */
+ private static final Duration MIN_TIME_BETWEEN_SAVES = Duration.ofSeconds(2);
+
+ private static final String UUID_STRING = "123e4567-e89b-12d3-a456-556642440000";
+
+ @Rule
+ public TemporaryFolder mFolder = new TemporaryFolder();
+
+ private TestableCriticalEventLog mCriticalEventLog;
+ private File mTestFile;
+
+ @Before
+ public void setup() throws IOException {
+ mTestFile = mFolder.newFile(CriticalEventLog.FILENAME);
+ setLogInstance();
+ }
+
+ @Test
+ public void loadEvents_validContents() throws Exception {
+ createTestFileWithEvents(2);
+ setLogInstance(); // Log instance reads the proto file at initialization.
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+ assertProtoArrayEquals(
+ logProto.events,
+ new CriticalEventProto[]{
+ watchdog(START_TIME_MS - 2000, "Old watchdog 1"),
+ watchdog(START_TIME_MS - 1000, "Old watchdog 2"),
+ });
+ }
+
+ @Test
+ public void loadEvents_fileDoesntExist() {
+ mTestFile.delete();
+ setLogInstance();
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+ assertThat(logProto.events).isEmpty();
+ }
+
+ @Test
+ public void loadEvents_directoryDoesntExist() {
+ mFolder.delete();
+ setLogInstance();
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+ assertThat(logProto.events).isEmpty();
+ }
+
+ @Test
+ public void loadEvents_unreadable() throws Exception {
+ createTestFileWithEvents(1);
+ mTestFile.setReadable(false);
+ setLogInstance();
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+ assertThat(logProto.events).isEmpty();
+ }
+
+ @Test
+ public void loadEvents_malformedFile() throws Exception {
+ try (FileOutputStream stream = new FileOutputStream(mTestFile)) {
+ stream.write("This is not a proto file.".getBytes(StandardCharsets.UTF_8));
+ }
+ setLogInstance();
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+ assertThat(logProto.events).isEmpty();
+ }
+
+ @Test
+ public void loadEvents_emptyProto() throws Exception {
+ createTestFileWithEvents(0);
+ setLogInstance();
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+ assertThat(logProto.events).isEmpty();
+ }
+
+ @Test
+ public void loadEvents_numEventsExceedsBufferCapacity() throws Exception {
+ createTestFileWithEvents(10); // Ring buffer capacity is 5
+ setLogInstance();
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+ // Log contains the last 5 events only.
+ assertProtoArrayEquals(
+ logProto.events,
+ new CriticalEventProto[]{
+ watchdog(START_TIME_MS - 5000, "Old watchdog 6"),
+ watchdog(START_TIME_MS - 4000, "Old watchdog 7"),
+ watchdog(START_TIME_MS - 3000, "Old watchdog 8"),
+ watchdog(START_TIME_MS - 2000, "Old watchdog 9"),
+ watchdog(START_TIME_MS - 1000, "Old watchdog 10"),
+ });
+ }
+
+ @Test
+ public void logLinesForAnrFile() {
+ mCriticalEventLog.incTimeSeconds(1);
+ mCriticalEventLog.logWatchdog("Watchdog subject",
+ UUID.fromString("123e4567-e89b-12d3-a456-556642440000"));
+ mCriticalEventLog.incTimeSeconds(1);
+ mCriticalEventLog.logHalfWatchdog("Half watchdog subject");
+ mCriticalEventLog.incTimeSeconds(1);
+
+ assertThat(mCriticalEventLog.logLinesForAnrFile()).isEqualTo(
+ "--- CriticalEventLog ---\n"
+ + "capacity: 5\n"
+ + "events <\n"
+ + " timestamp_ms: 1577880001000\n"
+ + " watchdog <\n"
+ + " subject: \"Watchdog subject\"\n"
+ + " uuid: \"123e4567-e89b-12d3-a456-556642440000\"\n"
+ + " >\n"
+ + ">\n"
+ + "events <\n"
+ + " timestamp_ms: 1577880002000\n"
+ + " half_watchdog <\n"
+ + " subject: \"Half watchdog subject\"\n"
+ + " >\n"
+ + ">\n"
+ + "timestamp_ms: 1577880003000\n"
+ + "window_ms: 300000\n\n");
+ }
+
+ @Test
+ public void logWatchdog() {
+ mCriticalEventLog.incTimeSeconds(1);
+ mCriticalEventLog.logWatchdog("Subject 1",
+ UUID.fromString("123e4567-e89b-12d3-a456-556642440000"));
+ mCriticalEventLog.incTimeSeconds(1);
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS + 2000);
+ assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+ watchdog(START_TIME_MS + 1000, "Subject 1", "123e4567-e89b-12d3-a456-556642440000")
+ });
+ }
+
+ @Test
+ public void logHalfWatchdog() {
+ setLogInstance();
+ mCriticalEventLog.incTimeSeconds(1);
+ mCriticalEventLog.logHalfWatchdog("Subject 1");
+ mCriticalEventLog.incTimeSeconds(1);
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS + 2000);
+ assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+ halfWatchdog(START_TIME_MS + 1000, "Subject 1")
+ });
+ }
+
+ @Test
+ public void getOutputLogProto_numberOfEventsExceedsCapacity() {
+ // Log 10 events in 10 sec (capacity = 5)
+ for (int i = 0; i < 10; i++) {
+ mCriticalEventLog.logWatchdog("Subject " + i,
+ UUID.fromString(UUID_STRING));
+ mCriticalEventLog.incTimeSeconds(1);
+ }
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS + 10000);
+ assertThat(logProto.windowMs).isEqualTo(300_000); // 5 minutes
+ assertThat(logProto.capacity).isEqualTo(5);
+
+ // Only last 5 events are included in log output.
+ assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+ watchdog(START_TIME_MS + 5000, "Subject 5", UUID_STRING),
+ watchdog(START_TIME_MS + 6000, "Subject 6", UUID_STRING),
+ watchdog(START_TIME_MS + 7000, "Subject 7", UUID_STRING),
+ watchdog(START_TIME_MS + 8000, "Subject 8", UUID_STRING),
+ watchdog(START_TIME_MS + 9000, "Subject 9", UUID_STRING),
+ });
+ }
+
+ @Test
+ public void getOutputLogProto_logContainsOldEvents() {
+ long logTimestamp = START_TIME_MS + Duration.ofDays(1).toMillis();
+
+ // Old events (older than 5 mins)
+ mCriticalEventLog.setCurrentTimeMillis(logTimestamp - Duration.ofSeconds(302).toMillis());
+ mCriticalEventLog.logHalfWatchdog("Old event 1"); // 5m2s old
+ mCriticalEventLog.incTimeSeconds(1);
+ mCriticalEventLog.logHalfWatchdog("Old event 2"); // 5m1s old
+ mCriticalEventLog.incTimeSeconds(1);
+
+ // New events (5 mins old or less)
+ mCriticalEventLog.logHalfWatchdog("New event 1"); // 5m0s old
+ mCriticalEventLog.incTimeSeconds(1);
+ mCriticalEventLog.logHalfWatchdog("New event 2"); // 5m59s old
+
+ mCriticalEventLog.setCurrentTimeMillis(logTimestamp);
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ assertThat(logProto.timestampMs).isEqualTo(logTimestamp);
+ assertThat(logProto.windowMs).isEqualTo(300_000); // 5 minutes
+ assertThat(logProto.capacity).isEqualTo(5);
+
+ // Only events with age <= 5 min are included
+ assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+ halfWatchdog(logTimestamp - Duration.ofSeconds(300).toMillis(), "New event 1"),
+ halfWatchdog(logTimestamp - Duration.ofSeconds(299).toMillis(), "New event 2"),
+ });
+ }
+
+ @Test
+ public void getOutputLogProto_logHasNotBeenLoadedFromDiskYet() throws Exception {
+ createTestFileWithEvents(5);
+ setLogInstance(new NoOpLogLoader());
+
+ CriticalEventLogProto logProto = mCriticalEventLog.getRecentEvents();
+
+ // Output log is empty.
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS);
+ assertThat(logProto.events).isEmpty();
+ }
+
+ @Test
+ public void saveEventsToDiskNow() throws Exception {
+ mCriticalEventLog.incTimeSeconds(1);
+ mCriticalEventLog.logWatchdog("Watchdog subject", UUID.fromString(UUID_STRING));
+
+ mCriticalEventLog.incTimeSeconds(1);
+ mCriticalEventLog.logHalfWatchdog("Half watchdog subject");
+
+ // Don't need to call saveEventsToDiskNow since it's called after every event
+ // when mSaveImmediately = true.
+
+ CriticalEventLogStorageProto expected = new CriticalEventLogStorageProto();
+ expected.events = new CriticalEventProto[]{
+ watchdog(START_TIME_MS + 1000, "Watchdog subject", UUID_STRING),
+ halfWatchdog(START_TIME_MS + 2000, "Half watchdog subject")
+ };
+
+ assertThat(MessageNano.messageNanoEquals(getEventsWritten(), expected)).isTrue();
+ }
+
+ @Test
+ public void saveDelayMs() {
+ // First save has no delay
+ assertThat(mCriticalEventLog.saveDelayMs()).isEqualTo(0L);
+
+ // Save log, then next save delay is in 2s
+ mCriticalEventLog.saveLogToFileNow();
+ assertThat(mCriticalEventLog.saveDelayMs()).isEqualTo(2000L);
+ mCriticalEventLog.incTimeSeconds(1);
+ assertThat(mCriticalEventLog.saveDelayMs()).isEqualTo(1000L);
+
+ // Save again, save delay is 2s again.
+ mCriticalEventLog.saveLogToFileNow();
+ assertThat(mCriticalEventLog.saveDelayMs()).isEqualTo(2000L);
+ }
+
+ @Test
+ public void simulateReboot_saveAndLoadCycle() {
+ TestableCriticalEventLog log1 = setLogInstance();
+
+ // Log 8 events
+ for (int i = 0; i < 8; i++) {
+ log1.logHalfWatchdog("Old subject " + i);
+ log1.incTimeSeconds(1);
+ }
+
+ // Simulate reboot by making new log instance.
+ TestableCriticalEventLog log2 = setLogInstance();
+ assertThat(log1).isNotSameInstanceAs(log2);
+
+ // Log one more event
+ log2.setCurrentTimeMillis(START_TIME_MS + 20_000);
+ log2.logHalfWatchdog("New subject");
+ log2.incTimeSeconds(1);
+
+ CriticalEventLogProto logProto = log2.getRecentEvents();
+
+ // Log contains 4 + 1 events.
+ assertThat(logProto.timestampMs).isEqualTo(START_TIME_MS + 21_000);
+ assertProtoArrayEquals(logProto.events, new CriticalEventProto[]{
+ halfWatchdog(START_TIME_MS + 4000, "Old subject 4"),
+ halfWatchdog(START_TIME_MS + 5000, "Old subject 5"),
+ halfWatchdog(START_TIME_MS + 6000, "Old subject 6"),
+ halfWatchdog(START_TIME_MS + 7000, "Old subject 7"),
+ halfWatchdog(START_TIME_MS + 20_000, "New subject")
+ });
+ }
+
+ private CriticalEventLogStorageProto getEventsWritten() throws IOException {
+ return CriticalEventLogStorageProto.parseFrom(
+ Files.readAllBytes(mTestFile.toPath()));
+ }
+
+ /**
+ * Creates a log file containing some watchdog events.
+ *
+ * They occur at a rate of one per second, with the last at 1 sec before START_TIME_MS.
+ */
+ private void createTestFileWithEvents(int numEvents) throws Exception {
+ CriticalEventLogStorageProto log = new CriticalEventLogStorageProto();
+ log.events = new CriticalEventProto[numEvents];
+ long startTimeMs = START_TIME_MS - (numEvents * 1000L);
+
+ for (int i = 0; i < numEvents; i++) {
+ long timestampMs = startTimeMs + (i * 1000L);
+ String subject = String.format("Old watchdog %d", i + 1);
+ log.events[i] = watchdog(timestampMs, subject);
+ }
+
+ try (FileOutputStream stream = new FileOutputStream(mTestFile)) {
+ stream.write(CriticalEventLogStorageProto.toByteArray(log));
+ }
+ }
+
+ private CriticalEventProto watchdog(long timestampMs, String subject) {
+ return watchdog(timestampMs, subject, "A UUID");
+ }
+
+ private CriticalEventProto watchdog(long timestampMs, String subject, String uuid) {
+ CriticalEventProto event = new CriticalEventProto();
+ event.timestampMs = timestampMs;
+ event.setWatchdog(new Watchdog());
+ event.getWatchdog().subject = subject;
+ event.getWatchdog().uuid = uuid;
+ return event;
+ }
+
+ private CriticalEventProto halfWatchdog(long timestampMs, String subject) {
+ CriticalEventProto event = new CriticalEventProto();
+ event.timestampMs = timestampMs;
+ event.setHalfWatchdog(new HalfWatchdog());
+ event.getHalfWatchdog().subject = subject;
+ return event;
+ }
+
+ private static void assertProtoArrayEquals(MessageNano[] actual, MessageNano[] expected) {
+ assertThat(expected).isNotNull();
+ assertThat(actual).isNotNull();
+
+ String message =
+ "Expected:\n" + Arrays.toString(expected) + "\nGot:\n" + Arrays.toString(actual);
+ assertWithMessage(message).that(expected.length).isEqualTo(actual.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertWithMessage(message).that(
+ MessageNano.messageNanoEquals(expected[i], actual[i])).isTrue();
+ }
+ }
+
+ private TestableCriticalEventLog setLogInstance() {
+ return setLogInstance(new LogLoader());
+ }
+
+ private TestableCriticalEventLog setLogInstance(ILogLoader logLoader) {
+ mCriticalEventLog = new TestableCriticalEventLog(mFolder.getRoot().getAbsolutePath(),
+ logLoader);
+ return mCriticalEventLog;
+ }
+
+ private static class TestableCriticalEventLog extends CriticalEventLog {
+ private long mNowMillis = START_TIME_MS;
+
+ TestableCriticalEventLog(String logDir, ILogLoader logLoader) {
+ super(logDir,
+ BUFFER_CAPACITY,
+ (int) LOG_WINDOW.toMillis(),
+ MIN_TIME_BETWEEN_SAVES.toMillis(),
+ /* loadAndSaveImmediately= */ true,
+ logLoader);
+ }
+
+ @Override
+ protected long getWallTimeMillis() {
+ return mNowMillis;
+ }
+
+ void incTimeSeconds(int seconds) {
+ mNowMillis += (seconds * 1000L);
+ }
+
+ void setCurrentTimeMillis(long millis) {
+ mNowMillis = millis;
+ }
+ }
+
+ /**
+ * A log loader that does nothing.
+ *
+ * Used to check behaviour when log loading is slow since the loading happens
+ * asynchronously.
+ */
+ private static class NoOpLogLoader implements ILogLoader {
+ @Override
+ public void load(File logFile,
+ CriticalEventLog.ThreadSafeRingBuffer<CriticalEventProto> buffer) {
+ // Do nothing.
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index f3070b6..8137d12 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -65,31 +65,13 @@
private static final byte[] POWER_ON = new byte[] { POWER_STATUS_ON };
private static final byte[] POWER_STANDBY = new byte[] { POWER_STATUS_STANDBY };
private static final byte[] POWER_TRANSIENT_TO_ON = new byte[] { POWER_STATUS_TRANSIENT_TO_ON };
- private static final HdmiCecMessage REPORT_POWER_STATUS_ON = new HdmiCecMessage(
- ADDR_PLAYBACK_2, ADDR_PLAYBACK_1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON);
- private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = new HdmiCecMessage(
- ADDR_PLAYBACK_2, ADDR_PLAYBACK_1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY);
- private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = new HdmiCecMessage(
- ADDR_PLAYBACK_2, ADDR_PLAYBACK_1, Constants.MESSAGE_REPORT_POWER_STATUS,
- POWER_TRANSIENT_TO_ON);
- private static final HdmiCecMessage SET_STREAM_PATH = HdmiCecMessageBuilder.buildSetStreamPath(
- ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_2);
- private static final HdmiCecMessage ROUTING_CHANGE = HdmiCecMessageBuilder.buildRoutingChange(
- ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_2);
- private static final HdmiCecMessage ACTIVE_SOURCE = HdmiCecMessageBuilder.buildActiveSource(
- ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2);
- private static final HdmiDeviceInfo INFO_PLAYBACK_1 = new HdmiDeviceInfo(
- ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1, HdmiDeviceInfo.DEVICE_PLAYBACK,
- 0x1234, "Playback 1",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- private static final HdmiDeviceInfo INFO_PLAYBACK_2 = new HdmiDeviceInfo(
- ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2, HdmiDeviceInfo.DEVICE_PLAYBACK,
- 0x1234, "Playback 2",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- private static final HdmiDeviceInfo INFO_PLAYBACK_3 = new HdmiDeviceInfo(
- ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3, PORT_3, HdmiDeviceInfo.DEVICE_PLAYBACK,
- 0x1234, "Playback 3",
- HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+
+ private HdmiCecMessage mReportPowerStatusOn;
+ private HdmiCecMessage mReportPowerStatusStandby;
+ private HdmiCecMessage mReportPowerStatusTransientToOn;
+ private HdmiCecMessage mSetStreamPath;
+ private HdmiCecMessage mRoutingChange;
+ private HdmiCecMessage mActiveSource;
private HdmiCecController mHdmiCecController;
private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
@@ -103,6 +85,10 @@
private TestLooper mTestLooper = new TestLooper();
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private int mPlaybackLogicalAddress1;
+ private int mPlaybackLogicalAddress2;
+ private int mPlaybackLogicalAddress3;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -157,9 +143,54 @@
mTestLooper.dispatchAll();
mNativeWrapper.clearResultMessages();
- mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_1);
- mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_2);
- mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_3);
+ // The addresses depend on local device's LA.
+ // This help the tests to pass with every local device LA.
+ synchronized (mHdmiCecLocalDevicePlayback.mLock) {
+ mPlaybackLogicalAddress1 =
+ mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress();
+ }
+ mPlaybackLogicalAddress2 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_2
+ ? ADDR_PLAYBACK_1 : ADDR_PLAYBACK_2;
+ mPlaybackLogicalAddress3 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_3
+ ? ADDR_PLAYBACK_1 : ADDR_PLAYBACK_3;
+
+ mReportPowerStatusOn = new HdmiCecMessage(
+ mPlaybackLogicalAddress2, mPlaybackLogicalAddress1,
+ Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON);
+ mReportPowerStatusStandby = new HdmiCecMessage(
+ mPlaybackLogicalAddress2, mPlaybackLogicalAddress1,
+ Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY);
+ mReportPowerStatusTransientToOn = new HdmiCecMessage(
+ mPlaybackLogicalAddress2, mPlaybackLogicalAddress1,
+ Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON);
+ mSetStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+ mPlaybackLogicalAddress1, PHYSICAL_ADDRESS_PLAYBACK_2);
+ mRoutingChange = HdmiCecMessageBuilder.buildRoutingChange(
+ mPlaybackLogicalAddress1, PHYSICAL_ADDRESS_PLAYBACK_3,
+ PHYSICAL_ADDRESS_PLAYBACK_2);
+ mActiveSource = HdmiCecMessageBuilder.buildActiveSource(
+ mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2);
+
+ HdmiDeviceInfo infoPlayback1 = new HdmiDeviceInfo(
+ mPlaybackLogicalAddress1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1,
+ HdmiDeviceInfo.DEVICE_PLAYBACK,
+ 0x1234, "Playback 1",
+ HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+ HdmiDeviceInfo infoPlayback2 = new HdmiDeviceInfo(
+ mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2,
+ HdmiDeviceInfo.DEVICE_PLAYBACK,
+ 0x1234, "Playback 2",
+ HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+ HdmiDeviceInfo infoPlayback3 = new HdmiDeviceInfo(
+ mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3, PORT_3,
+ HdmiDeviceInfo.DEVICE_PLAYBACK,
+ 0x1234, "Playback 3",
+ HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback1);
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback2);
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback3);
+
}
private static class TestActionTimer implements ActionTimer {
@@ -198,7 +229,7 @@
TestCallback callback,
boolean isCec20) {
HdmiDeviceInfo hdmiDeviceInfo =
- mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(ADDR_PLAYBACK_2);
+ mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(mPlaybackLogicalAddress2);
DeviceSelectActionFromPlayback action = new DeviceSelectActionFromPlayback(
mHdmiCecLocalDevicePlayback,
hdmiDeviceInfo, callback, isCec20);
@@ -214,23 +245,23 @@
TestCallback callback = new TestCallback();
DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
callback, /*isCec20=*/false);
- mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+ mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
"testDeviceSelectFromPlayback");
action.start();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_ON);
+ action.processCommand(mReportPowerStatusOn);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
- action.processCommand(ACTIVE_SOURCE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+ action.processCommand(mActiveSource);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@Test
public void testDeviceSelect_DeviceInStandbyStatus_Cec14b() {
- mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+ mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
"testDeviceSelectFromPlayback");
TestActionTimer actionTimer = new TestActionTimer();
TestCallback callback = new TestCallback();
@@ -238,25 +269,25 @@
callback, /*isCec20=*/false);
action.start();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_STANDBY);
+ action.processCommand(mReportPowerStatusStandby);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
- action.processCommand(REPORT_POWER_STATUS_ON);
+ action.processCommand(mReportPowerStatusOn);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
- action.processCommand(ACTIVE_SOURCE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+ action.processCommand(mActiveSource);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@Test
public void testDeviceSelect_DeviceInStandbyStatusWithSomeTimeouts_Cec14b() {
- mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+ mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
"testDeviceSelectFromPlayback");
TestActionTimer actionTimer = new TestActionTimer();
TestCallback callback = new TestCallback();
@@ -264,27 +295,27 @@
callback, /*isCec20=*/false);
action.start();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_STANDBY);
+ action.processCommand(mReportPowerStatusStandby);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_TRANSIENT_TO_ON);
+ action.processCommand(mReportPowerStatusTransientToOn);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_ON);
+ action.processCommand(mReportPowerStatusOn);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
- action.processCommand(ACTIVE_SOURCE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+ action.processCommand(mActiveSource);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@Test
public void testDeviceSelect_DeviceInStandbyAfterTimeoutForReportPowerStatus_Cec14b() {
- mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+ mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
"testDeviceSelectFromPlayback");
TestActionTimer actionTimer = new TestActionTimer();
TestCallback callback = new TestCallback();
@@ -292,118 +323,118 @@
callback, /*isCec20=*/false);
action.start();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_STANDBY);
+ action.processCommand(mReportPowerStatusStandby);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_TRANSIENT_TO_ON);
+ action.processCommand(mReportPowerStatusTransientToOn);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
action.handleTimerEvent(STATE_WAIT_FOR_REPORT_POWER_STATUS);
// Give up getting power status, and just send <Routing Change>
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
- action.processCommand(ACTIVE_SOURCE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+ action.processCommand(mActiveSource);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@Test
- public void testDeviceSelect_ReachSetStreamPath_Cec14b() {
+ public void testDeviceSelect_ReachmSetStreamPath_Cec14b() {
TestActionTimer actionTimer = new TestActionTimer();
TestCallback callback = new TestCallback();
DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
callback, /*isCec20=*/false);
- mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+ mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
"testDeviceSelectFromPlayback");
action.start();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_ON);
+ action.processCommand(mReportPowerStatusOn);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(
STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE);
action.handleTimerEvent(STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
- action.processCommand(ACTIVE_SOURCE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mSetStreamPath);
+ action.processCommand(mActiveSource);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@Test
- public void testDeviceSelect_ReachSetStreamPathDeviceInPowerOnStatus_Cec20() {
- mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_2,
+ public void testDeviceSelect_ReachmSetStreamPathDeviceInPowerOnStatus_Cec20() {
+ mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(mPlaybackLogicalAddress2,
HdmiControlManager.POWER_STATUS_ON);
TestActionTimer actionTimer = new TestActionTimer();
TestCallback callback = new TestCallback();
DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
callback, /*isCec20=*/true);
- mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+ mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
"testDeviceSelectFromPlayback");
action.start();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(
STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE);
action.handleTimerEvent(STATE_WAIT_FOR_ACTIVE_SOURCE_MESSAGE_AFTER_ROUTING_CHANGE);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
- action.processCommand(ACTIVE_SOURCE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mSetStreamPath);
+ action.processCommand(mActiveSource);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@Test
public void testDeviceSelect_DeviceInPowerOnStatus_Cec20() {
- mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_2,
+ mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(mPlaybackLogicalAddress2,
HdmiControlManager.POWER_STATUS_ON);
TestActionTimer actionTimer = new TestActionTimer();
TestCallback callback = new TestCallback();
DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
callback, /*isCec20=*/true);
- mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+ mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
"testDeviceSelectFromPlayback");
action.start();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
- action.processCommand(ACTIVE_SOURCE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+ action.processCommand(mActiveSource);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@Test
public void testDeviceSelect_DeviceInPowerUnknownStatus_Cec20() {
- mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_2,
+ mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(mPlaybackLogicalAddress2,
HdmiControlManager.POWER_STATUS_UNKNOWN);
TestActionTimer actionTimer = new TestActionTimer();
TestCallback callback = new TestCallback();
DeviceSelectActionFromPlayback action = createDeviceSelectActionFromPlayback(actionTimer,
callback, /*isCec20=*/true);
- mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+ mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
"testDeviceSelectFromPlayback");
action.start();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_ON);
+ action.processCommand(mReportPowerStatusOn);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
- action.processCommand(ACTIVE_SOURCE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+ action.processCommand(mActiveSource);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@Test
public void testDeviceSelect_DeviceInStandbyStatus_Cec20() {
- mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_2,
+ mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(mPlaybackLogicalAddress2,
HdmiControlManager.POWER_STATUS_STANDBY);
- mHdmiControlService.setActiveSource(ADDR_PLAYBACK_3, PHYSICAL_ADDRESS_PLAYBACK_3,
+ mHdmiControlService.setActiveSource(mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3,
"testDeviceSelectFromPlayback");
TestActionTimer actionTimer = new TestActionTimer();
TestCallback callback = new TestCallback();
@@ -411,18 +442,18 @@
callback, /*isCec20=*/true);
action.start();
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
- action.processCommand(REPORT_POWER_STATUS_STANDBY);
+ action.processCommand(mReportPowerStatusStandby);
mTestLooper.dispatchAll();
mNativeWrapper.clearResultMessages();
assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
- action.processCommand(REPORT_POWER_STATUS_ON);
+ action.processCommand(mReportPowerStatusOn);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).contains(ROUTING_CHANGE);
- action.processCommand(ACTIVE_SOURCE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(mRoutingChange);
+ action.processCommand(mActiveSource);
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 7acf946..372fd60 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -52,6 +52,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(JUnit4.class)
@@ -522,4 +523,31 @@
verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt());
}
+
+ /**
+ * Tests that receiving a message from a device does not prevent it from being discovered
+ * by HotplugDetectionAction.
+ */
+ @Test
+ public void hotplugDetectionAction_discoversDeviceAfterMessageReceived() {
+ // Playback 1 sends a message before ACKing a poll
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.NACK);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildActiveSource(
+ ADDR_PLAYBACK_1, ADDR_TV);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ // Playback 1 begins ACKing polls, allowing detection by HotplugDetectionAction
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+ for (int pollCount = 0; pollCount < HotplugDetectionAction.TIMEOUT_COUNT; pollCount++) {
+ mTestLooper.moveTimeForward(
+ TimeUnit.SECONDS.toMillis(HotplugDetectionAction.POLLING_INTERVAL_MS_FOR_TV));
+ mTestLooper.dispatchAll();
+ }
+
+ // Device sends <Give Physical Address> to Playback 1 after detecting it
+ HdmiCecMessage givePhysicalAddress = HdmiCecMessageBuilder.buildGivePhysicalAddress(
+ ADDR_TV, ADDR_PLAYBACK_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(givePhysicalAddress);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c434b13..65733d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2700,8 +2700,8 @@
final WindowState startingWindow = createWindowState(
new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), activity1);
activity1.addWindow(startingWindow);
- activity1.attachStartingWindow(startingWindow);
activity1.mStartingData = mock(StartingData.class);
+ activity1.attachStartingWindow(startingWindow);
final Task task = activity1.getTask();
final Rect taskBounds = task.getBounds();
final int width = taskBounds.width();
@@ -2729,6 +2729,10 @@
assertTrue(activity2.isResizeable());
activity1.reparent(taskFragment1, POSITION_TOP);
+ verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
+ eq(task.mSurfaceControl));
+ assertEquals(activity1.mStartingData, startingWindow.mStartingData);
+ assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
assertEquals(task, activity1.mStartingData.mAssociatedTask);
assertEquals(taskFragment1.getBounds(), activity1.getBounds());
// The activity was resized by task fragment, but starting window must still cover the task.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index d6d7f07..5fa76bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -27,6 +27,8 @@
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -44,6 +46,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.annotation.Nullable;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -763,59 +766,148 @@
}
@Test
- public void testGetRemoteAnimationOverrideTaskFragmentOrganizer() {
- // TaskFragmentOrganizer registers remote animation.
+ public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
- final ITaskFragmentOrganizer iOrganizer =
- ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
- final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
new TestRemoteAnimationRunner(), 10, 1);
- definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
- mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
- mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
// Create a TaskFragment with embedded activity.
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(createTask(mDisplayContent))
- .createActivityCount(1)
- .setOrganizer(organizer)
- .build();
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+ createTask(mDisplayContent), organizer);
final ActivityRecord activity = taskFragment.getTopMostActivity();
activity.allDrawn = true;
spyOn(mDisplayContent.mAppTransition);
- // Prepare a transition for TaskFragment.
- mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
- mDisplayContent.mOpeningApps.add(activity);
- mDisplayContent.mChangingContainers.add(taskFragment);
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- // Check if the transition has been overridden.
+ // Should be overridden.
verify(mDisplayContent.mAppTransition)
.overridePendingAppTransitionRemote(adapter, false /* sync */);
}
@Test
+ public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ final Task task = createTask(mDisplayContent);
+ // Closing non-embedded activity.
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ closingActivity.allDrawn = true;
+ // Opening TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ openingActivity.allDrawn = true;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+
+ // Should be overridden.
+ verify(mDisplayContent.mAppTransition)
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ final Task task = createTask(mDisplayContent);
+ // Closing TaskFragment with embedded activity.
+ final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
+ closingActivity.allDrawn = true;
+ closingActivity.info.applicationInfo.uid = 12345;
+ // Opening TaskFragment with embedded activity with different UID.
+ final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord openingActivity = taskFragment2.getTopMostActivity();
+ openingActivity.info.applicationInfo.uid = 54321;
+ openingActivity.allDrawn = true;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
+
+ // Should be overridden.
+ verify(mDisplayContent.mAppTransition)
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ // Closing activity in Task1.
+ final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
+ closingActivity.allDrawn = true;
+ // Opening TaskFragment with embedded activity in Task2.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+ createTask(mDisplayContent), organizer);
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ openingActivity.allDrawn = true;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition for TaskFragment.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+
+ // Should not be overridden.
+ verify(mDisplayContent.mAppTransition, never())
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ final Task task = createTask(mDisplayContent);
+ // Closing TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
+ closingActivity.allDrawn = true;
+ closingActivity.info.applicationInfo.uid = 12345;
+ // Opening non-embedded activity with different UID.
+ final ActivityRecord openingActivity = createActivityRecord(task);
+ openingActivity.info.applicationInfo.uid = 54321;
+ openingActivity.allDrawn = true;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+
+ // Should not be overridden
+ verify(mDisplayContent.mAppTransition, never())
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
public void testTransitionGoodToGoForTaskFragments() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = createTask(mDisplayContent);
- final TaskFragment changeTaskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .createActivityCount(1)
- .setOrganizer(organizer)
- .build();
+ final TaskFragment changeTaskFragment =
+ createTaskFragmentWithEmbeddedActivity(task, organizer);
final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setOrganizer(organizer)
.build();
changeTaskFragment.getTopMostActivity().allDrawn = true;
- mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
- mDisplayContent.mChangingContainers.add(changeTaskFragment);
spyOn(mDisplayContent.mAppTransition);
spyOn(emptyTaskFragment);
- mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+ prepareAndTriggerAppTransition(
+ null /* openingActivity */, null /* closingActivity*/, changeTaskFragment);
// Transition not ready because there is an empty non-finishing TaskFragment.
verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
@@ -829,4 +921,34 @@
// removed.
verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
}
+
+ /** Registers remote animation for the organizer. */
+ private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
+ RemoteAnimationAdapter adapter) {
+ final ITaskFragmentOrganizer iOrganizer =
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+ final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
+ mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
+ }
+
+ private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity,
+ @Nullable ActivityRecord closingActivity, @Nullable TaskFragment changingTaskFragment) {
+ if (openingActivity != null) {
+ mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+ mDisplayContent.mOpeningApps.add(openingActivity);
+ }
+ if (closingActivity != null) {
+ mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CLOSE, 0);
+ mDisplayContent.mClosingApps.add(closingActivity);
+ }
+ if (changingTaskFragment != null) {
+ mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
+ mDisplayContent.mChangingContainers.add(changingTaskFragment);
+ }
+ mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+ }
}
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 405d714..fb8bc7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -35,6 +35,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
@@ -42,6 +44,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
import android.graphics.Rect;
import android.os.Binder;
@@ -54,6 +57,7 @@
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentOrganizer;
@@ -397,7 +401,9 @@
@Test
public void testActivityRecordReparentToTaskFragment() {
final ActivityRecord activity = createActivityRecord(mDc);
+ final SurfaceControl activityLeash = mock(SurfaceControl.class);
activity.setVisibility(true);
+ activity.setSurfaceControl(activityLeash);
final Task task = activity.getTask();
// Add a TaskFragment of half of the Task size.
@@ -412,15 +418,20 @@
final Rect taskBounds = new Rect();
task.getBounds(taskBounds);
taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
+ spyOn(taskFragment);
assertTrue(mDc.mChangingContainers.isEmpty());
assertFalse(mDc.mAppTransition.isTransitionSet());
// Schedule app transition when reparent activity to a TaskFragment of different size.
+ final Rect startBounds = new Rect(activity.getBounds());
activity.reparent(taskFragment, POSITION_TOP);
- assertTrue(mDc.mChangingContainers.contains(activity));
+ // It should transit at TaskFragment level with snapshot on the activity surface.
+ verify(taskFragment).initializeChangeTransition(activity.getBounds(), activityLeash);
+ assertTrue(mDc.mChangingContainers.contains(taskFragment));
assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
+ assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
}
private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index 78946fc..1e86522 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -63,7 +63,7 @@
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
() -> mAreCornersRounded, () -> Color.valueOf(mColor),
() -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
- /* doubleTapCallback= */ () -> {});
+ /* doubleTapCallback= */ x -> {});
mTransaction = spy(StubTransaction.class);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
new file mode 100644
index 0000000..cb209abf
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.Mockito.clearInvocations;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link TaskFragment}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:TaskFragmentTest
+ */
+@MediumTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskFragmentTest extends WindowTestsBase {
+
+ private TaskFragmentOrganizer mOrganizer;
+ private TaskFragment mTaskFragment;
+ private SurfaceControl mLeash;
+ @Mock
+ private SurfaceControl.Transaction mTransaction;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mOrganizer = new TaskFragmentOrganizer(Runnable::run);
+ final ITaskFragmentOrganizer iOrganizer =
+ ITaskFragmentOrganizer.Stub.asInterface(mOrganizer.getOrganizerToken().asBinder());
+ mAtm.mWindowOrganizerController.mTaskFragmentOrganizerController
+ .registerOrganizer(iOrganizer);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .setOrganizer(mOrganizer)
+ .build();
+ mLeash = mTaskFragment.getSurfaceControl();
+ spyOn(mTaskFragment);
+ doReturn(mTransaction).when(mTaskFragment).getSyncTransaction();
+ doReturn(mTransaction).when(mTaskFragment).getPendingTransaction();
+ }
+
+ @Test
+ public void testOnConfigurationChanged_updateSurface() {
+ final Rect bounds = new Rect(100, 100, 1100, 1100);
+ mTaskFragment.setBounds(bounds);
+
+ verify(mTransaction).setPosition(mLeash, 100, 100);
+ verify(mTransaction).setWindowCrop(mLeash, 1000, 1000);
+ }
+
+ @Test
+ public void testStartChangeTransition_resetSurface() {
+ final Rect startBounds = new Rect(0, 0, 1000, 1000);
+ final Rect endBounds = new Rect(500, 500, 1000, 1000);
+ mTaskFragment.setBounds(startBounds);
+ doReturn(true).when(mTaskFragment).isVisible();
+
+ clearInvocations(mTransaction);
+ mTaskFragment.setBounds(endBounds);
+
+ // Surface reset when prepare transition.
+ verify(mTaskFragment).initializeChangeTransition(startBounds);
+ verify(mTransaction).setPosition(mLeash, 0, 0);
+ verify(mTransaction).setWindowCrop(mLeash, 0, 0);
+
+ clearInvocations(mTransaction);
+ mTaskFragment.mSurfaceFreezer.unfreeze(mTransaction);
+
+ // Update surface after animation.
+ verify(mTransaction).setPosition(mLeash, 500, 500);
+ verify(mTransaction).setWindowCrop(mLeash, 500, 500);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 6626aa4..8ec1bd6c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -699,6 +699,15 @@
return builder.build();
}
+ static TaskFragment createTaskFragmentWithEmbeddedActivity(@NonNull Task parentTask,
+ TaskFragmentOrganizer organizer) {
+ return new TaskFragmentBuilder(parentTask.mAtmService)
+ .setParentTask(parentTask)
+ .createActivityCount(1)
+ .setOrganizer(organizer)
+ .build();
+ }
+
/** Creates a {@link DisplayContent} that supports IME and adds it to the system. */
DisplayContent createNewDisplay() {
return createNewDisplayWithImeSupport(DISPLAY_IME_POLICY_LOCAL);
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 4d81b5e..7a424c8 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -308,6 +308,12 @@
return checkPrivilegedReadPermissionOrCarrierPrivilegePermission(
context, subId, callingPackage, callingFeatureId, message, false, reportFailure);
}
+
+ private static void throwSecurityExceptionAsUidDoesNotHaveAccess(String message, int uid) {
+ throw new SecurityException(message + ": The uid " + uid
+ + " does not meet the requirements to access device identifiers.");
+ }
+
/**
* Checks whether the app with the given pid/uid can read device identifiers.
*
@@ -343,9 +349,14 @@
LegacyPermissionManager permissionManager = (LegacyPermissionManager)
context.getSystemService(Context.LEGACY_PERMISSION_SERVICE);
- if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message, callingFeatureId,
- pid, uid) == PackageManager.PERMISSION_GRANTED) {
- return true;
+ try {
+ if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message,
+ callingFeatureId,
+ pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ } catch (SecurityException se) {
+ throwSecurityExceptionAsUidDoesNotHaveAccess(message, uid);
}
if (reportFailure) {
@@ -410,8 +421,8 @@
return false;
}
}
- throw new SecurityException(message + ": The user " + uid
- + " does not meet the requirements to access device identifiers.");
+ throwSecurityExceptionAsUidDoesNotHaveAccess(message, uid);
+ return true;
}
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 4d4f266..fd8abc6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.rotation
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -25,11 +26,13 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -78,6 +81,9 @@
class ChangeAppRotationTest(
testSpec: FlickerTestParameter
) : RotationTransition(testSpec) {
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
+
override val testApp = SimpleAppHelper(instrumentation)
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
@@ -89,6 +95,24 @@
}
}
+ @Postsubmit
+ @Test
+ fun runPresubmitAssertion() {
+ flickerRule.checkPresubmitAssertions()
+ }
+
+ @Postsubmit
+ @Test
+ fun runPostsubmitAssertion() {
+ flickerRule.checkPostsubmitAssertions()
+ }
+
+ @FlakyTest
+ @Test
+ fun runFlakyAssertion() {
+ flickerRule.checkFlakyAssertions()
+ }
+
/** {@inheritDoc} */
@FlakyTest(bugId = 190185577)
@Test
@@ -109,6 +133,7 @@
.isVisible(FlickerComponentName.ROTATION)
.then()
.isVisible(testApp.component)
+ .isInvisible(FlickerComponentName.ROTATION)
}
}