diff options
113 files changed, 4613 insertions, 1869 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 3c2ae5a03004..371177c8b0a5 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -1220,6 +1220,17 @@ java_aconfig_library { } java_aconfig_library { + name: "device_policy_aconfig_flags_java_export", + aconfig_declarations: "device_policy_aconfig_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "//apex_available:platform", + "com.android.permission", + ], +} + +java_aconfig_library { name: "device_policy_aconfig_flags_lib_host", aconfig_declarations: "device_policy_aconfig_flags", host_supported: true, diff --git a/core/api/current.txt b/core/api/current.txt index 1b429776244c..523ce8e87ee8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -17082,7 +17082,7 @@ package android.graphics { method public void arcTo(@NonNull android.graphics.RectF, float, float); method public void arcTo(float, float, float, float, float, float, boolean); method public void close(); - method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean); + method public void computeBounds(@NonNull android.graphics.RectF, boolean); method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF); method public void conicTo(float, float, float, float, float); method public void cubicTo(float, float, float, float, float, float); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0a3ecef8ecad..50d2bfea87dd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -78,6 +78,7 @@ package android { field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_INTELLIGENCE_SERVICE = "android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE"; field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE = "android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE"; field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"; + field @FlaggedApi("android.location.flags.population_density_provider") public static final String BIND_POPULATION_DENSITY_PROVIDER_SERVICE = "android.permission.BIND_POPULATION_DENSITY_PROVIDER_SERVICE"; field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE"; field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE"; field public static final String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 22bc356899f4..361ba73817c7 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -398,6 +398,7 @@ flag { flag { name: "split_create_managed_profile_enabled" + is_exported: true namespace: "enterprise" description: "Split up existing create and provision managed profile API." bug: "375382324" diff --git a/core/java/android/os/CpuHeadroomParamsInternal.aidl b/core/java/android/os/CpuHeadroomParamsInternal.aidl index d572f965579b..12b209357d9c 100644 --- a/core/java/android/os/CpuHeadroomParamsInternal.aidl +++ b/core/java/android/os/CpuHeadroomParamsInternal.aidl @@ -28,6 +28,5 @@ parcelable CpuHeadroomParamsInternal { int[] tids; int calculationWindowMillis = 1000; CpuHeadroomParams.CalculationType calculationType = CpuHeadroomParams.CalculationType.MIN; - CpuHeadroomParams.SelectionType selectionType = CpuHeadroomParams.SelectionType.ALL; } diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index bf7116d6a05b..6855d95d718f 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -50,6 +50,7 @@ import com.android.internal.util.ArrayUtils; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; +import dalvik.annotation.optimization.NeverInline; import libcore.util.SneakyThrow; @@ -587,6 +588,17 @@ public final class Parcel { return parcel; } + @NeverInline + private void errorUsedWhileRecycling() { + Log.wtf(TAG, "Parcel used while recycled. " + + Log.getStackTraceString(new Throwable()) + + " Original recycle call (if DEBUG_RECYCLE): ", mStack); + } + + private void assertNotRecycled() { + if (mRecycled) errorUsedWhileRecycling(); + } + /** * Put a Parcel object back into the pool. You must not touch * the object after this call. @@ -635,6 +647,7 @@ public final class Parcel { * @hide */ public void setReadWriteHelper(@Nullable ReadWriteHelper helper) { + assertNotRecycled(); mReadWriteHelper = helper != null ? helper : ReadWriteHelper.DEFAULT; } @@ -644,6 +657,7 @@ public final class Parcel { * @hide */ public boolean hasReadWriteHelper() { + assertNotRecycled(); return (mReadWriteHelper != null) && (mReadWriteHelper != ReadWriteHelper.DEFAULT); } @@ -670,6 +684,7 @@ public final class Parcel { * @hide */ public final void markSensitive() { + assertNotRecycled(); nativeMarkSensitive(mNativePtr); } @@ -686,6 +701,7 @@ public final class Parcel { * @hide */ public final boolean isForRpc() { + assertNotRecycled(); return nativeIsForRpc(mNativePtr); } @@ -693,21 +709,25 @@ public final class Parcel { @ParcelFlags @TestApi public int getFlags() { + assertNotRecycled(); return mFlags; } /** @hide */ public void setFlags(@ParcelFlags int flags) { + assertNotRecycled(); mFlags = flags; } /** @hide */ public void addFlags(@ParcelFlags int flags) { + assertNotRecycled(); mFlags |= flags; } /** @hide */ private boolean hasFlags(@ParcelFlags int flags) { + assertNotRecycled(); return (mFlags & flags) == flags; } @@ -720,6 +740,7 @@ public final class Parcel { // We don't really need to protect it; even if 3p / non-system apps, nothing would happen. // This would only work when used on a reply parcel by a binder object that's allowed-blocking. public void setPropagateAllowBlocking() { + assertNotRecycled(); addFlags(FLAG_PROPAGATE_ALLOW_BLOCKING); } @@ -727,6 +748,7 @@ public final class Parcel { * Returns the total amount of data contained in the parcel. */ public int dataSize() { + assertNotRecycled(); return nativeDataSize(mNativePtr); } @@ -735,6 +757,7 @@ public final class Parcel { * parcel. That is, {@link #dataSize}-{@link #dataPosition}. */ public final int dataAvail() { + assertNotRecycled(); return nativeDataAvail(mNativePtr); } @@ -743,6 +766,7 @@ public final class Parcel { * more than {@link #dataSize}. */ public final int dataPosition() { + assertNotRecycled(); return nativeDataPosition(mNativePtr); } @@ -753,6 +777,7 @@ public final class Parcel { * data buffer. */ public final int dataCapacity() { + assertNotRecycled(); return nativeDataCapacity(mNativePtr); } @@ -764,6 +789,7 @@ public final class Parcel { * @param size The new number of bytes in the Parcel. */ public final void setDataSize(int size) { + assertNotRecycled(); nativeSetDataSize(mNativePtr, size); } @@ -773,6 +799,7 @@ public final class Parcel { * {@link #dataSize}. */ public final void setDataPosition(int pos) { + assertNotRecycled(); nativeSetDataPosition(mNativePtr, pos); } @@ -784,11 +811,13 @@ public final class Parcel { * with this method. */ public final void setDataCapacity(int size) { + assertNotRecycled(); nativeSetDataCapacity(mNativePtr, size); } /** @hide */ public final boolean pushAllowFds(boolean allowFds) { + assertNotRecycled(); return nativePushAllowFds(mNativePtr, allowFds); } @@ -809,6 +838,7 @@ public final class Parcel { * in different versions of the platform. */ public final byte[] marshall() { + assertNotRecycled(); return nativeMarshall(mNativePtr); } @@ -816,15 +846,18 @@ public final class Parcel { * Fills the raw bytes of this Parcel with the supplied data. */ public final void unmarshall(@NonNull byte[] data, int offset, int length) { + assertNotRecycled(); nativeUnmarshall(mNativePtr, data, offset, length); } public final void appendFrom(Parcel parcel, int offset, int length) { + assertNotRecycled(); nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length); } /** @hide */ public int compareData(Parcel other) { + assertNotRecycled(); return nativeCompareData(mNativePtr, other.mNativePtr); } @@ -835,6 +868,7 @@ public final class Parcel { /** @hide */ public final void setClassCookie(Class clz, Object cookie) { + assertNotRecycled(); if (mClassCookies == null) { mClassCookies = new ArrayMap<>(); } @@ -844,11 +878,13 @@ public final class Parcel { /** @hide */ @Nullable public final Object getClassCookie(Class clz) { + assertNotRecycled(); return mClassCookies != null ? mClassCookies.get(clz) : null; } /** @hide */ public void removeClassCookie(Class clz, Object expectedCookie) { + assertNotRecycled(); if (mClassCookies != null) { Object removedCookie = mClassCookies.remove(clz); if (removedCookie != expectedCookie) { @@ -866,21 +902,25 @@ public final class Parcel { * @hide */ public boolean hasClassCookie(Class clz) { + assertNotRecycled(); return mClassCookies != null && mClassCookies.containsKey(clz); } /** @hide */ public final void adoptClassCookies(Parcel from) { + assertNotRecycled(); mClassCookies = from.mClassCookies; } /** @hide */ public Map<Class, Object> copyClassCookies() { + assertNotRecycled(); return new ArrayMap<>(mClassCookies); } /** @hide */ public void putClassCookies(Map<Class, Object> cookies) { + assertNotRecycled(); if (cookies == null) { return; } @@ -894,6 +934,7 @@ public final class Parcel { * Report whether the parcel contains any marshalled file descriptors. */ public boolean hasFileDescriptors() { + assertNotRecycled(); return nativeHasFileDescriptors(mNativePtr); } @@ -909,6 +950,7 @@ public final class Parcel { * @throws IllegalArgumentException if the parameters are out of the permitted ranges. */ public boolean hasFileDescriptors(int offset, int length) { + assertNotRecycled(); return nativeHasFileDescriptorsInRange(mNativePtr, offset, length); } @@ -993,6 +1035,7 @@ public final class Parcel { * @hide */ public boolean hasBinders() { + assertNotRecycled(); return nativeHasBinders(mNativePtr); } @@ -1010,6 +1053,7 @@ public final class Parcel { * @hide */ public boolean hasBinders(int offset, int length) { + assertNotRecycled(); return nativeHasBindersInRange(mNativePtr, offset, length); } @@ -1020,6 +1064,7 @@ public final class Parcel { * at the beginning of transactions as a header. */ public final void writeInterfaceToken(@NonNull String interfaceName) { + assertNotRecycled(); nativeWriteInterfaceToken(mNativePtr, interfaceName); } @@ -1030,6 +1075,7 @@ public final class Parcel { * should propagate to the caller. */ public final void enforceInterface(@NonNull String interfaceName) { + assertNotRecycled(); nativeEnforceInterface(mNativePtr, interfaceName); } @@ -1040,6 +1086,7 @@ public final class Parcel { * When used over binder, this exception should propagate to the caller. */ public void enforceNoDataAvail() { + assertNotRecycled(); final int n = dataAvail(); if (n > 0) { throw new BadParcelableException("Parcel data not fully consumed, unread size: " + n); @@ -1056,6 +1103,7 @@ public final class Parcel { * @hide */ public boolean replaceCallingWorkSourceUid(int workSourceUid) { + assertNotRecycled(); return nativeReplaceCallingWorkSourceUid(mNativePtr, workSourceUid); } @@ -1072,6 +1120,7 @@ public final class Parcel { * @hide */ public int readCallingWorkSourceUid() { + assertNotRecycled(); return nativeReadCallingWorkSourceUid(mNativePtr); } @@ -1081,6 +1130,7 @@ public final class Parcel { * @param b Bytes to place into the parcel. */ public final void writeByteArray(@Nullable byte[] b) { + assertNotRecycled(); writeByteArray(b, 0, (b != null) ? b.length : 0); } @@ -1092,6 +1142,7 @@ public final class Parcel { * @param len Number of bytes to write. */ public final void writeByteArray(@Nullable byte[] b, int offset, int len) { + assertNotRecycled(); if (b == null) { writeInt(-1); return; @@ -1113,6 +1164,7 @@ public final class Parcel { * @see #readBlob() */ public final void writeBlob(@Nullable byte[] b) { + assertNotRecycled(); writeBlob(b, 0, (b != null) ? b.length : 0); } @@ -1131,6 +1183,7 @@ public final class Parcel { * @see #readBlob() */ public final void writeBlob(@Nullable byte[] b, int offset, int len) { + assertNotRecycled(); if (b == null) { writeInt(-1); return; @@ -1149,6 +1202,7 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeInt(int val) { + assertNotRecycled(); int err = nativeWriteInt(mNativePtr, val); if (err != OK) { nativeSignalExceptionForError(err); @@ -1160,6 +1214,7 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeLong(long val) { + assertNotRecycled(); int err = nativeWriteLong(mNativePtr, val); if (err != OK) { nativeSignalExceptionForError(err); @@ -1171,6 +1226,7 @@ public final class Parcel { * dataPosition(), growing dataCapacity() if needed. */ public final void writeFloat(float val) { + assertNotRecycled(); int err = nativeWriteFloat(mNativePtr, val); if (err != OK) { nativeSignalExceptionForError(err); @@ -1182,6 +1238,7 @@ public final class Parcel { * current dataPosition(), growing dataCapacity() if needed. */ public final void writeDouble(double val) { + assertNotRecycled(); int err = nativeWriteDouble(mNativePtr, val); if (err != OK) { nativeSignalExceptionForError(err); @@ -1193,16 +1250,19 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeString(@Nullable String val) { + assertNotRecycled(); writeString16(val); } /** {@hide} */ public final void writeString8(@Nullable String val) { + assertNotRecycled(); mReadWriteHelper.writeString8(this, val); } /** {@hide} */ public final void writeString16(@Nullable String val) { + assertNotRecycled(); mReadWriteHelper.writeString16(this, val); } @@ -1214,16 +1274,19 @@ public final class Parcel { * @hide */ public void writeStringNoHelper(@Nullable String val) { + assertNotRecycled(); writeString16NoHelper(val); } /** {@hide} */ public void writeString8NoHelper(@Nullable String val) { + assertNotRecycled(); nativeWriteString8(mNativePtr, val); } /** {@hide} */ public void writeString16NoHelper(@Nullable String val) { + assertNotRecycled(); nativeWriteString16(mNativePtr, val); } @@ -1235,6 +1298,7 @@ public final class Parcel { * for true or false, respectively, but may change in the future. */ public final void writeBoolean(boolean val) { + assertNotRecycled(); writeInt(val ? 1 : 0); } @@ -1246,6 +1310,7 @@ public final class Parcel { @UnsupportedAppUsage @RavenwoodThrow(blockedBy = android.text.Spanned.class) public final void writeCharSequence(@Nullable CharSequence val) { + assertNotRecycled(); TextUtils.writeToParcel(val, this, 0); } @@ -1254,6 +1319,7 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeStrongBinder(IBinder val) { + assertNotRecycled(); nativeWriteStrongBinder(mNativePtr, val); } @@ -1262,6 +1328,7 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeStrongInterface(IInterface val) { + assertNotRecycled(); writeStrongBinder(val == null ? null : val.asBinder()); } @@ -1276,6 +1343,7 @@ public final class Parcel { * if {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set.</p> */ public final void writeFileDescriptor(@NonNull FileDescriptor val) { + assertNotRecycled(); nativeWriteFileDescriptor(mNativePtr, val); } @@ -1284,6 +1352,7 @@ public final class Parcel { * This will be the new name for writeFileDescriptor, for consistency. **/ public final void writeRawFileDescriptor(@NonNull FileDescriptor val) { + assertNotRecycled(); nativeWriteFileDescriptor(mNativePtr, val); } @@ -1294,6 +1363,7 @@ public final class Parcel { * @param value The array of objects to be written. */ public final void writeRawFileDescriptorArray(@Nullable FileDescriptor[] value) { + assertNotRecycled(); if (value != null) { int N = value.length; writeInt(N); @@ -1313,6 +1383,7 @@ public final class Parcel { * the future. */ public final void writeByte(byte val) { + assertNotRecycled(); writeInt(val); } @@ -1328,6 +1399,7 @@ public final class Parcel { * allows you to avoid mysterious type errors at the point of marshalling. */ public final void writeMap(@Nullable Map val) { + assertNotRecycled(); writeMapInternal((Map<String, Object>) val); } @@ -1336,6 +1408,7 @@ public final class Parcel { * growing dataCapacity() if needed. The Map keys must be String objects. */ /* package */ void writeMapInternal(@Nullable Map<String,Object> val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1361,6 +1434,7 @@ public final class Parcel { * growing dataCapacity() if needed. The Map keys must be String objects. */ /* package */ void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1390,6 +1464,7 @@ public final class Parcel { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void writeArrayMap(@Nullable ArrayMap<String, Object> val) { + assertNotRecycled(); writeArrayMapInternal(val); } @@ -1408,6 +1483,7 @@ public final class Parcel { */ public <T extends Parcelable> void writeTypedArrayMap(@Nullable ArrayMap<String, T> val, int parcelableFlags) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1429,6 +1505,7 @@ public final class Parcel { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void writeArraySet(@Nullable ArraySet<? extends Object> val) { + assertNotRecycled(); final int size = (val != null) ? val.size() : -1; writeInt(size); for (int i = 0; i < size; i++) { @@ -1441,6 +1518,7 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeBundle(@Nullable Bundle val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1454,6 +1532,7 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writePersistableBundle(@Nullable PersistableBundle val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1467,6 +1546,7 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeSize(@NonNull Size val) { + assertNotRecycled(); writeInt(val.getWidth()); writeInt(val.getHeight()); } @@ -1476,6 +1556,7 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeSizeF(@NonNull SizeF val) { + assertNotRecycled(); writeFloat(val.getWidth()); writeFloat(val.getHeight()); } @@ -1486,6 +1567,7 @@ public final class Parcel { * {@link #writeValue} and must follow the specification there. */ public final void writeList(@Nullable List val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1505,6 +1587,7 @@ public final class Parcel { * {@link #writeValue} and must follow the specification there. */ public final void writeArray(@Nullable Object[] val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1525,6 +1608,7 @@ public final class Parcel { * specification there. */ public final <T> void writeSparseArray(@Nullable SparseArray<T> val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1540,6 +1624,7 @@ public final class Parcel { } public final void writeSparseBooleanArray(@Nullable SparseBooleanArray val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1558,6 +1643,7 @@ public final class Parcel { * @hide */ public final void writeSparseIntArray(@Nullable SparseIntArray val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1573,6 +1659,7 @@ public final class Parcel { } public final void writeBooleanArray(@Nullable boolean[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1607,6 +1694,7 @@ public final class Parcel { } private void ensureWithinMemoryLimit(int typeSize, @NonNull int... dimensions) { + assertNotRecycled(); // For Multidimensional arrays, Calculate total object // which will be allocated. int totalObjects = 1; @@ -1624,6 +1712,7 @@ public final class Parcel { } private void ensureWithinMemoryLimit(int typeSize, int length) { + assertNotRecycled(); int estimatedAllocationSize = 0; try { estimatedAllocationSize = Math.multiplyExact(typeSize, length); @@ -1647,6 +1736,7 @@ public final class Parcel { @Nullable public final boolean[] createBooleanArray() { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_BOOLEAN, N); // >>2 as a fast divide-by-4 works in the create*Array() functions @@ -1664,6 +1754,7 @@ public final class Parcel { } public final void readBooleanArray(@NonNull boolean[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1676,6 +1767,7 @@ public final class Parcel { /** @hide */ public void writeShortArray(@Nullable short[] val) { + assertNotRecycled(); if (val != null) { int n = val.length; writeInt(n); @@ -1690,6 +1782,7 @@ public final class Parcel { /** @hide */ @Nullable public short[] createShortArray() { + assertNotRecycled(); int n = readInt(); ensureWithinMemoryLimit(SIZE_SHORT, n); if (n >= 0 && n <= (dataAvail() >> 2)) { @@ -1705,6 +1798,7 @@ public final class Parcel { /** @hide */ public void readShortArray(@NonNull short[] val) { + assertNotRecycled(); int n = readInt(); if (n == val.length) { for (int i = 0; i < n; i++) { @@ -1716,6 +1810,7 @@ public final class Parcel { } public final void writeCharArray(@Nullable char[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1729,6 +1824,7 @@ public final class Parcel { @Nullable public final char[] createCharArray() { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_CHAR, N); if (N >= 0 && N <= (dataAvail() >> 2)) { @@ -1743,6 +1839,7 @@ public final class Parcel { } public final void readCharArray(@NonNull char[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1754,6 +1851,7 @@ public final class Parcel { } public final void writeIntArray(@Nullable int[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1767,6 +1865,7 @@ public final class Parcel { @Nullable public final int[] createIntArray() { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_INT, N); if (N >= 0 && N <= (dataAvail() >> 2)) { @@ -1781,6 +1880,7 @@ public final class Parcel { } public final void readIntArray(@NonNull int[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1792,6 +1892,7 @@ public final class Parcel { } public final void writeLongArray(@Nullable long[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1805,6 +1906,7 @@ public final class Parcel { @Nullable public final long[] createLongArray() { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_LONG, N); // >>3 because stored longs are 64 bits @@ -1820,6 +1922,7 @@ public final class Parcel { } public final void readLongArray(@NonNull long[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1831,6 +1934,7 @@ public final class Parcel { } public final void writeFloatArray(@Nullable float[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1844,6 +1948,7 @@ public final class Parcel { @Nullable public final float[] createFloatArray() { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_FLOAT, N); // >>2 because stored floats are 4 bytes @@ -1859,6 +1964,7 @@ public final class Parcel { } public final void readFloatArray(@NonNull float[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1870,6 +1976,7 @@ public final class Parcel { } public final void writeDoubleArray(@Nullable double[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1883,6 +1990,7 @@ public final class Parcel { @Nullable public final double[] createDoubleArray() { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_DOUBLE, N); // >>3 because stored doubles are 8 bytes @@ -1898,6 +2006,7 @@ public final class Parcel { } public final void readDoubleArray(@NonNull double[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1909,20 +2018,24 @@ public final class Parcel { } public final void writeStringArray(@Nullable String[] val) { + assertNotRecycled(); writeString16Array(val); } @Nullable public final String[] createStringArray() { + assertNotRecycled(); return createString16Array(); } public final void readStringArray(@NonNull String[] val) { + assertNotRecycled(); readString16Array(val); } /** {@hide} */ public final void writeString8Array(@Nullable String[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1937,6 +2050,7 @@ public final class Parcel { /** {@hide} */ @Nullable public final String[] createString8Array() { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); if (N >= 0) { @@ -1952,6 +2066,7 @@ public final class Parcel { /** {@hide} */ public final void readString8Array(@NonNull String[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1964,6 +2079,7 @@ public final class Parcel { /** {@hide} */ public final void writeString16Array(@Nullable String[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1978,6 +2094,7 @@ public final class Parcel { /** {@hide} */ @Nullable public final String[] createString16Array() { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); if (N >= 0) { @@ -1993,6 +2110,7 @@ public final class Parcel { /** {@hide} */ public final void readString16Array(@NonNull String[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -2004,6 +2122,7 @@ public final class Parcel { } public final void writeBinderArray(@Nullable IBinder[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2028,6 +2147,7 @@ public final class Parcel { */ public final <T extends IInterface> void writeInterfaceArray( @SuppressLint("ArrayReturn") @Nullable T[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2043,6 +2163,7 @@ public final class Parcel { * @hide */ public final void writeCharSequenceArray(@Nullable CharSequence[] val) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2058,6 +2179,7 @@ public final class Parcel { * @hide */ public final void writeCharSequenceList(@Nullable ArrayList<CharSequence> val) { + assertNotRecycled(); if (val != null) { int N = val.size(); writeInt(N); @@ -2071,6 +2193,7 @@ public final class Parcel { @Nullable public final IBinder[] createBinderArray() { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); if (N >= 0) { @@ -2085,6 +2208,7 @@ public final class Parcel { } public final void readBinderArray(@NonNull IBinder[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -2106,6 +2230,7 @@ public final class Parcel { @Nullable public final <T extends IInterface> T[] createInterfaceArray( @NonNull IntFunction<T[]> newArray, @NonNull Function<IBinder, T> asInterface) { + assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); if (N >= 0) { @@ -2130,6 +2255,7 @@ public final class Parcel { public final <T extends IInterface> void readInterfaceArray( @SuppressLint("ArrayReturn") @NonNull T[] val, @NonNull Function<IBinder, T> asInterface) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -2155,6 +2281,7 @@ public final class Parcel { * @see Parcelable */ public final <T extends Parcelable> void writeTypedList(@Nullable List<T> val) { + assertNotRecycled(); writeTypedList(val, 0); } @@ -2174,6 +2301,7 @@ public final class Parcel { */ public final <T extends Parcelable> void writeTypedSparseArray(@Nullable SparseArray<T> val, int parcelableFlags) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2203,6 +2331,7 @@ public final class Parcel { * @see Parcelable */ public <T extends Parcelable> void writeTypedList(@Nullable List<T> val, int parcelableFlags) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2228,6 +2357,7 @@ public final class Parcel { * @see #readStringList */ public final void writeStringList(@Nullable List<String> val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2253,6 +2383,7 @@ public final class Parcel { * @see #readBinderList */ public final void writeBinderList(@Nullable List<IBinder> val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2275,6 +2406,7 @@ public final class Parcel { * @see #readInterfaceList */ public final <T extends IInterface> void writeInterfaceList(@Nullable List<T> val) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2296,6 +2428,7 @@ public final class Parcel { * @see #readParcelableList(List, ClassLoader) */ public final <T extends Parcelable> void writeParcelableList(@Nullable List<T> val, int flags) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2330,6 +2463,7 @@ public final class Parcel { */ public final <T extends Parcelable> void writeTypedArray(@Nullable T[] val, int parcelableFlags) { + assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2352,6 +2486,7 @@ public final class Parcel { */ public final <T extends Parcelable> void writeTypedObject(@Nullable T val, int parcelableFlags) { + assertNotRecycled(); if (val != null) { writeInt(1); val.writeToParcel(this, parcelableFlags); @@ -2389,6 +2524,7 @@ public final class Parcel { */ public <T> void writeFixedArray(@Nullable T val, int parcelableFlags, @NonNull int... dimensions) { + assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2500,6 +2636,7 @@ public final class Parcel { * should be used).</p> */ public final void writeValue(@Nullable Object v) { + assertNotRecycled(); if (v instanceof LazyValue) { LazyValue value = (LazyValue) v; value.writeToParcel(this); @@ -2617,6 +2754,7 @@ public final class Parcel { * @hide */ public void writeValue(int type, @Nullable Object v) { + assertNotRecycled(); switch (type) { case VAL_NULL: break; @@ -2730,6 +2868,7 @@ public final class Parcel { * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}. */ public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) { + assertNotRecycled(); if (p == null) { writeString(null); return; @@ -2745,6 +2884,7 @@ public final class Parcel { * @see #readParcelableCreator */ public final void writeParcelableCreator(@NonNull Parcelable p) { + assertNotRecycled(); String name = p.getClass().getName(); writeString(name); } @@ -2783,6 +2923,7 @@ public final class Parcel { */ @TestApi public boolean allowSquashing() { + assertNotRecycled(); boolean previous = mAllowSquashing; mAllowSquashing = true; return previous; @@ -2794,6 +2935,7 @@ public final class Parcel { */ @TestApi public void restoreAllowSquashing(boolean previous) { + assertNotRecycled(); mAllowSquashing = previous; if (!mAllowSquashing) { mWrittenSquashableParcelables = null; @@ -2850,6 +2992,7 @@ public final class Parcel { * @hide */ public boolean maybeWriteSquashed(@NonNull Parcelable p) { + assertNotRecycled(); if (!mAllowSquashing) { // Don't squash, and don't put it in the map either. writeInt(0); @@ -2900,6 +3043,7 @@ public final class Parcel { @SuppressWarnings("unchecked") @Nullable public <T extends Parcelable> T readSquashed(SquashReadHelper<T> reader) { + assertNotRecycled(); final int offset = readInt(); final int pos = dataPosition(); @@ -2933,6 +3077,7 @@ public final class Parcel { * using the other approaches to writing data in to a Parcel. */ public final void writeSerializable(@Nullable Serializable s) { + assertNotRecycled(); if (s == null) { writeString(null); return; @@ -2985,6 +3130,7 @@ public final class Parcel { */ @RavenwoodReplace(blockedBy = AppOpsManager.class) public final void writeException(@NonNull Exception e) { + assertNotRecycled(); AppOpsManager.prefixParcelWithAppOpsIfNeeded(this); int code = getExceptionCode(e); @@ -3065,6 +3211,7 @@ public final class Parcel { /** @hide */ public void writeStackTrace(@NonNull Throwable e) { + assertNotRecycled(); final int sizePosition = dataPosition(); writeInt(0); // Header size will be filled in later StackTraceElement[] stackTrace = e.getStackTrace(); @@ -3090,6 +3237,7 @@ public final class Parcel { */ @RavenwoodReplace(blockedBy = AppOpsManager.class) public final void writeNoException() { + assertNotRecycled(); AppOpsManager.prefixParcelWithAppOpsIfNeeded(this); // Despite the name of this function ("write no exception"), @@ -3133,6 +3281,7 @@ public final class Parcel { * @see #writeNoException */ public final void readException() { + assertNotRecycled(); int code = readExceptionCode(); if (code != 0) { String msg = readString(); @@ -3156,6 +3305,7 @@ public final class Parcel { @UnsupportedAppUsage @TestApi public final int readExceptionCode() { + assertNotRecycled(); int code = readInt(); if (code == EX_HAS_NOTED_APPOPS_REPLY_HEADER) { AppOpsManager.readAndLogNotedAppops(this); @@ -3189,6 +3339,7 @@ public final class Parcel { * @param msg The exception message. */ public final void readException(int code, String msg) { + assertNotRecycled(); String remoteStackTrace = null; final int remoteStackPayloadSize = readInt(); if (remoteStackPayloadSize > 0) { @@ -3219,6 +3370,7 @@ public final class Parcel { /** @hide */ public Exception createExceptionOrNull(int code, String msg) { + assertNotRecycled(); switch (code) { case EX_PARCELABLE: if (readInt() > 0) { @@ -3251,6 +3403,7 @@ public final class Parcel { * Read an integer value from the parcel at the current dataPosition(). */ public final int readInt() { + assertNotRecycled(); return nativeReadInt(mNativePtr); } @@ -3258,6 +3411,7 @@ public final class Parcel { * Read a long integer value from the parcel at the current dataPosition(). */ public final long readLong() { + assertNotRecycled(); return nativeReadLong(mNativePtr); } @@ -3266,6 +3420,7 @@ public final class Parcel { * dataPosition(). */ public final float readFloat() { + assertNotRecycled(); return nativeReadFloat(mNativePtr); } @@ -3274,6 +3429,7 @@ public final class Parcel { * current dataPosition(). */ public final double readDouble() { + assertNotRecycled(); return nativeReadDouble(mNativePtr); } @@ -3282,16 +3438,19 @@ public final class Parcel { */ @Nullable public final String readString() { + assertNotRecycled(); return readString16(); } /** {@hide} */ public final @Nullable String readString8() { + assertNotRecycled(); return mReadWriteHelper.readString8(this); } /** {@hide} */ public final @Nullable String readString16() { + assertNotRecycled(); return mReadWriteHelper.readString16(this); } @@ -3303,16 +3462,19 @@ public final class Parcel { * @hide */ public @Nullable String readStringNoHelper() { + assertNotRecycled(); return readString16NoHelper(); } /** {@hide} */ public @Nullable String readString8NoHelper() { + assertNotRecycled(); return nativeReadString8(mNativePtr); } /** {@hide} */ public @Nullable String readString16NoHelper() { + assertNotRecycled(); return nativeReadString16(mNativePtr); } @@ -3320,6 +3482,7 @@ public final class Parcel { * Read a boolean value from the parcel at the current dataPosition(). */ public final boolean readBoolean() { + assertNotRecycled(); return readInt() != 0; } @@ -3330,6 +3493,7 @@ public final class Parcel { @UnsupportedAppUsage @Nullable public final CharSequence readCharSequence() { + assertNotRecycled(); return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(this); } @@ -3337,6 +3501,7 @@ public final class Parcel { * Read an object from the parcel at the current dataPosition(). */ public final IBinder readStrongBinder() { + assertNotRecycled(); final IBinder result = nativeReadStrongBinder(mNativePtr); // If it's a reply from a method with @PropagateAllowBlocking, then inherit allow-blocking @@ -3352,6 +3517,7 @@ public final class Parcel { * Read a FileDescriptor from the parcel at the current dataPosition(). */ public final ParcelFileDescriptor readFileDescriptor() { + assertNotRecycled(); FileDescriptor fd = nativeReadFileDescriptor(mNativePtr); return fd != null ? new ParcelFileDescriptor(fd) : null; } @@ -3359,6 +3525,7 @@ public final class Parcel { /** {@hide} */ @UnsupportedAppUsage public final FileDescriptor readRawFileDescriptor() { + assertNotRecycled(); return nativeReadFileDescriptor(mNativePtr); } @@ -3369,6 +3536,7 @@ public final class Parcel { **/ @Nullable public final FileDescriptor[] createRawFileDescriptorArray() { + assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -3388,6 +3556,7 @@ public final class Parcel { * @return the FileDescriptor array, or null if the array is null. **/ public final void readRawFileDescriptorArray(FileDescriptor[] val) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -3402,6 +3571,7 @@ public final class Parcel { * Read a byte value from the parcel at the current dataPosition(). */ public final byte readByte() { + assertNotRecycled(); return (byte)(readInt() & 0xff); } @@ -3416,6 +3586,7 @@ public final class Parcel { */ @Deprecated public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) { + assertNotRecycled(); readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null); } @@ -3429,6 +3600,7 @@ public final class Parcel { public <K, V> void readMap(@NonNull Map<? super K, ? super V> outVal, @Nullable ClassLoader loader, @NonNull Class<K> clazzKey, @NonNull Class<V> clazzValue) { + assertNotRecycled(); Objects.requireNonNull(clazzKey); Objects.requireNonNull(clazzValue); readMapInternal(outVal, loader, clazzKey, clazzValue); @@ -3447,6 +3619,7 @@ public final class Parcel { */ @Deprecated public final void readList(@NonNull List outVal, @Nullable ClassLoader loader) { + assertNotRecycled(); int N = readInt(); readListInternal(outVal, N, loader, /* clazz */ null); } @@ -3468,6 +3641,7 @@ public final class Parcel { */ public <T> void readList(@NonNull List<? super T> outVal, @Nullable ClassLoader loader, @NonNull Class<T> clazz) { + assertNotRecycled(); Objects.requireNonNull(clazz); int n = readInt(); readListInternal(outVal, n, loader, clazz); @@ -3487,6 +3661,7 @@ public final class Parcel { @Deprecated @Nullable public HashMap readHashMap(@Nullable ClassLoader loader) { + assertNotRecycled(); return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null); } @@ -3501,6 +3676,7 @@ public final class Parcel { @Nullable public <K, V> HashMap<K, V> readHashMap(@Nullable ClassLoader loader, @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) { + assertNotRecycled(); Objects.requireNonNull(clazzKey); Objects.requireNonNull(clazzValue); return readHashMapInternal(loader, clazzKey, clazzValue); @@ -3513,6 +3689,7 @@ public final class Parcel { */ @Nullable public final Bundle readBundle() { + assertNotRecycled(); return readBundle(null); } @@ -3524,6 +3701,7 @@ public final class Parcel { */ @Nullable public final Bundle readBundle(@Nullable ClassLoader loader) { + assertNotRecycled(); int length = readInt(); if (length < 0) { if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length); @@ -3544,6 +3722,7 @@ public final class Parcel { */ @Nullable public final PersistableBundle readPersistableBundle() { + assertNotRecycled(); return readPersistableBundle(null); } @@ -3555,6 +3734,7 @@ public final class Parcel { */ @Nullable public final PersistableBundle readPersistableBundle(@Nullable ClassLoader loader) { + assertNotRecycled(); int length = readInt(); if (length < 0) { if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length); @@ -3573,6 +3753,7 @@ public final class Parcel { */ @NonNull public final Size readSize() { + assertNotRecycled(); final int width = readInt(); final int height = readInt(); return new Size(width, height); @@ -3583,6 +3764,7 @@ public final class Parcel { */ @NonNull public final SizeF readSizeF() { + assertNotRecycled(); final float width = readFloat(); final float height = readFloat(); return new SizeF(width, height); @@ -3593,6 +3775,7 @@ public final class Parcel { */ @Nullable public final byte[] createByteArray() { + assertNotRecycled(); return nativeCreateByteArray(mNativePtr); } @@ -3601,6 +3784,7 @@ public final class Parcel { * given byte array. */ public final void readByteArray(@NonNull byte[] val) { + assertNotRecycled(); boolean valid = nativeReadByteArray(mNativePtr, val, (val != null) ? val.length : 0); if (!valid) { throw new RuntimeException("bad array lengths"); @@ -3613,6 +3797,7 @@ public final class Parcel { */ @Nullable public final byte[] readBlob() { + assertNotRecycled(); return nativeReadBlob(mNativePtr); } @@ -3623,6 +3808,7 @@ public final class Parcel { @UnsupportedAppUsage @Nullable public final String[] readStringArray() { + assertNotRecycled(); return createString16Array(); } @@ -3632,6 +3818,7 @@ public final class Parcel { */ @Nullable public final CharSequence[] readCharSequenceArray() { + assertNotRecycled(); CharSequence[] array = null; int length = readInt(); @@ -3654,6 +3841,7 @@ public final class Parcel { */ @Nullable public final ArrayList<CharSequence> readCharSequenceList() { + assertNotRecycled(); ArrayList<CharSequence> array = null; int length = readInt(); @@ -3683,6 +3871,7 @@ public final class Parcel { @Deprecated @Nullable public ArrayList readArrayList(@Nullable ClassLoader loader) { + assertNotRecycled(); return readArrayListInternal(loader, /* clazz */ null); } @@ -3705,6 +3894,7 @@ public final class Parcel { @Nullable public <T> ArrayList<T> readArrayList(@Nullable ClassLoader loader, @NonNull Class<? extends T> clazz) { + assertNotRecycled(); Objects.requireNonNull(clazz); return readArrayListInternal(loader, clazz); } @@ -3724,6 +3914,7 @@ public final class Parcel { @Deprecated @Nullable public Object[] readArray(@Nullable ClassLoader loader) { + assertNotRecycled(); return readArrayInternal(loader, /* clazz */ null); } @@ -3745,6 +3936,7 @@ public final class Parcel { @SuppressLint({"ArrayReturn", "NullableCollection"}) @Nullable public <T> T[] readArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { + assertNotRecycled(); Objects.requireNonNull(clazz); return readArrayInternal(loader, clazz); } @@ -3764,6 +3956,7 @@ public final class Parcel { @Deprecated @Nullable public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) { + assertNotRecycled(); return readSparseArrayInternal(loader, /* clazz */ null); } @@ -3785,6 +3978,7 @@ public final class Parcel { @Nullable public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader, @NonNull Class<? extends T> clazz) { + assertNotRecycled(); Objects.requireNonNull(clazz); return readSparseArrayInternal(loader, clazz); } @@ -3796,6 +3990,7 @@ public final class Parcel { */ @Nullable public final SparseBooleanArray readSparseBooleanArray() { + assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -3812,6 +4007,7 @@ public final class Parcel { */ @Nullable public final SparseIntArray readSparseIntArray() { + assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -3836,6 +4032,7 @@ public final class Parcel { */ @Nullable public final <T> ArrayList<T> createTypedArrayList(@NonNull Parcelable.Creator<T> c) { + assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -3859,6 +4056,7 @@ public final class Parcel { * @see #writeTypedList */ public final <T> void readTypedList(@NonNull List<T> list, @NonNull Parcelable.Creator<T> c) { + assertNotRecycled(); int M = list.size(); int N = readInt(); int i = 0; @@ -3888,6 +4086,7 @@ public final class Parcel { */ public final @Nullable <T extends Parcelable> SparseArray<T> createTypedSparseArray( @NonNull Parcelable.Creator<T> creator) { + assertNotRecycled(); final int count = readInt(); if (count < 0) { return null; @@ -3917,6 +4116,7 @@ public final class Parcel { */ public final @Nullable <T extends Parcelable> ArrayMap<String, T> createTypedArrayMap( @NonNull Parcelable.Creator<T> creator) { + assertNotRecycled(); final int count = readInt(); if (count < 0) { return null; @@ -3944,6 +4144,7 @@ public final class Parcel { */ @Nullable public final ArrayList<String> createStringArrayList() { + assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -3970,6 +4171,7 @@ public final class Parcel { */ @Nullable public final ArrayList<IBinder> createBinderArrayList() { + assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -3997,6 +4199,7 @@ public final class Parcel { @Nullable public final <T extends IInterface> ArrayList<T> createInterfaceArrayList( @NonNull Function<IBinder, T> asInterface) { + assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -4017,6 +4220,7 @@ public final class Parcel { * @see #writeStringList */ public final void readStringList(@NonNull List<String> list) { + assertNotRecycled(); int M = list.size(); int N = readInt(); int i = 0; @@ -4038,6 +4242,7 @@ public final class Parcel { * @see #writeBinderList */ public final void readBinderList(@NonNull List<IBinder> list) { + assertNotRecycled(); int M = list.size(); int N = readInt(); int i = 0; @@ -4060,6 +4265,7 @@ public final class Parcel { */ public final <T extends IInterface> void readInterfaceList(@NonNull List<T> list, @NonNull Function<IBinder, T> asInterface) { + assertNotRecycled(); int M = list.size(); int N = readInt(); int i = 0; @@ -4091,6 +4297,7 @@ public final class Parcel { @NonNull public final <T extends Parcelable> List<T> readParcelableList(@NonNull List<T> list, @Nullable ClassLoader cl) { + assertNotRecycled(); return readParcelableListInternal(list, cl, /*clazz*/ null); } @@ -4112,6 +4319,7 @@ public final class Parcel { @NonNull public <T> List<T> readParcelableList(@NonNull List<T> list, @Nullable ClassLoader cl, @NonNull Class<? extends T> clazz) { + assertNotRecycled(); Objects.requireNonNull(list); Objects.requireNonNull(clazz); return readParcelableListInternal(list, cl, clazz); @@ -4157,6 +4365,7 @@ public final class Parcel { */ @Nullable public final <T> T[] createTypedArray(@NonNull Parcelable.Creator<T> c) { + assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -4170,6 +4379,7 @@ public final class Parcel { } public final <T> void readTypedArray(@NonNull T[] val, @NonNull Parcelable.Creator<T> c) { + assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -4186,6 +4396,7 @@ public final class Parcel { */ @Deprecated public final <T> T[] readTypedArray(Parcelable.Creator<T> c) { + assertNotRecycled(); return createTypedArray(c); } @@ -4202,6 +4413,7 @@ public final class Parcel { */ @Nullable public final <T> T readTypedObject(@NonNull Parcelable.Creator<T> c) { + assertNotRecycled(); if (readInt() != 0) { return c.createFromParcel(this); } else { @@ -4228,6 +4440,7 @@ public final class Parcel { * @see #readTypedArray */ public <T> void readFixedArray(@NonNull T val) { + assertNotRecycled(); Class<?> componentType = val.getClass().getComponentType(); if (componentType == boolean.class) { readBooleanArray((boolean[]) val); @@ -4268,6 +4481,7 @@ public final class Parcel { */ public <T, S extends IInterface> void readFixedArray(@NonNull T val, @NonNull Function<IBinder, S> asInterface) { + assertNotRecycled(); Class<?> componentType = val.getClass().getComponentType(); if (IInterface.class.isAssignableFrom(componentType)) { readInterfaceArray((S[]) val, asInterface); @@ -4294,6 +4508,7 @@ public final class Parcel { */ public <T, S extends Parcelable> void readFixedArray(@NonNull T val, @NonNull Parcelable.Creator<S> c) { + assertNotRecycled(); Class<?> componentType = val.getClass().getComponentType(); if (Parcelable.class.isAssignableFrom(componentType)) { readTypedArray((S[]) val, c); @@ -4351,6 +4566,7 @@ public final class Parcel { */ @Nullable public <T> T createFixedArray(@NonNull Class<T> cls, @NonNull int... dimensions) { + assertNotRecycled(); // Check if type matches with dimensions // If type is one-dimensional array, delegate to other creators // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray @@ -4424,6 +4640,7 @@ public final class Parcel { @Nullable public <T, S extends IInterface> T createFixedArray(@NonNull Class<T> cls, @NonNull Function<IBinder, S> asInterface, @NonNull int... dimensions) { + assertNotRecycled(); // Check if type matches with dimensions // If type is one-dimensional array, delegate to other creators // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray @@ -4484,6 +4701,7 @@ public final class Parcel { @Nullable public <T, S extends Parcelable> T createFixedArray(@NonNull Class<T> cls, @NonNull Parcelable.Creator<S> c, @NonNull int... dimensions) { + assertNotRecycled(); // Check if type matches with dimensions // If type is one-dimensional array, delegate to other creators // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray @@ -4547,6 +4765,7 @@ public final class Parcel { */ public final <T extends Parcelable> void writeParcelableArray(@Nullable T[] value, int parcelableFlags) { + assertNotRecycled(); if (value != null) { int N = value.length; writeInt(N); @@ -4565,6 +4784,7 @@ public final class Parcel { */ @Nullable public final Object readValue(@Nullable ClassLoader loader) { + assertNotRecycled(); return readValue(loader, /* clazz */ null); } @@ -4620,6 +4840,7 @@ public final class Parcel { */ @Nullable public Object readLazyValue(@Nullable ClassLoader loader) { + assertNotRecycled(); int start = dataPosition(); int type = readInt(); if (isLengthPrefixed(type)) { @@ -5022,6 +5243,7 @@ public final class Parcel { @Deprecated @Nullable public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) { + assertNotRecycled(); return readParcelableInternal(loader, /* clazz */ null); } @@ -5041,6 +5263,7 @@ public final class Parcel { */ @Nullable public <T> T readParcelable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { + assertNotRecycled(); Objects.requireNonNull(clazz); return readParcelableInternal(loader, clazz); } @@ -5069,6 +5292,7 @@ public final class Parcel { @Nullable public final <T extends Parcelable> T readCreator(@NonNull Parcelable.Creator<?> creator, @Nullable ClassLoader loader) { + assertNotRecycled(); if (creator instanceof Parcelable.ClassLoaderCreator<?>) { Parcelable.ClassLoaderCreator<?> classLoaderCreator = (Parcelable.ClassLoaderCreator<?>) creator; @@ -5096,6 +5320,7 @@ public final class Parcel { @Deprecated @Nullable public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) { + assertNotRecycled(); return readParcelableCreatorInternal(loader, /* clazz */ null); } @@ -5116,6 +5341,7 @@ public final class Parcel { @Nullable public <T> Parcelable.Creator<T> readParcelableCreator( @Nullable ClassLoader loader, @NonNull Class<T> clazz) { + assertNotRecycled(); Objects.requireNonNull(clazz); return readParcelableCreatorInternal(loader, clazz); } @@ -5238,6 +5464,7 @@ public final class Parcel { @Deprecated @Nullable public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) { + assertNotRecycled(); return readParcelableArrayInternal(loader, /* clazz */ null); } @@ -5258,6 +5485,7 @@ public final class Parcel { @SuppressLint({"ArrayReturn", "NullableCollection"}) @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { + assertNotRecycled(); return readParcelableArrayInternal(loader, requireNonNull(clazz)); } @@ -5291,6 +5519,7 @@ public final class Parcel { @Deprecated @Nullable public Serializable readSerializable() { + assertNotRecycled(); return readSerializableInternal(/* loader */ null, /* clazz */ null); } @@ -5307,6 +5536,7 @@ public final class Parcel { */ @Nullable public <T> T readSerializable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { + assertNotRecycled(); Objects.requireNonNull(clazz); return readSerializableInternal( loader == null ? getClass().getClassLoader() : loader, clazz); @@ -5548,6 +5778,7 @@ public final class Parcel { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal, @Nullable ClassLoader loader) { + assertNotRecycled(); final int N = readInt(); if (N < 0) { return; @@ -5564,6 +5795,7 @@ public final class Parcel { */ @UnsupportedAppUsage public @Nullable ArraySet<? extends Object> readArraySet(@Nullable ClassLoader loader) { + assertNotRecycled(); final int size = readInt(); if (size < 0) { return null; @@ -5703,6 +5935,7 @@ public final class Parcel { * @hide For testing */ public long getOpenAshmemSize() { + assertNotRecycled(); return nativeGetOpenAshmemSize(mNativePtr); } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 1e965c5db7ae..9b8551bf134c 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -85,6 +85,51 @@ public class ArrayUtils { } /** + * This is like <code>new byte[length]</code>, but it allocates the array as non-movable. This + * prevents copies of the data from being left on the Java heap as a result of heap compaction. + * Use this when the array will contain sensitive data such as a password or cryptographic key + * that needs to be wiped from memory when no longer needed. The owner of the array is still + * responsible for the zeroization; {@link #zeroize(byte[])} should be used to do so. + * + * @param length the length of the array to allocate + * @return the new array + */ + public static byte[] newNonMovableByteArray(int length) { + return (byte[]) VMRuntime.getRuntime().newNonMovableArray(byte.class, length); + } + + /** + * Like {@link #newNonMovableByteArray(int)}, but allocates a char array. + * + * @param length the length of the array to allocate + * @return the new array + */ + public static char[] newNonMovableCharArray(int length) { + return (char[]) VMRuntime.getRuntime().newNonMovableArray(char.class, length); + } + + /** + * Zeroizes a byte array as securely as possible. Use this when the array contains sensitive + * data such as a password or cryptographic key. + * <p> + * This zeroizes the array in a way that is guaranteed to not be optimized out by the compiler. + * If supported by the architecture, it zeroizes the data not just in the L1 data cache but also + * in other levels of the memory hierarchy up to and including main memory (but not above that). + * <p> + * This works on any <code>byte[]</code>, but to ensure that copies of the array aren't left on + * the Java heap the array should have been allocated with {@link #newNonMovableByteArray(int)}. + * Use on other arrays might also introduce performance anomalies. + * + * @param array the array to zeroize + */ + public static native void zeroize(byte[] array); + + /** + * Like {@link #zeroize(byte[])}, but for char arrays. + */ + public static native void zeroize(char[] array); + + /** * Checks if the beginnings of two byte arrays are equal. * * @param array1 the first byte array diff --git a/core/jni/Android.bp b/core/jni/Android.bp index e22d9587093b..0ffe2f9e6f2b 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -92,6 +92,7 @@ cc_library_shared_for_libandroid_runtime { "android_view_VelocityTracker.cpp", "android_view_VerifiedKeyEvent.cpp", "android_view_VerifiedMotionEvent.cpp", + "com_android_internal_util_ArrayUtils.cpp", "com_android_internal_util_VirtualRefBasePtr.cpp", "core_jni_helpers.cpp", ":deviceproductinfoconstants_aidl", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index ac187b08f0f1..78d69f0714e0 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -220,6 +220,7 @@ extern int register_com_android_internal_os_Zygote(JNIEnv *env); extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_security_VerityUtils(JNIEnv* env); +extern int register_com_android_internal_util_ArrayUtils(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_android_window_WindowInfosListener(JNIEnv* env); extern int register_android_window_ScreenCapture(JNIEnv* env); @@ -1621,6 +1622,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer), REG_JNI(register_com_android_internal_os_ZygoteInit), REG_JNI(register_com_android_internal_security_VerityUtils), + REG_JNI(register_com_android_internal_util_ArrayUtils), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_camera2_CameraMetadata), diff --git a/core/jni/com_android_internal_util_ArrayUtils.cpp b/core/jni/com_android_internal_util_ArrayUtils.cpp new file mode 100644 index 000000000000..c69e6c903ee5 --- /dev/null +++ b/core/jni/com_android_internal_util_ArrayUtils.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "ArrayUtils" + +#include <android-base/logging.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <string.h> +#include <unistd.h> +#include <utils/Log.h> + +namespace android { + +static size_t GetCacheLineSize() { + long size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); + if (size <= 0) { + ALOGE("Unable to determine L1 data cache line size. Assuming 32 bytes"); + return 32; + } + return size; +} + +#ifdef __aarch64__ +static void CleanDataCache(const uint8_t* p, size_t size, size_t cache_line_size) { + // Execute 'dc cvac' at least once on each cache line in the memory region. + // + // 'dc cvac' stands for "Data Cache line Clean by Virtual Address to point-of-Coherency". + // It writes the cache line back to the "point-of-coherency", i.e. main memory. + // + // Since the memory region is not guaranteed to be cache-line-aligned, we use an "extra" + // instruction after the loop to make sure the last cache line gets covered. + for (size_t i = 0; i < size; i += cache_line_size) { + asm volatile("dc cvac, %0" ::"r"(p + i)); + } + asm volatile("dc cvac, %0" ::"r"(p + size - 1)); +} +#elif defined(__i386__) || defined(__x86_64__) +static void CleanDataCache(const uint8_t* p, size_t size, size_t cache_line_size) { + for (size_t i = 0; i < size; i += cache_line_size) { + asm volatile("clflush (%0)" ::"r"(p + i)); + } + asm volatile("clflush (%0)" ::"r"(p + size - 1)); +} +#elif defined(__riscv) +static void CleanDataCache(const uint8_t* p, size_t size, size_t cache_line_size) { + // This should eventually work, but it is not ready to be enabled yet: + // 1.) The Android emulator needs to add support for zicbom. + // 2.) Kernel needs to enable zicbom in usermode. + // 3.) Android clang needs to add zicbom to the target. +#if 0 + for (size_t i = 0; i < size; i += cache_line_size) { + asm volatile("cbo.clean (%0)" ::"r"(p + i)); + } + asm volatile("cbo.clean (%0)" ::"r"(p + size - 1)); +#endif +} +#elif defined(__arm__) +// arm32 has a cacheflush() syscall, but it is undocumented and only flushes the icache. +// It is not the same as cacheflush(2) as documented in the Linux man-pages project. +static void CleanDataCache(const uint8_t* p, size_t size, size_t cache_line_size) {} +#else +#error "Unknown architecture" +#endif + +static void ZeroizePrimitiveArray(JNIEnv* env, jclass clazz, jarray array, size_t component_len) { + static const size_t cache_line_size = GetCacheLineSize(); + + size_t size = env->GetArrayLength(array) * component_len; + if (size == 0) { + return; + } + + // ART guarantees that GetPrimitiveArrayCritical never copies. + jboolean isCopy; + void* elems = env->GetPrimitiveArrayCritical(array, &isCopy); + CHECK(!isCopy); + +#ifdef __BIONIC__ + memset_explicit(elems, 0, size); +#else + memset(elems, 0, size); +#endif + // Clean the data cache so that the data gets zeroized in main memory right away. Without this, + // it might not be written to main memory until the cache line happens to be evicted. + CleanDataCache(static_cast<const uint8_t*>(elems), size, cache_line_size); + + env->ReleasePrimitiveArrayCritical(array, elems, /* mode= */ 0); +} + +static void ZeroizeByteArray(JNIEnv* env, jclass clazz, jbyteArray array) { + ZeroizePrimitiveArray(env, clazz, array, sizeof(jbyte)); +} + +static void ZeroizeCharArray(JNIEnv* env, jclass clazz, jcharArray array) { + ZeroizePrimitiveArray(env, clazz, array, sizeof(jchar)); +} + +static const JNINativeMethod sMethods[] = { + {"zeroize", "([B)V", (void*)ZeroizeByteArray}, + {"zeroize", "([C)V", (void*)ZeroizeCharArray}, +}; + +int register_com_android_internal_util_ArrayUtils(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/internal/util/ArrayUtils", sMethods, + NELEM(sMethods)); +} + +} // namespace android diff --git a/core/res/Android.bp b/core/res/Android.bp index aacd8699c202..903d08b9a2ab 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -162,6 +162,7 @@ android_app { "android.appwidget.flags-aconfig", "android.companion.virtualdevice.flags-aconfig", "android.content.pm.flags-aconfig", + "android.location.flags-aconfig", "android.media.audio-aconfig", "android.provider.flags-aconfig", "camera_platform_flags", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7d77ff054fe6..9bb1be85c998 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2145,6 +2145,21 @@ <permission android:name="android.permission.CONTROL_AUTOMOTIVE_GNSS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows an application to bind to a + android.service.PopulationDensityProviderService for the purpose of + querying population density. This prevents arbitrary clients connecting + to the service. The system server checks that the provider's intent + service explicitly sets this permission via the android:permission + attribute of the service. + This is only expected to be possessed by the system server outside of + tests. + @FlaggedApi(android.location.flags.Flags.FLAG_POPULATION_DENSITY_PROVIDER) + <p>Protection level: signature + --> + <permission android:name="android.permission.BIND_POPULATION_DENSITY_PROVIDER_SERVICE" + android:featureFlag="android.location.flags.population_density_provider" + android:protectionLevel="signature" /> + <!-- ======================================= --> <!-- Permissions for accessing networks --> <!-- ======================================= --> diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java index 3b9f35b1eb68..3c264f15abd3 100644 --- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java @@ -496,4 +496,51 @@ public class ArrayUtilsTest { // expected } } + + // Note: the zeroize() tests only test the behavior that can be tested from a Java test. + // They do not verify that no copy of the data is left anywhere. + + @Test + @SmallTest + public void testZeroizeNonMovableByteArray() { + final int length = 10; + byte[] array = ArrayUtils.newNonMovableByteArray(length); + assertArrayEquals(array, new byte[length]); + Arrays.fill(array, (byte) 0xff); + ArrayUtils.zeroize(array); + assertArrayEquals(array, new byte[length]); + } + + @Test + @SmallTest + public void testZeroizeRegularByteArray() { + final int length = 10; + byte[] array = new byte[length]; + assertArrayEquals(array, new byte[length]); + Arrays.fill(array, (byte) 0xff); + ArrayUtils.zeroize(array); + assertArrayEquals(array, new byte[length]); + } + + @Test + @SmallTest + public void testZeroizeNonMovableCharArray() { + final int length = 10; + char[] array = ArrayUtils.newNonMovableCharArray(length); + assertArrayEquals(array, new char[length]); + Arrays.fill(array, (char) 0xff); + ArrayUtils.zeroize(array); + assertArrayEquals(array, new char[length]); + } + + @Test + @SmallTest + public void testZeroizeRegularCharArray() { + final int length = 10; + char[] array = new char[length]; + assertArrayEquals(array, new char[length]); + Arrays.fill(array, (char) 0xff); + ArrayUtils.zeroize(array); + assertArrayEquals(array, new char[length]); + } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 897fc543517e..a26f5e383586 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -514,7 +514,6 @@ applications that come with the platform <permission name="android.permission.RENOUNCE_PERMISSIONS" /> <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" /> <permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" /> - <permission name="android.permission.READ_DROPBOX_DATA" /> <permission name="android.permission.READ_LOGS" /> <permission name="android.permission.BRIGHTNESS_SLIDER_USAGE" /> <permission name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" /> diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index 073307c7a2e8..d010c525e099 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -301,10 +301,7 @@ public class Path { * * @param bounds Returns the computed bounds of the path's control points. * @param exact This parameter is no longer used. - * - * @deprecated use computeBounds(RectF) instead */ - @Deprecated @SuppressWarnings({"UnusedDeclaration"}) public void computeBounds(@NonNull RectF bounds, boolean exact) { computeBounds(bounds); diff --git a/native/android/display_luts.cpp b/native/android/display_luts.cpp index 179a32bd1c03..b03a718d4a65 100644 --- a/native/android/display_luts.cpp +++ b/native/android/display_luts.cpp @@ -26,8 +26,9 @@ #define CHECK_NOT_NULL(name) \ LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument"); -ADisplayLutsEntry* ADisplayLutsEntry_createEntry(float* buffer, int32_t length, int32_t dimension, - int32_t key) { +ADisplayLutsEntry* ADisplayLutsEntry_createEntry(float* buffer, int32_t length, + ADisplayLuts_Dimension dimension, + ADisplayLuts_SamplingKey key) { CHECK_NOT_NULL(buffer); LOG_ALWAYS_FATAL_IF(length >= ADISPLAYLUTS_BUFFER_LENGTH_LIMIT, "the lut raw buffer length is too big to handle"); @@ -64,7 +65,7 @@ void ADisplayLutsEntry_destroy(ADisplayLutsEntry* entry) { ADisplayLuts_Dimension ADisplayLutsEntry_getDimension(const ADisplayLutsEntry* entry) { CHECK_NOT_NULL(entry); - return static_cast<ADisplayLuts_Dimension>(entry->properties.dimension); + return entry->properties.dimension; } int32_t ADisplayLutsEntry_getSize(const ADisplayLutsEntry* entry) { @@ -74,7 +75,7 @@ int32_t ADisplayLutsEntry_getSize(const ADisplayLutsEntry* entry) { ADisplayLuts_SamplingKey ADisplayLutsEntry_getSamplingKey(const ADisplayLutsEntry* entry) { CHECK_NOT_NULL(entry); - return static_cast<ADisplayLuts_SamplingKey>(entry->properties.samplingKey); + return entry->properties.samplingKey; } const float* ADisplayLutsEntry_getBuffer(const ADisplayLutsEntry* _Nonnull entry) { diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 6bca1456db3a..4fe0b80f3951 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -64,6 +64,8 @@ static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_RGB) == static_cast<int>(android::gui::LutProperties::SamplingKey::RGB)); static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_MAX_RGB) == static_cast<int>(android::gui::LutProperties::SamplingKey::MAX_RGB)); +static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_CIE_Y) == + static_cast<int>(android::gui::LutProperties::SamplingKey::CIE_Y)); Transaction* ASurfaceTransaction_to_Transaction(ASurfaceTransaction* aSurfaceTransaction) { return reinterpret_cast<Transaction*>(aSurfaceTransaction); diff --git a/nfc/tests/src/android/nfc/NdefRecordTest.java b/nfc/tests/src/android/nfc/NdefRecordTest.java index 231e939b4fbe..044c67448329 100644 --- a/nfc/tests/src/android/nfc/NdefRecordTest.java +++ b/nfc/tests/src/android/nfc/NdefRecordTest.java @@ -24,6 +24,8 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Locale; + @SmallTest @RunWith(AndroidJUnit4.class) public class NdefRecordTest { @@ -56,4 +58,20 @@ public class NdefRecordTest { assertThat(ndefRecord.getType()).isEqualTo(NdefRecord.RTD_URI); } + @Test + public void testCreateMime() { + NdefRecord ndefRecord = NdefRecord.createMime("text/plain", "example".getBytes()); + assertThat(ndefRecord).isNotNull(); + assertThat(ndefRecord.getTnf()).isEqualTo(NdefRecord.TNF_MIME_MEDIA); + } + + @Test + public void testCreateTextRecord() { + String languageCode = Locale.getDefault().getLanguage(); + NdefRecord ndefRecord = NdefRecord.createTextRecord(languageCode, "testdata"); + assertThat(ndefRecord).isNotNull(); + assertThat(ndefRecord.getTnf()).isEqualTo(NdefRecord.TNF_WELL_KNOWN); + assertThat(ndefRecord.getType()).isEqualTo(NdefRecord.RTD_TEXT); + } + } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 0ec5571a7b8f..fa6e2dbe02f3 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -65,7 +65,6 @@ <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" /> <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" /> <uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" /> - <uses-permission android:name="android.permission.READ_DROPBOX_DATA" /> <uses-permission android:name="android.permission.READ_LOGS" /> <uses-permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" /> <uses-permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" /> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 02e7b5f96866..e445884d7051 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -427,6 +427,18 @@ flag { } } + +flag { + name: "status_bar_chips_modernization" + namespace: "systemui" + description: "Deprecate OngoingCallController and implement OngoingActivityChips" + "in compose" + bug: "372657935" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "status_bar_use_repos_for_call_chip" namespace: "systemui" @@ -1204,6 +1216,13 @@ flag { } flag { + name: "communal_responsive_grid" + namespace: "systemui" + description: "Enables responsive grid on glanceable hub" + bug: "378171351" +} + +flag { name: "communal_standalone_support" namespace: "systemui" description: "Support communal features without a dock" diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt new file mode 100644 index 000000000000..2f83d82bbec7 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import org.jetbrains.uast.UClass +import org.jetbrains.uast.getContainingUFile + +class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner { + override fun getApplicableUastTypes() = listOf(UClass::class.java) + + override fun createUastHandler(context: JavaContext) = + object : UElementHandler() { + override fun visitClass(node: UClass) { + for (constructor in node.constructors) { + // Visit all injected constructors in shade-relevant packages + if (!constructor.hasAnnotation(INJECT_ANNOTATION)) continue + if (!isInRelevantShadePackage(node)) continue + if (IGNORED_PACKAGES.contains(node.qualifiedName)) continue + + // Check the any context-dependent parameter to see if it has @ShadeDisplayAware + // annotation + for (parameter in constructor.parameterList.parameters) { + val shouldReport = + CONTEXT_DEPENDENT_SHADE_CLASSES.contains( + parameter.type.canonicalText + ) && !parameter.hasAnnotation(SHADE_DISPLAY_AWARE_ANNOTATION) + if (shouldReport) { + context.report( + issue = ISSUE, + scope = parameter.declarationScope, + location = context.getNameLocation(parameter), + message = reportMsg(className = parameter.type.presentableText), + ) + } + } + } + } + } + + companion object { + private const val INJECT_ANNOTATION = "javax.inject.Inject" + private const val SHADE_DISPLAY_AWARE_ANNOTATION = + "com.android.systemui.shade.ShadeDisplayAware" + + private val CONTEXT_DEPENDENT_SHADE_CLASSES = + setOf( + "android.content.Context", + "android.view.WindowManager", + "android.view.LayoutInflater", + "android.content.res.Resources", + "com.android.systemui.common.ui.ConfigurationState", + "com.android.systemui.statusbar.policy.ConfigurationController", + "com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor", + ) + + private val SHADE_WINDOW_PACKAGES = + listOf( + "com.android.systemui.biometrics", + "com.android.systemui.bouncer", + "com.android.systemui.keyboard.docking.ui.viewmodel", + "com.android.systemui.qs", + "com.android.systemui.shade", + "com.android.systemui.statusbar.notification", + "com.android.systemui.unfold.domain.interactor", + ) + + private val IGNORED_PACKAGES = + setOf( + "com.android.systemui.biometrics.UdfpsController", + "com.android.systemui.qs.customize.TileAdapter", + ) + + private fun isInRelevantShadePackage(node: UClass): Boolean { + val packageName = node.getContainingUFile()?.packageName + if (packageName.isNullOrBlank()) return false + return SHADE_WINDOW_PACKAGES.any { relevantPackage -> + packageName.startsWith(relevantPackage) + } + } + + private fun reportMsg(className: String) = + """UI elements of the shade window + |should use ShadeDisplayAware-annotated $className, as the shade might move between windows, and only + |@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so + |might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). + |If the usage of $className is not related to display specific configuration or UI, then there is + |technically no need to use the annotation, and you can annotate the class with + |@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker") + |""" + .trimMargin() + + @JvmField + val ISSUE: Issue = + Issue.create( + id = "ShadeDisplayAwareContextChecker", + briefDescription = "Using non-ShadeDisplayAware component within shade", + explanation = + """ + Any context-dependent components (Resources, LayoutInflater, ConfigurationState, + etc.) being injected into Shade-relevant classes must have the @ShadeDisplayAware + annotation to ensure they work with when the shade is moved to a different display. + When the shade is moved, the configuration might change, and only @ShadeDisplayAware + components will update accordingly to reflect the new display. + """ + .trimIndent(), + category = Category.CORRECTNESS, + priority = 8, + severity = Severity.ERROR, + implementation = + Implementation(ShadeDisplayAwareDetector::class.java, Scope.JAVA_FILE_SCOPE), + ) + } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt index a1f4f5507e5f..6d18f9377806 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt @@ -46,8 +46,9 @@ class SystemUIIssueRegistry : IssueRegistry() { DemotingTestWithoutBugDetector.ISSUE, TestFunctionNameViolationDetector.ISSUE, MissingApacheLicenseDetector.ISSUE, + ShadeDisplayAwareDetector.ISSUE, RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING, - RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR + RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR, ) override val api: Int @@ -60,6 +61,6 @@ class SystemUIIssueRegistry : IssueRegistry() { Vendor( vendorName = "Android", feedbackUrl = "http://b/issues/new?component=78010", - contact = "jernej@google.com" + contact = "jernej@google.com", ) } diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt new file mode 100644 index 000000000000..58ad363f5b57 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.checks.infrastructure.TestMode +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +class ShadeDisplayAwareDetectorTest : SystemUILintDetectorTest() { + override fun getDetector(): Detector = ShadeDisplayAwareDetector() + + override fun getIssues(): List<Issue> = listOf(ShadeDisplayAwareDetector.ISSUE) + + private val qsContext: TestFile = + java( + """ + package com.android.systemui.qs.dagger; + + import static java.lang.annotation.RetentionPolicy.RUNTIME; + + import java.lang.annotation.Retention; + + @Retention(RUNTIME) public @interface QSThemedContext {} + """ + ) + .indented() + + private val injectStub: TestFile = + kotlin( + """ + package javax.inject + + @Retention(AnnotationRetention.RUNTIME) annotation class Inject + """ + ) + .indented() + + private val shadeDisplayAwareStub: TestFile = + kotlin( + """ + package com.android.systemui.shade + + @Retention(AnnotationRetention.RUNTIME) annotation class ShadeDisplayAware + """ + ) + .indented() + + private val configStateStub: TestFile = + kotlin( + """ + package com.android.systemui.common.ui + + class ConfigurationState + """ + ) + .indented() + + private val configControllerStub: TestFile = + kotlin( + """ + package com.android.systemui.statusbar.policy + + class ConfigurationController + """ + ) + .indented() + + private val configInteractorStub: TestFile = + kotlin( + """ + package com.android.systemui.common.ui.domain.interactor + + class ConfigurationInteractor + """ + ) + .indented() + + private val otherStubs = + arrayOf( + injectStub, + qsContext, + shadeDisplayAwareStub, + configStateStub, + configControllerStub, + configInteractorStub, + ) + + @Test + fun injectedConstructor_inRelevantPackage_withRelevantParameter_withoutAnnotation() { + lint() + .files( + TestFiles.kotlin( + """ + package com.android.systemui.shade.example + + import javax.inject.Inject + import android.content.Context + + class ExampleClass + @Inject + constructor(private val context: Context) + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectErrorCount(1) + .expectContains(errorMsgString(8, "Context")) + .expectContains("[ShadeDisplayAwareContextChecker]") + .expectContains( + "constructor(private val context: Context)\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + ) + .expectContains("1 errors, 0 warnings") + } + + @Test + fun injectedConstructor_inRelevantPackage_withMultipleRelevantParameters_withoutAnnotation() { + lint() + .files( + TestFiles.kotlin( + """ + package com.android.systemui.shade.example + + import javax.inject.Inject + import android.content.Context + import android.content.res.Resources + import android.view.LayoutInflater + import android.view.WindowManager + import com.android.systemui.common.ui.ConfigurationState + import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor + import com.android.systemui.statusbar.policy.ConfigurationController + + class ExampleClass + @Inject + constructor( + private val context: Context, + private val inflater: LayoutInflater, + private val windowManager: WindowManager, + private val configState: ConfigurationState, + private val configController: ConfigurationController, + private val configInteractor: ConfigurationInteractor, + ) + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectErrorCount(6) + .expectContains(errorMsgString(lineNumber = 15, className = "Context")) + .expectContains( + "private val context: Context,\n" + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + ) + .expectContains(errorMsgString(lineNumber = 16, className = "LayoutInflater")) + .expectContains( + "private val inflater: LayoutInflater,\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + ) + .expectContains(errorMsgString(lineNumber = 17, className = "WindowManager")) + .expectContains( + "private val windowManager: WindowManager,\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + ) + .expectContains(errorMsgString(lineNumber = 18, className = "ConfigurationState")) + .expectContains( + "private val configState: ConfigurationState,\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + ) + .expectContains(errorMsgString(lineNumber = 19, className = "ConfigurationController")) + .expectContains( + "private val configController: ConfigurationController,\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + ) + .expectContains(errorMsgString(lineNumber = 20, className = "ConfigurationInteractor")) + .expectContains( + "private val configInteractor: ConfigurationInteractor,\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + ) + .expectContains(" [ShadeDisplayAwareContextChecker]") + } + + @Test + fun injectedConstructor_inRelevantPackage_withRelevantParameter_withAnnotation() { + lint() + .files( + TestFiles.kotlin( + """ + package com.android.systemui.shade.example + + import javax.inject.Inject + import android.content.Context + import com.android.systemui.shade.ShadeDisplayAware + + class ExampleClass + @Inject + constructor(@ShadeDisplayAware private val context: Context) + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectClean() + } + + @Test + fun injectedConstructor_inRelevantPackage_withoutRelevantParameter_withoutAnnotation() { + lint() + .files( + TestFiles.kotlin( + """ + package com.android.systemui.shade.example + + import javax.inject.Inject + import android.content.ContextWrapper + + class ExampleClass + @Inject + constructor(private val contextWrapper: ContextWrapper) + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectClean() + } + + @Test + fun injectedConstructor_notInRelevantPackage_withRelevantParameter_withoutAnnotation() { + lint() + .files( + TestFiles.kotlin( + """ + package com.android.systemui.keyboard + + import javax.inject.Inject + import android.content.Context + + class ExampleClass @Inject constructor(private val context: Context) + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectClean() + } + + @Test + fun nonInjectedConstructor_inRelevantPackage_withRelevantParameter_withoutAnnotation() { + lint() + .files( + TestFiles.kotlin( + """ + package com.android.systemui.shade.example + + import android.content.Context + + class ExampleClass(private val context: Context) + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectClean() + } + + @Test + fun injectedConstructor_inRelevantPackage_withRelevantParameter_withoutAnnotation_suppressed() { + lint() + .files( + TestFiles.kotlin( + """ + package com.android.systemui.shade.example + + import javax.inject.Inject + import android.content.Context + + @Suppress("ShadeDisplayAwareContextChecker") + class ExampleClass + @Inject + constructor( + private val context: Context + ) + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectClean() + } + + @Test + fun injectedConstructor_inExemptPackage_withRelevantParameter_withoutAnnotation() { + lint() + .files( + TestFiles.java( + """ + package com.android.systemui.qs.customize; + + import javax.inject.Inject; + import com.android.systemui.qs.dagger.QSThemedContext; + import android.content.Context; + + public class TileAdapter { + @Inject + public TileAdapter(@QSThemedContext Context context) {} + } + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectClean() + } + + private fun errorMsgString(lineNumber: Int, className: String) = + """ + src/com/android/systemui/shade/example/ExampleClass.kt:$lineNumber: Error: UI elements of the shade window + should use ShadeDisplayAware-annotated $className, as the shade might move between windows, and only + @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so + might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). + If the usage of $className is not related to display specific configuration or UI, then there is + technically no need to use the annotation, and you can annotate the class with + @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker") + """ +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index f3cf521f5fbc..573e5ca5e2d5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -66,6 +66,7 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyGridScope import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed @@ -169,6 +170,7 @@ import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.internal.R.dimen.system_app_widget_background_radius import com.android.systemui.Flags +import com.android.systemui.Flags.communalResponsiveGrid import com.android.systemui.Flags.communalTimerFlickerFix import com.android.systemui.Flags.communalWidgetResizing import com.android.systemui.communal.domain.model.CommunalContentModel @@ -194,7 +196,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.phone.SystemUIDialogFactory import kotlin.math.max import kotlin.math.min -import kotlin.math.roundToInt import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -693,7 +694,12 @@ private fun ResizableItemFrameWrapper( onResize = onResize, minHeightPx = minHeightPx, maxHeightPx = maxHeightPx, - resizeMultiple = CommunalContentSize.HALF.span, + resizeMultiple = + if (communalResponsiveGrid()) { + 1 + } else { + CommunalContentSize.FixedSize.HALF.span + }, ) { content(Modifier) } @@ -701,14 +707,22 @@ private fun ResizableItemFrameWrapper( } @Composable -fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): WidgetSizeInfo { +fun calculateWidgetSize( + cellHeight: Dp?, + availableHeight: Dp?, + item: CommunalContentModel, + isResizable: Boolean, +): WidgetSizeInfo { val density = LocalDensity.current + val minHeight = cellHeight ?: CommunalContentSize.FixedSize.HALF.dp() + val maxHeight = availableHeight ?: CommunalContentSize.FixedSize.FULL.dp() + return if (isResizable && item is CommunalContentModel.WidgetContent.Widget) { with(density) { val minHeightPx = (min(item.providerInfo.minResizeHeight, item.providerInfo.minHeight) - .coerceAtLeast(CommunalContentSize.HALF.dp().toPx().roundToInt())) + .coerceAtLeast(minHeight.roundToPx())) val maxHeightPx = (if (item.providerInfo.maxResizeHeight > 0) { @@ -716,7 +730,7 @@ fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): Widge } else { Int.MAX_VALUE }) - .coerceIn(minHeightPx, CommunalContentSize.FULL.dp().toPx().roundToInt()) + .coerceIn(minHeightPx, maxHeight.roundToPx()) WidgetSizeInfo(minHeightPx, maxHeightPx) } @@ -725,6 +739,37 @@ fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): Widge } } +@Composable +private fun HorizontalGridWrapper( + contentPadding: PaddingValues, + gridState: LazyGridState, + modifier: Modifier = Modifier, + content: LazyGridScope.(sizeInfo: SizeInfo?) -> Unit, +) { + if (communalResponsiveGrid()) { + ResponsiveLazyHorizontalGrid( + cellAspectRatio = 1.5f, + modifier = modifier, + state = gridState, + minContentPadding = contentPadding, + minHorizontalArrangement = Dimensions.ItemSpacing, + minVerticalArrangement = Dimensions.ItemSpacing, + content = content, + ) + } else { + LazyHorizontalGrid( + modifier = modifier, + state = gridState, + rows = GridCells.Fixed(CommunalContentSize.FixedSize.FULL.span), + contentPadding = contentPadding, + horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), + verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), + ) { + content(null) + } + } +} + @OptIn(ExperimentalFoundationApi::class) @Composable private fun BoxScope.CommunalHubLazyGrid( @@ -778,28 +823,32 @@ private fun BoxScope.CommunalHubLazyGrid( // Since the grid has its own listener for in-grid drag events, we use a separate element // for android drag events. Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {} + } else if (communalResponsiveGrid()) { + gridModifier = gridModifier.fillMaxSize() } else { gridModifier = gridModifier.height(hubDimensions.GridHeight) } - val itemArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing) - LazyHorizontalGrid( + HorizontalGridWrapper( modifier = gridModifier, - state = gridState, - rows = GridCells.Fixed(CommunalContentSize.FULL.span), + gridState = gridState, contentPadding = contentPadding, - horizontalArrangement = itemArrangement, - verticalArrangement = itemArrangement, - ) { + ) { sizeInfo -> itemsIndexed( items = list, key = { _, item -> item.key }, contentType = { _, item -> item.key }, - span = { _, item -> GridItemSpan(item.size.span) }, + span = { _, item -> GridItemSpan(item.getSpanOrMax(sizeInfo?.gridSize?.height)) }, ) { index, item -> - val size = SizeF(Dimensions.CardWidth.value, item.size.dp().value) + val currentItemSpan = item.getSpanOrMax(sizeInfo?.gridSize?.height) + val dpSize = + if (sizeInfo != null) { + DpSize(sizeInfo.cellSize.width, sizeInfo.calculateHeight(currentItemSpan)) + } else { + DpSize(Dimensions.CardWidth, (item.size as CommunalContentSize.FixedSize).dp()) + } + val size = SizeF(dpSize.width.value, dpSize.height.value) val selected = item.key == selectedKey.value - val dpSize = DpSize(size.width.dp, size.height.dp) val isResizable = if (item is CommunalContentModel.WidgetContent.Widget) { item.providerInfo.resizeMode and AppWidgetProviderInfo.RESIZE_VERTICAL != 0 @@ -809,7 +858,7 @@ private fun BoxScope.CommunalHubLazyGrid( val resizeableItemFrameViewModel = rememberViewModel( - key = item.size.span, + key = currentItemSpan, traceName = "ResizeableItemFrame.viewModel.$index", ) { ResizeableItemFrameViewModel() @@ -822,13 +871,23 @@ private fun BoxScope.CommunalHubLazyGrid( animationSpec = spring(stiffness = Spring.StiffnessMediumLow), label = "Widget resizing outline alpha", ) - val widgetSizeInfo = calculateWidgetSize(item, isResizable) + + val widgetSizeInfo = + calculateWidgetSize( + cellHeight = sizeInfo?.cellSize?.height, + availableHeight = sizeInfo?.availableHeight, + item = item, + isResizable = isResizable, + ) ResizableItemFrameWrapper( key = item.key, - currentSpan = GridItemSpan(item.size.span), + currentSpan = GridItemSpan(currentItemSpan), gridState = gridState, gridContentPadding = contentPadding, - verticalArrangement = itemArrangement, + verticalArrangement = + Arrangement.spacedBy( + sizeInfo?.verticalArrangement ?: Dimensions.ItemSpacing + ), enabled = selected, alpha = { outlineAlpha }, modifier = @@ -1686,11 +1745,11 @@ private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingIn } } -private fun CommunalContentSize.dp(): Dp { +private fun CommunalContentSize.FixedSize.dp(): Dp { return when (this) { - CommunalContentSize.FULL -> Dimensions.CardHeightFull - CommunalContentSize.HALF -> Dimensions.CardHeightHalf - CommunalContentSize.THIRD -> Dimensions.CardHeightThird + CommunalContentSize.FixedSize.FULL -> Dimensions.CardHeightFull + CommunalContentSize.FixedSize.HALF -> Dimensions.CardHeightHalf + CommunalContentSize.FixedSize.THIRD -> Dimensions.CardHeightThird } } @@ -1709,7 +1768,10 @@ class Dimensions(val context: Context, val config: Configuration) { val GridTopSpacing: Dp get() { val result = - if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { + if ( + communalResponsiveGrid() || + config.orientation == Configuration.ORIENTATION_LANDSCAPE + ) { 114.dp } else { val windowMetrics = @@ -1729,7 +1791,7 @@ class Dimensions(val context: Context, val config: Configuration) { get() = 530.adjustedDp val ItemSpacing - get() = 50.adjustedDp + get() = if (communalResponsiveGrid()) 32.adjustedDp else 50.adjustedDp val CardHeightHalf get() = (CardHeightFull - ItemSpacing) / 2 @@ -1771,6 +1833,13 @@ class Dimensions(val context: Context, val config: Configuration) { data class WidgetSizeInfo(val minHeightPx: Int, val maxHeightPx: Int) +private fun CommunalContentModel.getSpanOrMax(maxSpan: Int?) = + if (maxSpan != null) { + size.span.coerceAtMost(maxSpan) + } else { + size.span + } + private object Colors { val DisabledColorFilter by lazy { disabledColorMatrix() } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt index e3310780afd7..3642127d0823 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt @@ -147,9 +147,9 @@ fun ResponsiveLazyHorizontalGrid( SizeInfo( cellSize = finalSize, contentPadding = finalContentPadding, - horizontalArrangement = minHorizontalArrangement, verticalArrangement = minVerticalArrangement, maxHeight = maxHeight, + gridSize = gridSize, ) ) } @@ -176,16 +176,15 @@ private fun calculateClosestSize(maxWidth: Dp, maxHeight: Dp, aspectRatio: Float * Provides size info of the responsive grid, since the size is dynamic. * * @property cellSize The size of each cell in the grid. - * @property contentPadding The final content padding of the grid. - * @property horizontalArrangement The space between columns in the grid. * @property verticalArrangement The space between rows in the grid. + * @property gridSize The size of the grid, in cell units. * @property availableHeight The maximum height an item in the grid may occupy. */ data class SizeInfo( val cellSize: DpSize, - val contentPadding: PaddingValues, - val horizontalArrangement: Dp, val verticalArrangement: Dp, + val gridSize: IntSize, + private val contentPadding: PaddingValues, private val maxHeight: Dp, ) { val availableHeight: Dp @@ -193,6 +192,11 @@ data class SizeInfo( maxHeight - contentPadding.calculateBottomPadding() - contentPadding.calculateTopPadding() + + /** Calculates the height in dp of a certain number of rows. */ + fun calculateHeight(numRows: Int): Dp { + return numRows * cellSize.height + (numRows - 1) * verticalArrangement + } } @Composable diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml index 7577147a6f16..42694d5ab119 100644 --- a/packages/SystemUI/lint-baseline.xml +++ b/packages/SystemUI/lint-baseline.xml @@ -32784,4 +32784,972 @@ column="23"/> </issue> + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, val theme: Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt" + line="33" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt" + line="39" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @NonNull Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java" + line="300" + column="30"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" Context context, DeviceConfigProxy proxy) {" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java" + line="75" + column="21"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" public AuthController(Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java" + line="716" + column="35"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of WindowManager is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @NonNull WindowManager windowManager," + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java" + line="721" + column="36"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val sysuiContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt" + line="72" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of ConfigurationController is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val configurationController: ConfigurationController," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt" + line="74" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt" + line="32" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java" + line="46" + column="21"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main Resources resources," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java" + line="52" + column="29"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" public BiometricNotificationService(@NonNull Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java" + line="148" + column="58"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val applicationContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt" + line="62" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val applicationContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt" + line="37" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" c: Context," + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt" + line="61" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt" + line="32" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt" + line="33" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val applicationContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt" + line="52" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val applicationContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt" + line="30" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="class CustomTileStatePersisterImpl @Inject constructor(context: Context) :" + errorLine2=" ~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt" + line="74" + column="56"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt" + line="32" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt" + line="18" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt" + line="77" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt" + line="68" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt" + line="38" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt" + line="37" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt" + line="42" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application val applicationContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt" + line="95" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt" + line="142" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt" + line="44" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt" + line="33" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt" + line="32" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @NonNull final Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java" + line="186" + column="36"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of ConfigurationController is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" ConfigurationController configurationController," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java" + line="192" + column="37"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt" + line="32" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="class IconBuilder @Inject constructor(private val context: Context) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt" + line="27" + column="39"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt" + line="31" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of WindowManager is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val windowManager: WindowManager," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt" + line="39" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt" + line="40" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt" + line="41" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt" + line="33" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated LayoutInflater, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of LayoutInflater is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val layoutInflater: LayoutInflater" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt" + line="29" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="class MinimumTilesResourceRepository @Inject constructor(@Main resources: Resources) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt" + line="38" + column="58"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt" + line="33" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt" + line="38" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt" + line="42" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt" + line="32" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" public NotificationGutsManager(Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java" + line="137" + column="44"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt" + line="47" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt" + line="53" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(val context: Context) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt" + line="27" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of ConfigurationController is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" ConfigurationController configurationController," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java" + line="737" + column="37"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt" + line="63" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" Builder(@Main Resources resources, ViewConfiguration viewConfiguration," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java" + line="563" + column="33"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt" + line="32" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" public PackageManagerAdapter(Context context) {" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java" + line="45" + column="42"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt" + line="36" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt" + line="74" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt" + line="41" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt" + line="82" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt" + line="32" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt" + line="36" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" Factory(Context context, QSCustomizerController qsCustomizerController) {" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java" + line="99" + column="25"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt" + line="32" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt" + line="33" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt" + line="36" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt" + line="47" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt" + line="36" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt" + line="51" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt" + line="33" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated LayoutInflater, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of LayoutInflater is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val layoutInflater: LayoutInflater," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt" + line="43" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" context: Context" + errorLine2=" ~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt" + line="35" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val applicationContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt" + line="63" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val applicationContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt" + line="53" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt" + line="51" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of WindowManager is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" windowManager: WindowManager," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt" + line="53" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val applicationContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt" + line="60" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt" + line="65" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt" + line="91" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(context: Context, val shadeViewController: ShadeViewController) {" + errorLine2=" ~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt" + line="30" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt" + line="31" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val context: Context" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt" + line="33" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt" + line="95" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt" + line="33" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Application private val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt" + line="49" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of ConfigurationController is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val configurationController: ConfigurationController," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt" + line="43" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt" + line="37" + column="13"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt" + line="36" + column="5"/> + </issue> + </issues> diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt index 596db0767867..f1c58a2aeac9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt @@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP +import com.android.systemui.communal.shared.model.SpanValue import com.android.systemui.communal.widgets.CommunalWidgetHost import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope @@ -117,7 +118,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() { componentName = defaultWidgets[0], rank = 0, userSerialNumber = 0, - spanY = 3, + spanY = SpanValue.Fixed(3), ) verify(communalWidgetDao) .addWidget( @@ -125,7 +126,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() { componentName = defaultWidgets[1], rank = 1, userSerialNumber = 0, - spanY = 3, + spanY = SpanValue.Fixed(3), ) verify(communalWidgetDao) .addWidget( @@ -133,7 +134,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() { componentName = defaultWidgets[2], rank = 2, userSerialNumber = 0, - spanY = 3, + spanY = SpanValue.Fixed(3), ) } @@ -155,7 +156,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() { componentName = any(), rank = anyInt(), userSerialNumber = anyInt(), - spanY = anyInt(), + spanY = any(), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt index 55d7d08e8519..335e39902983 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt @@ -24,11 +24,15 @@ import android.content.ComponentName import android.content.applicationContext import android.graphics.Bitmap import android.os.UserHandle +import android.os.UserManager import android.os.userManager +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING +import com.android.systemui.Flags.communalResponsiveGrid import com.android.systemui.SysuiTestCase import com.android.systemui.common.data.repository.fakePackageChangeRepository import com.android.systemui.common.shared.model.PackageInstallSession @@ -40,11 +44,15 @@ import com.android.systemui.communal.data.db.defaultWidgetPopulation import com.android.systemui.communal.nano.CommunalHubState import com.android.systemui.communal.proto.toByteArray import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import com.android.systemui.communal.shared.model.SpanValue import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.CommunalWidgetHost import com.android.systemui.communal.widgets.widgetConfiguratorFail import com.android.systemui.communal.widgets.widgetConfiguratorSuccess -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.log.LogBuffer @@ -52,48 +60,55 @@ import com.android.systemui.log.logcatLogBuffer import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mock import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidJUnit4::class) -class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { - @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost - @Mock private lateinit var providerInfoA: AppWidgetProviderInfo - @Mock private lateinit var providerInfoB: AppWidgetProviderInfo - @Mock private lateinit var providerInfoC: AppWidgetProviderInfo - @Mock private lateinit var communalWidgetHost: CommunalWidgetHost - @Mock private lateinit var communalWidgetDao: CommunalWidgetDao - @Mock private lateinit var backupManager: BackupManager +@RunWith(ParameterizedAndroidJunit4::class) +class CommunalWidgetRepositoryLocalImplTest(flags: FlagsParameterization) : SysuiTestCase() { + private val kosmos = testKosmos() + + private val appWidgetHost = mock<CommunalAppWidgetHost>() + private val providerInfoA = mock<AppWidgetProviderInfo>() + private val providerInfoB = mock<AppWidgetProviderInfo>() + private val providerInfoC = mock<AppWidgetProviderInfo>() private val communalHubStateCaptor = argumentCaptor<CommunalHubState>() private val componentNameCaptor = argumentCaptor<ComponentName>() - private lateinit var backupUtils: CommunalBackupUtils - private lateinit var logBuffer: LogBuffer - private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>> - private lateinit var fakeProviders: MutableStateFlow<Map<Int, AppWidgetProviderInfo?>> + private val Kosmos.communalWidgetHost by + Kosmos.Fixture { + mock<CommunalWidgetHost> { on { appWidgetProviders } doReturn fakeProviders } + } + private val Kosmos.communalWidgetDao by + Kosmos.Fixture { mock<CommunalWidgetDao> { on { getWidgets() } doReturn fakeWidgets } } + + private val Kosmos.backupManager by Kosmos.Fixture { mock<BackupManager>() } - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - private val packageChangeRepository = kosmos.fakePackageChangeRepository - private val userManager = kosmos.userManager + private val Kosmos.backupUtils: CommunalBackupUtils by + Kosmos.Fixture { CommunalBackupUtils(applicationContext) } + + private val Kosmos.logBuffer: LogBuffer by + Kosmos.Fixture { logcatLogBuffer(name = "CommunalWidgetRepoLocalImplTest") } + + private val Kosmos.fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>> by + Kosmos.Fixture { MutableStateFlow(emptyMap()) } + + private val Kosmos.fakeProviders: MutableStateFlow<Map<Int, AppWidgetProviderInfo?>> by + Kosmos.Fixture { MutableStateFlow(emptyMap()) } private val mainUser = UserHandle(0) private val workProfile = UserHandle(10) @@ -105,48 +120,49 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { "com.android.fake/WidgetProviderC", ) - private lateinit var underTest: CommunalWidgetRepositoryLocalImpl - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - fakeWidgets = MutableStateFlow(emptyMap()) - fakeProviders = MutableStateFlow(emptyMap()) - logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoLocalImplTest") - backupUtils = CommunalBackupUtils(kosmos.applicationContext) - - setAppWidgetIds(emptyList()) - - overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray()) - - whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets) - whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders) - whenever(userManager.mainUser).thenReturn(mainUser) - - restoreUser(mainUser) - - underTest = + private val Kosmos.underTest by + Kosmos.Fixture { CommunalWidgetRepositoryLocalImpl( appWidgetHost, testScope.backgroundScope, - kosmos.testDispatcher, + testDispatcher, communalWidgetHost, communalWidgetDao, logBuffer, backupManager, backupUtils, - packageChangeRepository, + fakePackageChangeRepository, userManager, - kosmos.defaultWidgetPopulation, + defaultWidgetPopulation, ) + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setUp() { + kosmos.userManager = mock<UserManager> { on { mainUser } doReturn mainUser } + setAppWidgetIds(emptyList()) + overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray()) + restoreUser(mainUser) } @Test fun communalWidgets_queryWidgetsFromDb() = - testScope.runTest { + kosmos.runTest { val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1) val communalWidgetItemEntry = - CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0, 3) + CommunalWidgetItem( + uid = 1L, + widgetId = 1, + componentName = "pk_name/cls_name", + itemId = 1L, + userSerialNumber = 0, + spanY = 3, + spanYNew = 1, + ) fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry) fakeProviders.value = mapOf(1 to providerInfoA) @@ -158,7 +174,12 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { appWidgetId = communalWidgetItemEntry.widgetId, providerInfo = providerInfoA, rank = communalItemRankEntry.rank, - spanY = communalWidgetItemEntry.spanY, + spanY = + if (communalResponsiveGrid()) { + communalWidgetItemEntry.spanYNew + } else { + communalWidgetItemEntry.spanY + }, ) ) @@ -168,18 +189,50 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun communalWidgets_widgetsWithoutMatchingProvidersAreSkipped() = - testScope.runTest { + kosmos.runTest { // Set up 4 widgets, but widget 3 and 4 don't have matching providers fakeWidgets.value = mapOf( CommunalItemRank(uid = 1L, rank = 1) to - CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3), + CommunalWidgetItem( + uid = 1L, + widgetId = 1, + componentName = "pk_1/cls_1", + itemId = 1L, + userSerialNumber = 0, + spanY = 3, + spanYNew = 1, + ), CommunalItemRank(uid = 2L, rank = 2) to - CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3), + CommunalWidgetItem( + uid = 2L, + widgetId = 2, + componentName = "pk_2/cls_2", + itemId = 2L, + userSerialNumber = 0, + spanY = 3, + spanYNew = 1, + ), CommunalItemRank(uid = 3L, rank = 3) to - CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0, 3), + CommunalWidgetItem( + uid = 3L, + widgetId = 3, + componentName = "pk_3/cls_3", + itemId = 3L, + userSerialNumber = 0, + spanY = 3, + spanYNew = 1, + ), CommunalItemRank(uid = 4L, rank = 4) to - CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0, 3), + CommunalWidgetItem( + uid = 4L, + widgetId = 4, + componentName = "pk_4/cls_4", + itemId = 4L, + userSerialNumber = 0, + spanY = 3, + spanYNew = 1, + ), ) fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB) @@ -191,27 +244,43 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { appWidgetId = 1, providerInfo = providerInfoA, rank = 1, - spanY = 3, + spanY = if (communalResponsiveGrid()) 1 else 3, ), CommunalWidgetContentModel.Available( appWidgetId = 2, providerInfo = providerInfoB, rank = 2, - spanY = 3, + spanY = if (communalResponsiveGrid()) 1 else 3, ), ) } @Test fun communalWidgets_updatedWhenProvidersUpdate() = - testScope.runTest { + kosmos.runTest { // Set up widgets and providers fakeWidgets.value = mapOf( CommunalItemRank(uid = 1L, rank = 1) to - CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3), + CommunalWidgetItem( + uid = 1L, + widgetId = 1, + componentName = "pk_1/cls_1", + itemId = 1L, + userSerialNumber = 0, + spanY = 3, + spanYNew = 1, + ), CommunalItemRank(uid = 2L, rank = 2) to - CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3), + CommunalWidgetItem( + uid = 2L, + widgetId = 2, + componentName = "pk_2/cls_2", + itemId = 2L, + userSerialNumber = 0, + spanY = 6, + spanYNew = 2, + ), ) fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB) @@ -224,13 +293,13 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { appWidgetId = 1, providerInfo = providerInfoA, rank = 1, - spanY = 3, + spanY = if (communalResponsiveGrid()) 1 else 3, ), CommunalWidgetContentModel.Available( appWidgetId = 2, providerInfo = providerInfoB, rank = 2, - spanY = 3, + spanY = if (communalResponsiveGrid()) 2 else 6, ), ) @@ -245,20 +314,20 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { // Verify that provider info updated providerInfo = providerInfoC, rank = 1, - spanY = 3, + spanY = if (communalResponsiveGrid()) 1 else 3, ), CommunalWidgetContentModel.Available( appWidgetId = 2, providerInfo = providerInfoB, rank = 2, - spanY = 3, + spanY = if (communalResponsiveGrid()) 2 else 6, ), ) } @Test fun addWidget_allocateId_bindWidget_andAddToDb() = - testScope.runTest { + kosmos.runTest { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val rank = 1 @@ -275,7 +344,8 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser) - verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser)) + verify(communalWidgetDao) + .addWidget(id, provider, rank, testUserSerialNumber(mainUser), SpanValue.Fixed(3)) // Verify backup requested verify(backupManager).dataChanged() @@ -283,7 +353,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun addWidget_configurationFails_doNotAddWidgetToDb() = - testScope.runTest { + kosmos.runTest { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val rank = 1 @@ -301,7 +371,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser) verify(communalWidgetDao, never()) - .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt()) + .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), any()) verify(appWidgetHost).deleteAppWidgetId(id) // Verify backup not requested @@ -310,7 +380,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun addWidget_configurationThrowsError_doNotAddWidgetToDb() = - testScope.runTest { + kosmos.runTest { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val rank = 1 @@ -330,7 +400,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser) verify(communalWidgetDao, never()) - .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt()) + .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), any()) verify(appWidgetHost).deleteAppWidgetId(id) // Verify backup not requested @@ -339,7 +409,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() = - testScope.runTest { + kosmos.runTest { val provider = ComponentName("pkg_name", "cls_name") val id = 1 val rank = 1 @@ -356,7 +426,8 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { runCurrent() verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser) - verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser)) + verify(communalWidgetDao) + .addWidget(id, provider, rank, testUserSerialNumber(mainUser), SpanValue.Fixed(3)) // Verify backup requested verify(backupManager).dataChanged() @@ -364,7 +435,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun deleteWidget_deleteFromDbTrue_alsoDeleteFromHost() = - testScope.runTest { + kosmos.runTest { val id = 1 whenever(communalWidgetDao.deleteWidgetById(eq(id))).thenReturn(true) underTest.deleteWidget(id) @@ -379,7 +450,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun deleteWidget_deleteFromDbFalse_doesNotDeleteFromHost() = - testScope.runTest { + kosmos.runTest { val id = 1 whenever(communalWidgetDao.deleteWidgetById(eq(id))).thenReturn(false) underTest.deleteWidget(id) @@ -394,7 +465,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun reorderWidgets_queryDb() = - testScope.runTest { + kosmos.runTest { val widgetIdToRankMap = mapOf(104 to 1, 103 to 2, 101 to 3) underTest.updateWidgetOrder(widgetIdToRankMap) runCurrent() @@ -407,7 +478,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun restoreWidgets_deleteStateFileIfRestoreFails() = - testScope.runTest { + kosmos.runTest { // Write a state file that is invalid, and verify it is written backupUtils.writeBytesToDisk(byteArrayOf(1, 2, 3, 4, 5, 6)) assertThat(backupUtils.fileExists()).isTrue() @@ -422,7 +493,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun restoreWidgets_deleteStateFileAfterWidgetsRestored() = - testScope.runTest { + kosmos.runTest { // Write a state file, and verify it is written backupUtils.writeBytesToDisk(fakeState.toByteArray()) assertThat(backupUtils.fileExists()).isTrue() @@ -443,7 +514,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun restoreWidgets_restoredWidgetsNotRegisteredWithHostAreSkipped() = - testScope.runTest { + kosmos.runTest { // Write fake state to file backupUtils.writeBytesToDisk(fakeState.toByteArray()) @@ -470,7 +541,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun restoreWidgets_registeredWidgetsNotRestoredAreRemoved() = - testScope.runTest { + kosmos.runTest { // Write fake state to file backupUtils.writeBytesToDisk(fakeState.toByteArray()) @@ -504,7 +575,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun restoreWidgets_onlySomeWidgetsGotNewIds() = - testScope.runTest { + kosmos.runTest { // Write fake state to file backupUtils.writeBytesToDisk(fakeState.toByteArray()) @@ -536,7 +607,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun restoreWidgets_undefinedUser_restoredAsMain() = - testScope.runTest { + kosmos.runTest { // Write two widgets to file, both of which have user serial number undefined. val fakeState = CommunalHubState().apply { @@ -584,7 +655,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun restoreWidgets_workProfileNotRestored_widgetSkipped() = - testScope.runTest { + kosmos.runTest { // Write fake state to file backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray()) @@ -610,7 +681,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun restoreWidgets_workProfileRestored_manuallyBindWidget() = - testScope.runTest { + kosmos.runTest { // Write fake state to file backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray()) @@ -649,7 +720,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { componentNameCaptor.capture(), eq(2), eq(testUserSerialNumber(workProfile)), - anyInt(), + any(), ) assertThat(componentNameCaptor.firstValue) @@ -658,13 +729,29 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { @Test fun pendingWidgets() = - testScope.runTest { + kosmos.runTest { fakeWidgets.value = mapOf( CommunalItemRank(uid = 1L, rank = 1) to - CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3), + CommunalWidgetItem( + uid = 1L, + widgetId = 1, + componentName = "pk_1/cls_1", + itemId = 1L, + userSerialNumber = 0, + spanY = 3, + spanYNew = 1, + ), CommunalItemRank(uid = 2L, rank = 2) to - CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3), + CommunalWidgetItem( + uid = 2L, + widgetId = 2, + componentName = "pk_2/cls_2", + itemId = 2L, + userSerialNumber = 0, + spanY = 3, + spanYNew = 1, + ), ) // Widget 1 is installed @@ -672,7 +759,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { // Widget 2 is pending install val fakeIcon = mock<Bitmap>() - packageChangeRepository.setInstallSessions( + fakePackageChangeRepository.setInstallSessions( listOf( PackageInstallSession( sessionId = 1, @@ -690,7 +777,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { appWidgetId = 1, providerInfo = providerInfoA, rank = 1, - spanY = 3, + spanY = if (communalResponsiveGrid()) 1 else 3, ), CommunalWidgetContentModel.Pending( appWidgetId = 2, @@ -698,23 +785,31 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { componentName = ComponentName("pk_2", "cls_2"), icon = fakeIcon, user = mainUser, - spanY = 3, + spanY = if (communalResponsiveGrid()) 1 else 3, ), ) } @Test fun pendingWidgets_pendingWidgetBecomesAvailableAfterInstall() = - testScope.runTest { + kosmos.runTest { fakeWidgets.value = mapOf( CommunalItemRank(uid = 1L, rank = 1) to - CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3) + CommunalWidgetItem( + uid = 1L, + widgetId = 1, + componentName = "pk_1/cls_1", + itemId = 1L, + userSerialNumber = 0, + spanY = 3, + spanYNew = 1, + ) ) // Widget 1 is pending install val fakeIcon = mock<Bitmap>() - packageChangeRepository.setInstallSessions( + fakePackageChangeRepository.setInstallSessions( listOf( PackageInstallSession( sessionId = 1, @@ -734,12 +829,12 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { componentName = ComponentName("pk_1", "cls_1"), icon = fakeIcon, user = mainUser, - spanY = 3, + spanY = if (communalResponsiveGrid()) 1 else 3, ) ) // Package for widget 1 finished installing - packageChangeRepository.setInstallSessions(emptyList()) + fakePackageChangeRepository.setInstallSessions(emptyList()) // Provider info for widget 1 becomes available fakeProviders.value = mapOf(1 to providerInfoA) @@ -752,15 +847,32 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { appWidgetId = 1, providerInfo = providerInfoA, rank = 1, - spanY = 3, + spanY = if (communalResponsiveGrid()) 1 else 3, ) ) } @Test @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING) - fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup() = - testScope.runTest { + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) + fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup_fixed() = + kosmos.runTest { + val widgetId = 1 + val newSpanY = 6 + val widgetIdToRankMap = emptyMap<Int, Int>() + + underTest.resizeWidget(widgetId, newSpanY, widgetIdToRankMap) + runCurrent() + + verify(communalWidgetDao) + .resizeWidget(widgetId, SpanValue.Fixed(newSpanY), widgetIdToRankMap) + verify(backupManager).dataChanged() + } + + @Test + @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID) + fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup_responsive() = + kosmos.runTest { val widgetId = 1 val newSpanY = 6 val widgetIdToRankMap = emptyMap<Int, Int>() @@ -768,7 +880,8 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { underTest.resizeWidget(widgetId, newSpanY, widgetIdToRankMap) runCurrent() - verify(communalWidgetDao).resizeWidget(widgetId, newSpanY, widgetIdToRankMap) + verify(communalWidgetDao) + .resizeWidget(widgetId, SpanValue.Responsive(newSpanY), widgetIdToRankMap) verify(backupManager).dataChanged() } @@ -784,13 +897,19 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() { } private fun restoreUser(user: UserHandle) { - whenever(backupManager.getUserForAncestralSerialNumber(user.identifier.toLong())) + whenever(kosmos.backupManager.getUserForAncestralSerialNumber(user.identifier.toLong())) .thenReturn(user) - whenever(userManager.getUserSerialNumber(user.identifier)) + whenever(kosmos.userManager.getUserSerialNumber(user.identifier)) .thenReturn(testUserSerialNumber(user)) } - private companion object { + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID) + } + val PROVIDER_INFO_REQUIRES_CONFIGURATION = AppWidgetProviderInfo().apply { configure = ComponentName("test.pkg", "test.cmp") } val PROVIDER_INFO_CONFIGURATION_OPTIONAL = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 611a61a6282c..b9e646fee98f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -24,14 +24,16 @@ import android.content.pm.UserInfo import android.os.UserHandle import android.os.UserManager import android.os.userManager +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED import android.widget.RemoteViews -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher @@ -96,6 +98,8 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters /** * This class of test cases assume that communal is enabled. For disabled cases, see @@ -103,8 +107,8 @@ import org.mockito.MockitoAnnotations */ @SmallTest @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidJUnit4::class) -class CommunalInteractorTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Mock private lateinit var mainUser: UserInfo @Mock private lateinit var secondaryUser: UserInfo @@ -129,6 +133,10 @@ class CommunalInteractorTest : SysuiTestCase() { private lateinit var underTest: CommunalInteractor + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -262,71 +270,84 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(widgetContent!![2].appWidgetId).isEqualTo(3) } + /** TODO(b/378171351): Handle ongoing content in responsive grid. */ @Test + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun smartspaceDynamicSizing_oneCard_fullSize() = testSmartspaceDynamicSizing( totalTargets = 1, - expectedSizes = listOf(CommunalContentSize.FULL), + expectedSizes = listOf(CommunalContentSize.FixedSize.FULL), ) + /** TODO(b/378171351): Handle ongoing content in responsive grid. */ @Test + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun smartspace_dynamicSizing_twoCards_halfSize() = testSmartspaceDynamicSizing( totalTargets = 2, - expectedSizes = listOf(CommunalContentSize.HALF, CommunalContentSize.HALF), + expectedSizes = + listOf(CommunalContentSize.FixedSize.HALF, CommunalContentSize.FixedSize.HALF), ) + /** TODO(b/378171351): Handle ongoing content in responsive grid. */ @Test + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun smartspace_dynamicSizing_threeCards_thirdSize() = testSmartspaceDynamicSizing( totalTargets = 3, expectedSizes = listOf( - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, ), ) + /** TODO(b/378171351): Handle ongoing content in responsive grid. */ @Test + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun smartspace_dynamicSizing_fourCards_threeThirdSizeAndOneFullSize() = testSmartspaceDynamicSizing( totalTargets = 4, expectedSizes = listOf( - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, - CommunalContentSize.FULL, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.FULL, ), ) + /** TODO(b/378171351): Handle ongoing content in responsive grid. */ @Test + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun smartspace_dynamicSizing_fiveCards_threeThirdAndTwoHalfSize() = testSmartspaceDynamicSizing( totalTargets = 5, expectedSizes = listOf( - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, - CommunalContentSize.HALF, - CommunalContentSize.HALF, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.HALF, + CommunalContentSize.FixedSize.HALF, ), ) + /** TODO(b/378171351): Handle ongoing content in responsive grid. */ @Test + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun smartspace_dynamicSizing_sixCards_allThirdSize() = testSmartspaceDynamicSizing( totalTargets = 6, expectedSizes = listOf( - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, - CommunalContentSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, + CommunalContentSize.FixedSize.THIRD, ), ) @@ -383,7 +404,9 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(umoContent?.size).isEqualTo(0) } + /** TODO(b/378171351): Handle ongoing content in responsive grid. */ @Test + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun ongoing_shouldOrderAndSizeByTimestamp() = testScope.runTest { // Keyguard showing, and tutorial completed. @@ -410,15 +433,15 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(ongoingContent?.size).isEqualTo(4) assertThat(ongoingContent?.get(0)?.key) .isEqualTo(CommunalContentModel.KEY.smartspace("timer3")) - assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.HALF) + assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF) assertThat(ongoingContent?.get(1)?.key) .isEqualTo(CommunalContentModel.KEY.smartspace("timer2")) - assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.HALF) + assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF) assertThat(ongoingContent?.get(2)?.key).isEqualTo(CommunalContentModel.KEY.umo()) - assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.HALF) + assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF) assertThat(ongoingContent?.get(3)?.key) .isEqualTo(CommunalContentModel.KEY.smartspace("timer1")) - assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.HALF) + assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF) } @Test @@ -1082,6 +1105,7 @@ class CommunalInteractorTest : SysuiTestCase() { @Test @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING) + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_withoutUpdatingOrder() = testScope.runTest { val userInfos = listOf(MAIN_USER_INFO) @@ -1094,45 +1118,97 @@ class CommunalInteractorTest : SysuiTestCase() { appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, - spanY = CommunalContentSize.HALF.span, + spanY = CommunalContentSize.FixedSize.HALF.span, ) widgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, - spanY = CommunalContentSize.HALF.span, + spanY = CommunalContentSize.FixedSize.HALF.span, ) widgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, - spanY = CommunalContentSize.HALF.span, + spanY = CommunalContentSize.FixedSize.HALF.span, ) val widgetContent by collectLastValue(underTest.widgetContent) assertThat(widgetContent?.map { it.appWidgetId to it.size }) .containsExactly( - 1 to CommunalContentSize.HALF, - 2 to CommunalContentSize.HALF, - 3 to CommunalContentSize.HALF, + 1 to CommunalContentSize.FixedSize.HALF, + 2 to CommunalContentSize.FixedSize.HALF, + 3 to CommunalContentSize.FixedSize.HALF, ) .inOrder() - underTest.resizeWidget(2, CommunalContentSize.FULL.span, emptyMap()) + underTest.resizeWidget(2, CommunalContentSize.FixedSize.FULL.span, emptyMap()) // Widget 2 should have been resized to FULL assertThat(widgetContent?.map { it.appWidgetId to it.size }) .containsExactly( - 1 to CommunalContentSize.HALF, - 2 to CommunalContentSize.FULL, - 3 to CommunalContentSize.HALF, + 1 to CommunalContentSize.FixedSize.HALF, + 2 to CommunalContentSize.FixedSize.FULL, + 3 to CommunalContentSize.FixedSize.HALF, + ) + .inOrder() + } + + @Test + @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID) + fun resizeWidget_withoutUpdatingOrder_responsive() = + testScope.runTest { + val userInfos = listOf(MAIN_USER_INFO) + userRepository.setUserInfos(userInfos) + userTracker.set(userInfos = userInfos, selectedUserIndex = 0) + runCurrent() + + // Widgets available. + widgetRepository.addWidget( + appWidgetId = 1, + userId = MAIN_USER_INFO.id, + rank = 0, + spanY = 1, + ) + widgetRepository.addWidget( + appWidgetId = 2, + userId = MAIN_USER_INFO.id, + rank = 1, + spanY = 1, + ) + widgetRepository.addWidget( + appWidgetId = 3, + userId = MAIN_USER_INFO.id, + rank = 2, + spanY = 1, + ) + + val widgetContent by collectLastValue(underTest.widgetContent) + + assertThat(widgetContent?.map { it.appWidgetId to it.size }) + .containsExactly( + 1 to CommunalContentSize.Responsive(1), + 2 to CommunalContentSize.Responsive(1), + 3 to CommunalContentSize.Responsive(1), + ) + .inOrder() + + underTest.resizeWidget(appWidgetId = 2, spanY = 5, widgetIdToRankMap = emptyMap()) + + // Widget 2 should have been resized to FULL + assertThat(widgetContent?.map { it.appWidgetId to it.size }) + .containsExactly( + 1 to CommunalContentSize.Responsive(1), + 2 to CommunalContentSize.Responsive(5), + 3 to CommunalContentSize.Responsive(1), ) .inOrder() } @Test @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING) + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_andUpdateOrder() = testScope.runTest { val userInfos = listOf(MAIN_USER_INFO) @@ -1145,39 +1221,98 @@ class CommunalInteractorTest : SysuiTestCase() { appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, - spanY = CommunalContentSize.HALF.span, + spanY = CommunalContentSize.FixedSize.HALF.span, + ) + widgetRepository.addWidget( + appWidgetId = 2, + userId = MAIN_USER_INFO.id, + rank = 1, + spanY = CommunalContentSize.FixedSize.HALF.span, + ) + widgetRepository.addWidget( + appWidgetId = 3, + userId = MAIN_USER_INFO.id, + rank = 2, + spanY = CommunalContentSize.FixedSize.HALF.span, + ) + + val widgetContent by collectLastValue(underTest.widgetContent) + + assertThat(widgetContent?.map { it.appWidgetId to it.size }) + .containsExactly( + 1 to CommunalContentSize.FixedSize.HALF, + 2 to CommunalContentSize.FixedSize.HALF, + 3 to CommunalContentSize.FixedSize.HALF, + ) + .inOrder() + + underTest.resizeWidget( + 2, + CommunalContentSize.FixedSize.FULL.span, + mapOf(2 to 0, 1 to 1), + ) + + // Widget 2 should have been resized to FULL and moved to the front of the list + assertThat(widgetContent?.map { it.appWidgetId to it.size }) + .containsExactly( + 2 to CommunalContentSize.FixedSize.FULL, + 1 to CommunalContentSize.FixedSize.HALF, + 3 to CommunalContentSize.FixedSize.HALF, + ) + .inOrder() + } + + @Test + @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID) + fun resizeWidget_andUpdateOrder_responsive() = + testScope.runTest { + val userInfos = listOf(MAIN_USER_INFO) + userRepository.setUserInfos(userInfos) + userTracker.set(userInfos = userInfos, selectedUserIndex = 0) + runCurrent() + + // Widgets available. + widgetRepository.addWidget( + appWidgetId = 1, + userId = MAIN_USER_INFO.id, + rank = 0, + spanY = 1, ) widgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, - spanY = CommunalContentSize.HALF.span, + spanY = 1, ) widgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, - spanY = CommunalContentSize.HALF.span, + spanY = 1, ) val widgetContent by collectLastValue(underTest.widgetContent) assertThat(widgetContent?.map { it.appWidgetId to it.size }) .containsExactly( - 1 to CommunalContentSize.HALF, - 2 to CommunalContentSize.HALF, - 3 to CommunalContentSize.HALF, + 1 to CommunalContentSize.Responsive(1), + 2 to CommunalContentSize.Responsive(1), + 3 to CommunalContentSize.Responsive(1), ) .inOrder() - underTest.resizeWidget(2, CommunalContentSize.FULL.span, mapOf(2 to 0, 1 to 1)) + underTest.resizeWidget( + appWidgetId = 2, + spanY = 5, + widgetIdToRankMap = mapOf(2 to 0, 1 to 1), + ) // Widget 2 should have been resized to FULL and moved to the front of the list assertThat(widgetContent?.map { it.appWidgetId to it.size }) .containsExactly( - 2 to CommunalContentSize.FULL, - 1 to CommunalContentSize.HALF, - 3 to CommunalContentSize.HALF, + 2 to CommunalContentSize.Responsive(5), + 1 to CommunalContentSize.Responsive(1), + 3 to CommunalContentSize.Responsive(1), ) .inOrder() } @@ -1191,9 +1326,15 @@ class CommunalInteractorTest : SysuiTestCase() { ) } - private companion object { - val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) - val USER_INFO_WORK = + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID) + } + + private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) + private val USER_INFO_WORK = UserInfo( 10, "work", diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 3eba8ff4b198..763ea392deb9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -18,12 +18,14 @@ package com.android.systemui.communal.view.viewmodel import android.content.ComponentName import android.content.pm.UserInfo +import android.platform.test.annotations.DisableFlags import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import android.widget.RemoteViews import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.model.CommunalSmartspaceTimer import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository @@ -248,7 +250,9 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java) } + /** TODO(b/378171351): Handle ongoing content in responsive grid. */ @Test + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun ongoingContent_umoAndOneTimer_sizedAppropriately() = testScope.runTest { // Widgets available. @@ -280,11 +284,13 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat(timer).isInstanceOf(CommunalContentModel.Smartspace::class.java) assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java) - assertThat(timer?.size).isEqualTo(CommunalContentSize.HALF) - assertThat(umo?.size).isEqualTo(CommunalContentSize.HALF) + assertThat(timer?.size).isEqualTo(CommunalContentSize.FixedSize.HALF) + assertThat(umo?.size).isEqualTo(CommunalContentSize.FixedSize.HALF) } + /** TODO(b/378171351): Handle ongoing content in responsive grid. */ @Test + @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun ongoingContent_umoAndTwoTimers_sizedAppropriately() = testScope.runTest { // Widgets available. @@ -324,9 +330,9 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java) // One full-sized timer and a half-sized timer and half-sized UMO. - assertThat(timer1?.size).isEqualTo(CommunalContentSize.HALF) - assertThat(timer2?.size).isEqualTo(CommunalContentSize.HALF) - assertThat(umo?.size).isEqualTo(CommunalContentSize.FULL) + assertThat(timer1?.size).isEqualTo(CommunalContentSize.FixedSize.HALF) + assertThat(timer2?.size).isEqualTo(CommunalContentSize.FixedSize.HALF) + assertThat(umo?.size).isEqualTo(CommunalContentSize.FixedSize.FULL) } @Test @@ -891,7 +897,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { @JvmStatic @Parameters(name = "{0}") fun getParams(): List<FlagsParameterization> { - return FlagsParameterization.allCombinationsOf().andSceneContainer() + return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID) + .andSceneContainer() } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt index c287da8df135..407bdf8dcefb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt @@ -662,7 +662,7 @@ object TestShortcuts { val standardAddShortcutRequest = ShortcutCustomizationRequestInfo.Add( label = "Standard shortcut", - categoryType = ShortcutCategoryType.System, + categoryType = System, subCategoryLabel = "Standard subcategory", ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt index 2d05ee0fe234..755c218f6789 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt @@ -108,7 +108,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset) val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - assertThat(uiState).isEqualTo(ResetShortcutDialog()) + assertThat(uiState).isEqualTo(ResetShortcutDialog) } } @@ -118,45 +118,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - assertThat(uiState).isEqualTo(DeleteShortcutDialog()) - } - } - - @Test - fun uiState_consumedOnAddDialogShown() { - testScope.runTest { - val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest) - viewModel.onDialogShown() - - assertThat((uiState as AddShortcutDialog).isDialogShowing) - .isTrue() - } - } - - @Test - fun uiState_consumedOnDeleteDialogShown() { - testScope.runTest { - val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) - viewModel.onDialogShown() - - assertThat( - (uiState as DeleteShortcutDialog).isDialogShowing - ) - .isTrue() - } - } - - @Test - fun uiState_consumedOnResetDialogShown() { - testScope.runTest { - val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) - viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset) - viewModel.onDialogShown() - - assertThat((uiState as ResetShortcutDialog).isDialogShowing) - .isTrue() + assertThat(uiState).isEqualTo(DeleteShortcutDialog) } } @@ -165,7 +127,6 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { testScope.runTest { val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest) - viewModel.onDialogShown() viewModel.onDialogDismissed() assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive) } @@ -199,7 +160,6 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { testScope.runTest { val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest) - viewModel.onDialogShown() assertThat((uiState as AddShortcutDialog).errorMessage) .isEmpty() @@ -339,7 +299,6 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { private suspend fun openAddShortcutDialogAndSetShortcut() { viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest) - viewModel.onDialogShown() viewModel.onKeyPressed(keyDownEventWithActionKeyPressed) viewModel.onKeyPressed(keyUpEventWithActionKeyPressed) @@ -349,14 +308,12 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { private suspend fun openDeleteShortcutDialogAndDeleteShortcut() { viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) - viewModel.onDialogShown() viewModel.deleteShortcutCurrentlyBeingCustomized() } private suspend fun openResetShortcutDialogAndResetAllCustomShortcuts() { viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset) - viewModel.onDialogShown() viewModel.resetAllCustomShortcuts() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt index 3bd249689da9..78fce276a5f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt @@ -18,6 +18,13 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel import android.app.role.RoleManager import android.app.role.mockRoleManager +import android.content.Context +import android.content.Context.INPUT_SERVICE +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.NameNotFoundException +import android.hardware.input.InputGestureData +import android.hardware.input.fakeInputManager import android.view.KeyEvent import android.view.KeyboardShortcutGroup import android.view.KeyboardShortcutInfo @@ -31,6 +38,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking @@ -56,7 +64,6 @@ import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.fakeUserTracker import com.android.systemui.settings.userTracker import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -64,6 +71,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -73,6 +84,9 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { private val fakeSystemSource = FakeKeyboardShortcutGroupsSource() private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource() private val fakeCurrentAppsSource = FakeKeyboardShortcutGroupsSource() + private val mockPackageManager: PackageManager = mock() + private val mockUserContext: Context = mock() + private val mockApplicationInfo: ApplicationInfo = mock() private val kosmos = Kosmos().also { @@ -83,7 +97,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource - it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { context }) + it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } private val testScope = kosmos.testScope @@ -91,13 +105,20 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { private val sysUiState = kosmos.sysUiState private val fakeUserTracker = kosmos.fakeUserTracker private val mockRoleManager = kosmos.mockRoleManager + private val inputManager = kosmos.fakeInputManager.inputManager private val viewModel = kosmos.shortcutHelperViewModel + @Before fun setUp() { fakeSystemSource.setGroups(TestShortcuts.systemGroups) fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups) fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups) + whenever(mockPackageManager.getApplicationInfo(anyString(), eq(0))).thenReturn(mockApplicationInfo) + whenever(mockPackageManager.getApplicationLabel(mockApplicationInfo)).thenReturn("Current App") + whenever(mockPackageManager.getApplicationIcon(anyString())).thenThrow(NameNotFoundException()) + whenever(mockUserContext.packageManager).thenReturn(mockPackageManager) + whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) } @Test @@ -259,11 +280,11 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { fun shortcutsUiState_currentAppIsLauncher_defaultSelectedCategoryIsSystem() = testScope.runTest { whenever( - mockRoleManager.getRoleHoldersAsUser( - RoleManager.ROLE_HOME, - fakeUserTracker.userHandle, - ) + mockRoleManager.getRoleHoldersAsUser( + RoleManager.ROLE_HOME, + fakeUserTracker.userHandle, ) + ) .thenReturn(listOf(TestShortcuts.currentAppPackageName)) val uiState by collectLastValue(viewModel.shortcutsUiState) @@ -299,23 +320,23 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { label = "System", iconSource = IconSource(imageVector = Icons.Default.Tv), shortcutCategory = - ShortcutCategory( - System, - subCategoryWithShortcutLabels("first Foo shortcut1"), - subCategoryWithShortcutLabels( - "second foO shortcut2", - subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL, - ), + ShortcutCategory( + System, + subCategoryWithShortcutLabels("first Foo shortcut1"), + subCategoryWithShortcutLabels( + "second foO shortcut2", + subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL, ), + ), ), ShortcutCategoryUi( label = "Multitasking", iconSource = IconSource(imageVector = Icons.Default.VerticalSplit), shortcutCategory = - ShortcutCategory( - MultiTasking, - subCategoryWithShortcutLabels("third FoO shortcut1"), - ), + ShortcutCategory( + MultiTasking, + subCategoryWithShortcutLabels("third FoO shortcut1"), + ), ), ) } @@ -387,6 +408,31 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { assertThat(activeUiState.defaultSelectedCategory).isInstanceOf(CurrentApp::class.java) } + @Test + fun shortcutsUiState_shouldShowResetButton_isFalseWhenThereAreNoCustomShortcuts() = + testScope.runTest { + val uiState by collectLastValue(viewModel.shortcutsUiState) + + testHelper.showFromActivity() + + val activeUiState = uiState as ShortcutsUiState.Active + assertThat(activeUiState.shouldShowResetButton).isFalse() + } + + @Test + fun shortcutsUiState_shouldShowResetButton_isTrueWhenThereAreCustomShortcuts() = + testScope.runTest { + whenever( + inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) + ).thenReturn(listOf(allAppsInputGestureData)) + val uiState by collectLastValue(viewModel.shortcutsUiState) + + testHelper.showFromActivity() + + val activeUiState = uiState as ShortcutsUiState.Active + assertThat(activeUiState.shouldShowResetButton).isTrue() + } + private fun groupWithShortcutLabels( vararg shortcutLabels: String, groupLabel: String = FIRST_SIMPLE_GROUP_LABEL, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt index 9f84e346d54a..f33de4d9144d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt @@ -128,7 +128,7 @@ class InternetTileNewImplTest : SysuiTestCase() { viewModel, dialogManager, wifiStateWorker, - accessPointController + accessPointController, ) underTest.initialize() @@ -156,10 +156,7 @@ class InternetTileNewImplTest : SysuiTestCase() { testScope.runTest { connectivityRepository.defaultConnections.value = DefaultConnectionModel() wifiRepository.wifiScanResults.value = - listOf( - WifiScanEntry(ssid = "ssid 1"), - WifiScanEntry(ssid = "ssid 2"), - ) + listOf(WifiScanEntry(ssid = "ssid 1"), WifiScanEntry(ssid = "ssid 2")) runCurrent() looper.processAllMessages() @@ -204,10 +201,7 @@ class InternetTileNewImplTest : SysuiTestCase() { testScope.runTest { airplaneModeRepository.setIsAirplaneMode(true) connectivityRepository.defaultConnections.value = - DefaultConnectionModel( - wifi = Wifi(true), - isValidated = true, - ) + DefaultConnectionModel(wifi = Wifi(true), isValidated = true) wifiRepository.setIsWifiEnabled(true) wifiRepository.setWifiNetwork(ACTIVE_WIFI) @@ -222,10 +216,7 @@ class InternetTileNewImplTest : SysuiTestCase() { fun wifiConnected() = testScope.runTest { connectivityRepository.defaultConnections.value = - DefaultConnectionModel( - wifi = Wifi(true), - isValidated = true, - ) + DefaultConnectionModel(wifi = Wifi(true), isValidated = true) wifiRepository.setIsWifiEnabled(true) wifiRepository.setWifiNetwork(ACTIVE_WIFI) @@ -242,6 +233,7 @@ class InternetTileNewImplTest : SysuiTestCase() { whenever(wifiStateWorker.isWifiEnabled).thenReturn(true) underTest.secondaryClick(null) + looper.processAllMessages() verify(wifiStateWorker, times(1)).isWifiEnabled = eq(false) } @@ -251,6 +243,7 @@ class InternetTileNewImplTest : SysuiTestCase() { whenever(wifiStateWorker.isWifiEnabled).thenReturn(false) underTest.secondaryClick(null) + looper.processAllMessages() verify(wifiStateWorker, times(1)).isWifiEnabled = eq(true) } @@ -258,10 +251,6 @@ class InternetTileNewImplTest : SysuiTestCase() { companion object { const val WIFI_SSID = "test ssid" val ACTIVE_WIFI = - WifiNetworkModel.Active.of( - isValidated = true, - level = 4, - ssid = WIFI_SSID, - ) + WifiNetworkModel.Active.of(isValidated = true, level = 4, ssid = WIFI_SSID) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java index 0cf96047fcc0..b5ec0a022dd0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java @@ -180,6 +180,7 @@ public class InternetTileTest extends SysuiTestCase { when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); mTile.secondaryClick(null); + mTestableLooper.processAllMessages(); verify(mWifiStateWorker, times(1)).setWifiEnabled(eq(false)); } @@ -189,6 +190,7 @@ public class InternetTileTest extends SysuiTestCase { when(mWifiStateWorker.isWifiEnabled()).thenReturn(false); mTile.secondaryClick(null); + mTestableLooper.processAllMessages(); verify(mWifiStateWorker, times(1)).setWifiEnabled(eq(true)); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index 03c1f92aad4c..4068d9fd7f3d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -46,6 +46,9 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Handler; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.service.quickaccesswallet.Flags; import android.service.quickaccesswallet.GetWalletCardsError; import android.service.quickaccesswallet.GetWalletCardsResponse; import android.service.quickaccesswallet.QuickAccessWalletClient; @@ -221,6 +224,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { } @Test + @DisableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE}) public void testHandleClick_startQuickAccessUiIntent_noCard() { setUpWalletCard(/* hasCard= */ false); @@ -234,6 +238,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { } @Test + @DisableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE}) public void testHandleClick_startQuickAccessUiIntent_hasCard() { setUpWalletCard(/* hasCard= */ true); @@ -247,6 +252,34 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { } @Test + @EnableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE}) + public void testHandleClick_startCardIntent_noCard() { + setUpWalletCard(/* hasCard= */ false); + + mTile.handleClick(/* view= */ null); + mTestableLooper.processAllMessages(); + + verify(mController).startQuickAccessUiIntent( + eq(mActivityStarter), + eq(null), + /* hasCard= */ eq(false)); + } + + @Test + @EnableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE}) + public void testHandleClick_startCardIntent_hasCard() { + setUpWalletCard(/* hasCard= */ true); + + mTile.handleClick(null /* view */); + mTestableLooper.processAllMessages(); + + verify(mController).startWalletCardPendingIntent( + any(), + eq(mActivityStarter), + eq(null)); + } + + @Test public void testHandleUpdateState_updateLabelAndIcon() { QSTile.State state = new QSTile.State(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java deleted file mode 100644 index 3bd12e6efab6..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.phone; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.platform.test.annotations.DisableFlags; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; -import android.view.View; -import android.widget.TextView; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shade.ShadeHeadsUpTracker; -import com.android.systemui.shade.ShadeViewController; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.HeadsUpStatusBarView; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor; -import com.android.systemui.statusbar.notification.headsup.PinnedStatus; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotificationTestHelper; -import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; -import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; -import com.android.systemui.statusbar.policy.Clock; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Optional; - -@SmallTest -@RunWith(AndroidJUnit4.class) -@RunWithLooper -public class HeadsUpAppearanceControllerTest extends SysuiTestCase { - - private final NotificationStackScrollLayoutController mStackScrollerController = - mock(NotificationStackScrollLayoutController.class); - private final ShadeViewController mShadeViewController = - mock(ShadeViewController.class); - private final ShadeHeadsUpTracker mShadeHeadsUpTracker = mock(ShadeHeadsUpTracker.class); - private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class); - private HeadsUpAppearanceController mHeadsUpAppearanceController; - private NotificationTestHelper mTestHelper; - private ExpandableNotificationRow mRow; - private NotificationEntry mEntry; - private HeadsUpStatusBarView mHeadsUpStatusBarView; - private HeadsUpManager mHeadsUpManager; - private View mOperatorNameView; - private StatusBarStateController mStatusbarStateController; - private PhoneStatusBarTransitions mPhoneStatusBarTransitions; - private KeyguardBypassController mBypassController; - private NotificationWakeUpCoordinator mWakeUpCoordinator; - private KeyguardStateController mKeyguardStateController; - private CommandQueue mCommandQueue; - private NotificationRoundnessManager mNotificationRoundnessManager; - - @Before - public void setUp() throws Exception { - allowTestableLooperAsMainThread(); - mTestHelper = new NotificationTestHelper( - mContext, - mDependency, - TestableLooper.get(this)); - mRow = mTestHelper.createRow(); - mEntry = mRow.getEntry(); - mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class), - mock(TextView.class)); - mHeadsUpManager = mock(HeadsUpManager.class); - mOperatorNameView = new View(mContext); - mStatusbarStateController = mock(StatusBarStateController.class); - mPhoneStatusBarTransitions = mock(PhoneStatusBarTransitions.class); - mBypassController = mock(KeyguardBypassController.class); - mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class); - mKeyguardStateController = mock(KeyguardStateController.class); - mCommandQueue = mock(CommandQueue.class); - mNotificationRoundnessManager = mock(NotificationRoundnessManager.class); - when(mShadeViewController.getShadeHeadsUpTracker()).thenReturn(mShadeHeadsUpTracker); - mHeadsUpAppearanceController = new HeadsUpAppearanceController( - mHeadsUpManager, - mStatusbarStateController, - mPhoneStatusBarTransitions, - mBypassController, - mWakeUpCoordinator, - mDarkIconDispatcher, - mKeyguardStateController, - mCommandQueue, - mStackScrollerController, - mShadeViewController, - mNotificationRoundnessManager, - mHeadsUpStatusBarView, - new Clock(mContext, null), - mock(HeadsUpNotificationIconInteractor.class), - Optional.of(mOperatorNameView)); - mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f); - } - - @Test - public void testShowinEntryUpdated() { - mRow.setPinnedStatus(PinnedStatus.PinnedBySystem); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); - when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry); - mHeadsUpAppearanceController.onHeadsUpPinned(mEntry); - assertEquals(mRow.getEntry(), mHeadsUpStatusBarView.getShowingEntry()); - - mRow.setPinnedStatus(PinnedStatus.NotPinned); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry); - assertNull(mHeadsUpStatusBarView.getShowingEntry()); - } - - @Test - public void testPinnedStatusUpdated() { - mRow.setPinnedStatus(PinnedStatus.PinnedBySystem); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); - when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry); - mHeadsUpAppearanceController.onHeadsUpPinned(mEntry); - assertEquals(PinnedStatus.PinnedBySystem, mHeadsUpAppearanceController.getPinnedStatus()); - - mRow.setPinnedStatus(PinnedStatus.NotPinned); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry); - assertEquals(PinnedStatus.NotPinned, mHeadsUpAppearanceController.getPinnedStatus()); - } - - @Test - @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME) - public void testHeaderUpdated() { - mRow.setPinnedStatus(PinnedStatus.PinnedBySystem); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); - when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry); - mHeadsUpAppearanceController.onHeadsUpPinned(mEntry); - assertEquals(mRow.getHeaderVisibleAmount(), 0.0f, 0.0f); - - mRow.setPinnedStatus(PinnedStatus.NotPinned); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry); - assertEquals(mRow.getHeaderVisibleAmount(), 1.0f, 0.0f); - } - - @Test - public void testOperatorNameViewUpdated() { - mHeadsUpAppearanceController.setAnimationsEnabled(false); - - mRow.setPinnedStatus(PinnedStatus.PinnedBySystem); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); - when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry); - mHeadsUpAppearanceController.onHeadsUpPinned(mEntry); - assertEquals(View.INVISIBLE, mOperatorNameView.getVisibility()); - - mRow.setPinnedStatus(PinnedStatus.NotPinned); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry); - assertEquals(View.VISIBLE, mOperatorNameView.getVisibility()); - } - - @Test - public void constructor_animationValuesUpdated() { - float appearFraction = .75f; - float expandedHeight = 400f; - when(mStackScrollerController.getAppearFraction()).thenReturn(appearFraction); - when(mStackScrollerController.getExpandedHeight()).thenReturn(expandedHeight); - - HeadsUpAppearanceController newController = new HeadsUpAppearanceController( - mHeadsUpManager, - mStatusbarStateController, - mPhoneStatusBarTransitions, - mBypassController, - mWakeUpCoordinator, - mDarkIconDispatcher, - mKeyguardStateController, - mCommandQueue, - mStackScrollerController, - mShadeViewController, - mNotificationRoundnessManager, - mHeadsUpStatusBarView, - new Clock(mContext, null), - mock(HeadsUpNotificationIconInteractor.class), - Optional.empty()); - - assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f); - assertEquals(appearFraction, newController.mAppearFraction, 0.0f); - } - - @Test - public void testDestroy() { - reset(mHeadsUpManager); - reset(mDarkIconDispatcher); - reset(mShadeHeadsUpTracker); - reset(mStackScrollerController); - - mHeadsUpAppearanceController.onViewDetached(); - - verify(mHeadsUpManager).removeListener(any()); - verify(mDarkIconDispatcher).removeDarkReceiver(any()); - verify(mShadeHeadsUpTracker).removeTrackingHeadsUpListener(any()); - verify(mShadeHeadsUpTracker).setHeadsUpAppearanceController(isNull()); - verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any()); - } - - @Test - public void testPulsingRoundness_onUpdateHeadsUpAndPulsingRoundness() { - // Pulsing: Enable flag and dozing - when(mNotificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true); - when(mTestHelper.getStatusBarStateController().isDozing()).thenReturn(true); - - // Pulsing: Enabled - mRow.setHeadsUp(true); - mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(mEntry); - - String debugString = mRow.getRoundableState().debugString(); - assertEquals( - "If Pulsing is enabled, roundness should be set to 1. Value: " + debugString, - /* expected = */ 1, - /* actual = */ mRow.getTopRoundness(), - /* delta = */ 0.001 - ); - assertTrue(debugString.contains("Pulsing")); - - // Pulsing: Disabled - mRow.setHeadsUp(false); - mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(mEntry); - - assertEquals( - "If Pulsing is disabled, roundness should be set to 0. Value: " - + mRow.getRoundableState().debugString(), - /* expected = */ 0, - /* actual = */ mRow.getTopRoundness(), - /* delta = */ 0.001 - ); - } - - @Test - public void testPulsingRoundness_onHeadsUpStateChanged() { - // Pulsing: Enable flag and dozing - when(mNotificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true); - when(mTestHelper.getStatusBarStateController().isDozing()).thenReturn(true); - - // Pulsing: Enabled - mEntry.setHeadsUp(true); - mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true); - - String debugString = mRow.getRoundableState().debugString(); - assertEquals( - "If Pulsing is enabled, roundness should be set to 1. Value: " + debugString, - /* expected = */ 1, - /* actual = */ mRow.getTopRoundness(), - /* delta = */ 0.001 - ); - assertTrue(debugString.contains("Pulsing")); - - // Pulsing: Disabled - mEntry.setHeadsUp(false); - mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false); - - assertEquals( - "If Pulsing is disabled, roundness should be set to 0. Value: " - + mRow.getRoundableState().debugString(), - /* expected = */ 0, - /* actual = */ mRow.getTopRoundness(), - /* delta = */ 0.001 - ); - } - - @Test - public void onHeadsUpStateChanged_true_transitionsNotified() { - mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true); - - verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(true); - } - - @Test - public void onHeadsUpStateChanged_false_transitionsNotified() { - mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false); - - verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(false); - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt new file mode 100644 index 000000000000..d017402675bf --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.statusbar.phone + +import android.platform.test.annotations.DisableFlags +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.view.View +import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeHeadsUpTracker +import com.android.systemui.shade.ShadeViewController +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.HeadsUpStatusBarView +import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager +import com.android.systemui.statusbar.notification.headsup.PinnedStatus +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.NotificationTestHelper +import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation +import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.policy.Clock +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper +class HeadsUpAppearanceControllerTest : SysuiTestCase() { + private val mStackScrollerController: NotificationStackScrollLayoutController = + mock<NotificationStackScrollLayoutController>() + private val mShadeViewController: ShadeViewController = mock<ShadeViewController>() + private val mShadeHeadsUpTracker: ShadeHeadsUpTracker = mock<ShadeHeadsUpTracker>() + private val mDarkIconDispatcher: DarkIconDispatcher = mock<DarkIconDispatcher>() + private var mHeadsUpAppearanceController: HeadsUpAppearanceController? = null + private var mTestHelper: NotificationTestHelper? = null + private var mRow: ExpandableNotificationRow? = null + private var mEntry: NotificationEntry? = null + private var mHeadsUpStatusBarView: HeadsUpStatusBarView? = null + private var mHeadsUpManager: HeadsUpManager? = null + private var mOperatorNameView: View? = null + private var mStatusbarStateController: StatusBarStateController? = null + private var mPhoneStatusBarTransitions: PhoneStatusBarTransitions? = null + private var mBypassController: KeyguardBypassController? = null + private var mWakeUpCoordinator: NotificationWakeUpCoordinator? = null + private var mKeyguardStateController: KeyguardStateController? = null + private var mCommandQueue: CommandQueue? = null + private var mNotificationRoundnessManager: NotificationRoundnessManager? = null + + @Before + @Throws(Exception::class) + fun setUp() { + allowTestableLooperAsMainThread() + mTestHelper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + mRow = mTestHelper!!.createRow() + mEntry = mRow!!.entry + mHeadsUpStatusBarView = HeadsUpStatusBarView(mContext, mock<View>(), mock<TextView>()) + mHeadsUpManager = mock<HeadsUpManager>() + mOperatorNameView = View(mContext) + mStatusbarStateController = mock<StatusBarStateController>() + mPhoneStatusBarTransitions = mock<PhoneStatusBarTransitions>() + mBypassController = mock<KeyguardBypassController>() + mWakeUpCoordinator = mock<NotificationWakeUpCoordinator>() + mKeyguardStateController = mock<KeyguardStateController>() + mCommandQueue = mock<CommandQueue>() + mNotificationRoundnessManager = mock<NotificationRoundnessManager>() + whenever(mShadeViewController.shadeHeadsUpTracker).thenReturn(mShadeHeadsUpTracker) + mHeadsUpAppearanceController = + HeadsUpAppearanceController( + mHeadsUpManager, + mStatusbarStateController, + mPhoneStatusBarTransitions, + mBypassController, + mWakeUpCoordinator, + mDarkIconDispatcher, + mKeyguardStateController, + mCommandQueue, + mStackScrollerController, + mShadeViewController, + mNotificationRoundnessManager, + mHeadsUpStatusBarView, + Clock(mContext, null), + mock<HeadsUpNotificationIconInteractor>(), + Optional.of(mOperatorNameView!!), + ) + mHeadsUpAppearanceController!!.setAppearFraction(0.0f, 0.0f) + } + + @Test + fun testShowinEntryUpdated() { + mRow!!.setPinnedStatus(PinnedStatus.PinnedBySystem) + whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(true) + whenever(mHeadsUpManager!!.getTopEntry()).thenReturn(mEntry) + mHeadsUpAppearanceController!!.onHeadsUpPinned(mEntry) + assertThat(mHeadsUpStatusBarView!!.showingEntry).isEqualTo(mRow!!.entry) + + mRow!!.setPinnedStatus(PinnedStatus.NotPinned) + whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(false) + mHeadsUpAppearanceController!!.onHeadsUpUnPinned(mEntry) + assertThat(mHeadsUpStatusBarView!!.showingEntry).isNull() + } + + @Test + fun testPinnedStatusUpdated() { + mRow!!.setPinnedStatus(PinnedStatus.PinnedBySystem) + whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(true) + whenever(mHeadsUpManager!!.getTopEntry()).thenReturn(mEntry) + mHeadsUpAppearanceController!!.onHeadsUpPinned(mEntry) + assertThat(mHeadsUpAppearanceController!!.pinnedStatus) + .isEqualTo(PinnedStatus.PinnedBySystem) + + mRow!!.setPinnedStatus(PinnedStatus.NotPinned) + whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(false) + mHeadsUpAppearanceController!!.onHeadsUpUnPinned(mEntry) + assertThat(mHeadsUpAppearanceController!!.pinnedStatus).isEqualTo(PinnedStatus.NotPinned) + } + + @Test + @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME) + fun testHeaderUpdated() { + mRow!!.setPinnedStatus(PinnedStatus.PinnedBySystem) + whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(true) + whenever(mHeadsUpManager!!.getTopEntry()).thenReturn(mEntry) + mHeadsUpAppearanceController!!.onHeadsUpPinned(mEntry) + assertThat(mRow!!.headerVisibleAmount).isEqualTo(0.0f) + + mRow!!.setPinnedStatus(PinnedStatus.NotPinned) + whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(false) + mHeadsUpAppearanceController!!.onHeadsUpUnPinned(mEntry) + assertThat(mRow!!.headerVisibleAmount).isEqualTo(1.0f) + } + + @Test + fun testOperatorNameViewUpdated() { + mHeadsUpAppearanceController!!.setAnimationsEnabled(false) + + mRow!!.setPinnedStatus(PinnedStatus.PinnedBySystem) + whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(true) + whenever(mHeadsUpManager!!.getTopEntry()).thenReturn(mEntry) + mHeadsUpAppearanceController!!.onHeadsUpPinned(mEntry) + assertThat(mOperatorNameView!!.visibility).isEqualTo(View.INVISIBLE) + + mRow!!.setPinnedStatus(PinnedStatus.NotPinned) + whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(false) + mHeadsUpAppearanceController!!.onHeadsUpUnPinned(mEntry) + assertThat(mOperatorNameView!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun constructor_animationValuesUpdated() { + val appearFraction = .75f + val expandedHeight = 400f + whenever(mStackScrollerController.appearFraction).thenReturn(appearFraction) + whenever(mStackScrollerController.expandedHeight).thenReturn(expandedHeight) + + val newController = + HeadsUpAppearanceController( + mHeadsUpManager, + mStatusbarStateController, + mPhoneStatusBarTransitions, + mBypassController, + mWakeUpCoordinator, + mDarkIconDispatcher, + mKeyguardStateController, + mCommandQueue, + mStackScrollerController, + mShadeViewController, + mNotificationRoundnessManager, + mHeadsUpStatusBarView, + Clock(mContext, null), + mock<HeadsUpNotificationIconInteractor>(), + Optional.empty(), + ) + + assertThat(newController.mExpandedHeight).isEqualTo(expandedHeight) + assertThat(newController.mAppearFraction).isEqualTo(appearFraction) + } + + @Test + fun testDestroy() { + reset(mHeadsUpManager) + reset(mDarkIconDispatcher) + reset(mShadeHeadsUpTracker) + reset(mStackScrollerController) + + mHeadsUpAppearanceController!!.onViewDetached() + + verify(mHeadsUpManager!!).removeListener(any()) + verify(mDarkIconDispatcher).removeDarkReceiver(any()) + verify(mShadeHeadsUpTracker).removeTrackingHeadsUpListener(any()) + verify(mShadeHeadsUpTracker).setHeadsUpAppearanceController(null) + verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any()) + } + + @Test + fun testPulsingRoundness_onUpdateHeadsUpAndPulsingRoundness() { + // Pulsing: Enable flag and dozing + whenever(mNotificationRoundnessManager!!.shouldRoundNotificationPulsing()).thenReturn(true) + whenever(mTestHelper!!.statusBarStateController.isDozing).thenReturn(true) + + // Pulsing: Enabled + mRow!!.isHeadsUp = true + mHeadsUpAppearanceController!!.updateHeadsUpAndPulsingRoundness(mEntry) + + val debugString: String = mRow!!.roundableState.debugString() + // If Pulsing is enabled, roundness should be set to 1 + assertThat(mRow!!.topRoundness.toDouble()).isWithin(0.001).of(1.0) + assertThat(debugString).contains("Pulsing") + + // Pulsing: Disabled + mRow!!.isHeadsUp = false + mHeadsUpAppearanceController!!.updateHeadsUpAndPulsingRoundness(mEntry) + + // If Pulsing is disabled, roundness should be set to 0 + assertThat(mRow!!.topRoundness.toDouble()).isWithin(0.001).of(0.0) + } + + @Test + fun testPulsingRoundness_onHeadsUpStateChanged() { + // Pulsing: Enable flag and dozing + whenever(mNotificationRoundnessManager!!.shouldRoundNotificationPulsing()).thenReturn(true) + whenever(mTestHelper!!.statusBarStateController.isDozing).thenReturn(true) + + // Pulsing: Enabled + mEntry!!.setHeadsUp(true) + mHeadsUpAppearanceController!!.onHeadsUpStateChanged(mEntry!!, true) + + val debugString: String = mRow!!.roundableState.debugString() + // If Pulsing is enabled, roundness should be set to 1 + assertThat(mRow!!.topRoundness.toDouble()).isWithin(0.001).of(1.0) + assertThat(debugString).contains("Pulsing") + + // Pulsing: Disabled + mEntry!!.setHeadsUp(false) + mHeadsUpAppearanceController!!.onHeadsUpStateChanged(mEntry!!, false) + + // If Pulsing is disabled, roundness should be set to 0 + assertThat(mRow!!.topRoundness.toDouble()).isWithin(0.001).of(0.0) + } + + @Test + fun onHeadsUpStateChanged_true_transitionsNotified() { + mHeadsUpAppearanceController!!.onHeadsUpStateChanged(mEntry!!, true) + + verify(mPhoneStatusBarTransitions!!).onHeadsUpStateChanged(true) + } + + @Test + fun onHeadsUpStateChanged_false_transitionsNotified() { + mHeadsUpAppearanceController!!.onHeadsUpStateChanged(mEntry!!, false) + + verify(mPhoneStatusBarTransitions!!).onHeadsUpStateChanged(false) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt index 110dec6c33aa..f76ee5e3ebc9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt @@ -87,7 +87,7 @@ private const val PROC_STATE_INVISIBLE = ActivityManager.PROCESS_STATE_FOREGROUN @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper @OptIn(ExperimentalCoroutinesApi::class) -@DisableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP) +@DisableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP, StatusBarChipsModernization.FLAG_NAME) class OngoingCallControllerViaListenerTest : SysuiTestCase() { private val kosmos = Kosmos() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt index 2ad50cc38b7c..647b5f86fcee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt @@ -29,6 +29,7 @@ import android.widget.LinearLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON +import com.android.systemui.Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP import com.android.systemui.SysuiTestCase @@ -77,6 +78,7 @@ import org.mockito.kotlin.whenever @TestableLooper.RunWithLooper @OptIn(ExperimentalCoroutinesApi::class) @EnableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP) +@DisableFlags(StatusBarChipsModernization.FLAG_NAME) class OngoingCallControllerViaRepoTest : SysuiTestCase() { private val kosmos = Kosmos() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt index 4c6eaa589e6a..a44631348796 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt @@ -16,10 +16,13 @@ package com.android.systemui.statusbar.phone.ongoingcall.data.repository +import android.platform.test.annotations.DisableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel import com.google.common.truth.Truth.assertThat @@ -28,6 +31,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@DisableFlags(StatusBarChipsModernization.FLAG_NAME) class OngoingCallRepositoryTest : SysuiTestCase() { private val kosmos = Kosmos() private val underTest = kosmos.ongoingCallRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt new file mode 100644 index 000000000000..ca1413e48966 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor + +import android.app.PendingIntent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.data.repository.activityManagerRepository +import com.android.systemui.activity.data.repository.fake +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.shared.CallType +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +@SmallTest +@RunWith(AndroidJUnit4::class) +class OngoingCallInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos().useUnconfinedTestDispatcher() + private val repository = kosmos.activeNotificationListRepository + private val underTest = kosmos.ongoingCallInteractor + + @Test + fun noNotification_emitsNoCall() = runTest { + val state by collectLastValue(underTest.ongoingCallState) + assertThat(state).isInstanceOf(OngoingCallModel.NoCall::class.java) + } + + @Test + fun ongoingCallNotification_setsNotificationIconAndIntent() = + kosmos.runTest { + val latest by collectLastValue(underTest.ongoingCallState) + + // Set up notification with icon view and intent + val testIconView: StatusBarIconView = mock() + val testIntent: PendingIntent = mock() + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, + statusBarChipIcon = testIconView, + contentIntent = testIntent + ) + ) + } + .build() + + // Verify model is InCall and has the correct icon and intent. + assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + val model = latest as OngoingCallModel.InCall + assertThat(model.notificationIconView).isSameInstanceAs(testIconView) + assertThat(model.intent).isSameInstanceAs(testIntent) + } + + @Test + fun ongoingCallNotification_emitsInCall() = + kosmos.runTest { + val latest by collectLastValue(underTest.ongoingCallState) + + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", whenTime = 1000L, callType = CallType.Ongoing + ) + ) + } + .build() + + assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + } + + @Test + fun notificationRemoved_emitsNoCall() = + kosmos.runTest { + val latest by collectLastValue(underTest.ongoingCallState) + + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", whenTime = 1000L, callType = CallType.Ongoing + ) + ) + } + .build() + + repository.activeNotifications.value = ActiveNotificationsStore() + assertThat(latest).isInstanceOf(OngoingCallModel.NoCall::class.java) + } + + @Test + fun ongoingCallNotification_appVisibleInitially_emitsInCallWithVisibleApp() = + kosmos.runTest { + kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true + val latest by collectLastValue(underTest.ongoingCallState) + + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, + uid = UID + ) + ) + } + .build() + + assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java) + } + + @Test + fun ongoingCallNotification_appNotVisibleInitially_emitsInCall() = + kosmos.runTest { + kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false + val latest by collectLastValue(underTest.ongoingCallState) + + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, + uid = UID + ) + ) + } + .build() + + assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + } + + @Test + fun ongoingCallNotification_visibilityChanges_updatesState() = + kosmos.runTest { + val latest by collectLastValue(underTest.ongoingCallState) + + // Start with notification and app not visible + kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, + uid = UID + ) + ) + } + .build() + assertThat(latest) + .isInstanceOf(OngoingCallModel.InCall::class.java) + + // App becomes visible + kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true) + assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java) + + // App becomes invisible again + kosmos.activityManagerRepository.fake.setIsAppVisible(UID, false) + assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + } + + companion object { + private const val UID = 885 + } +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index bc81a4b6bb67..e417da4d8815 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3814,6 +3814,9 @@ shortcut helper The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_customize_button_text">Customize</string> + <!-- Description text of the button that allows user to resets all custom shortcuts in keyboard + shortcut helper when in customization mode. [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_reset_button_text">Reset</string> <!-- Description text of the button that allows user to exit shortcut customization mode in keyboard shortcut helper The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json new file mode 100644 index 000000000000..c5a83c4d0d8c --- /dev/null +++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json @@ -0,0 +1,95 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "a83f96ef4babe730b3a00e8acb777a25", + "entities": [ + { + "tableName": "communal_widget_table", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1, `span_y` INTEGER NOT NULL DEFAULT 3, `span_y_new` INTEGER NOT NULL DEFAULT 1)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "widgetId", + "columnName": "widget_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "componentName", + "columnName": "component_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itemId", + "columnName": "item_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userSerialNumber", + "columnName": "user_serial_number", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "spanY", + "columnName": "span_y", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "3" + }, + { + "fieldPath": "spanYNew", + "columnName": "span_y_new", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uid" + ] + } + }, + { + "tableName": "communal_item_rank_table", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "rank", + "columnName": "rank", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uid" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a83f96ef4babe730b3a00e8acb777a25')" + ] + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 071cf8a46b9f..5af80cbd4b29 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -63,6 +63,7 @@ import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData import com.android.systemui.plugins.clocks.ZenData.ZenMode import com.android.systemui.res.R as SysuiR +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.settings.UserTracker import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.statusbar.policy.BatteryController @@ -466,6 +467,15 @@ constructor( batteryController.addCallback(batteryCallback) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) zenModeController.addCallback(zenModeCallback) + if (SceneContainerFlag.isEnabled) { + handleDoze( + when (AOD) { + keyguardTransitionInteractor.getCurrentState() -> 1f + keyguardTransitionInteractor.getStartedState() -> 1f + else -> 0f + } + ) + } disposableHandle = parent.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt index c3d2683ce953..41ea7b6c2202 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt @@ -29,9 +29,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking /** Utilities for communal backup and restore. */ -class CommunalBackupUtils( - private val context: Context, -) { +class CommunalBackupUtils(private val context: Context) { /** * Retrieves a communal hub state protobuf that represents the current state of the communal @@ -50,6 +48,8 @@ class CommunalBackupUtils( widgetId = widget.widgetId componentName = widget.componentName userSerialNumber = widget.userSerialNumber + spanY = widget.spanY + spanYNew = widget.spanYNew } ) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt index e72088f37fa7..679d0714b1b4 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt @@ -26,9 +26,11 @@ import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl +import com.android.systemui.communal.shared.model.SpanValue +import com.android.systemui.communal.shared.model.toResponsive import com.android.systemui.res.R -@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 4) +@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 5) abstract class CommunalDatabase : RoomDatabase() { abstract fun communalWidgetDao(): CommunalWidgetDao @@ -59,7 +61,12 @@ abstract class CommunalDatabase : RoomDatabase() { context.resources.getString(R.string.config_communalDatabase), ) .also { builder -> - builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) + builder.addMigrations( + MIGRATION_1_2, + MIGRATION_2_3, + MIGRATION_3_4, + MIGRATION_4_5, + ) builder.fallbackToDestructiveMigration(dropAllTables = true) callback?.let { callback -> builder.addCallback(callback) } } @@ -123,5 +130,30 @@ abstract class CommunalDatabase : RoomDatabase() { ) } } + + /** This migration adds a new spanY column for responsive grid sizing. */ + @VisibleForTesting + val MIGRATION_4_5 = + object : Migration(4, 5) { + override fun migrate(db: SupportSQLiteDatabase) { + Log.i(TAG, "Migrating from version 4 to 5") + db.execSQL( + "ALTER TABLE communal_widget_table " + + "ADD COLUMN span_y_new INTEGER NOT NULL DEFAULT 1" + ) + db.query("SELECT item_id, span_y FROM communal_widget_table").use { cursor -> + while (cursor.moveToNext()) { + val id = cursor.getInt(cursor.getColumnIndex("item_id")) + val spanYFixed = + SpanValue.Fixed(cursor.getInt(cursor.getColumnIndex("span_y"))) + val spanYResponsive = spanYFixed.toResponsive() + db.execSQL( + "UPDATE communal_widget_table SET span_y_new = " + + "${spanYResponsive.value} WHERE item_id = $id" + ) + } + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt index f9d2a843c213..6ef4bb8e55eb 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt @@ -45,7 +45,12 @@ data class CommunalWidgetItem( * The vertical span of the widget. Span_Y default value corresponds to * CommunalContentSize.HALF.span */ - @ColumnInfo(name = "span_y", defaultValue = "3") val spanY: Int, + @Deprecated("Use spanYNew instead") + @ColumnInfo(name = "span_y", defaultValue = "3") + val spanY: Int, + + /** The vertical span of the widget in grid cell units. */ + @ColumnInfo(name = "span_y_new", defaultValue = "1") val spanYNew: Int, ) { companion object { /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt index 3d40aa75b488..3907a37cd5d9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt @@ -27,7 +27,9 @@ import androidx.room.Update import androidx.sqlite.db.SupportSQLiteDatabase import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.communal.nano.CommunalHubState -import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.shared.model.SpanValue +import com.android.systemui.communal.shared.model.toFixed +import com.android.systemui.communal.shared.model.toResponsive import com.android.systemui.communal.widgets.CommunalWidgetHost import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS import com.android.systemui.dagger.SysUISingleton @@ -101,6 +103,7 @@ constructor( componentName = name, rank = index, userSerialNumber = userSerialNumber, + spanY = SpanValue.Fixed(3), ) } } @@ -155,15 +158,16 @@ interface CommunalWidgetDao { @Query( "INSERT INTO communal_widget_table" + - "(widget_id, component_name, item_id, user_serial_number, span_y) " + - "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber, :spanY)" + "(widget_id, component_name, item_id, user_serial_number, span_y, span_y_new) " + + "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber, :spanY, :spanYNew)" ) fun insertWidget( widgetId: Int, componentName: String, itemId: Long, userSerialNumber: Int, - spanY: Int = 3, + spanY: Int, + spanYNew: Int, ): Long @Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)") @@ -189,10 +193,12 @@ interface CommunalWidgetDao { } @Transaction - fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) { + fun resizeWidget(appWidgetId: Int, spanY: SpanValue, widgetIdToRankMap: Map<Int, Int>) { val widget = getWidgetByIdNow(appWidgetId) if (widget != null) { - updateWidget(widget.copy(spanY = spanY)) + updateWidget( + widget.copy(spanY = spanY.toFixed().value, spanYNew = spanY.toResponsive().value) + ) } updateWidgetOrder(widgetIdToRankMap) } @@ -203,7 +209,7 @@ interface CommunalWidgetDao { provider: ComponentName, rank: Int? = null, userSerialNumber: Int, - spanY: Int = CommunalContentSize.HALF.span, + spanY: SpanValue, ): Long { return addWidget( widgetId = widgetId, @@ -220,7 +226,7 @@ interface CommunalWidgetDao { componentName: String, rank: Int? = null, userSerialNumber: Int, - spanY: Int = 3, + spanY: SpanValue, ): Long { val widgets = getWidgetsNow() @@ -241,7 +247,8 @@ interface CommunalWidgetDao { componentName = componentName, itemId = insertItemRank(newRank), userSerialNumber = userSerialNumber, - spanY = spanY, + spanY = spanY.toFixed().value, + spanYNew = spanY.toResponsive().value, ) } @@ -264,7 +271,11 @@ interface CommunalWidgetDao { clearCommunalItemRankTable() state.widgets.forEach { - val spanY = if (it.spanY != 0) it.spanY else CommunalContentSize.HALF.span + // Check if there is a new value to restore. If so, restore that new value. + val spanYResponsive = if (it.spanYNew != 0) SpanValue.Responsive(it.spanYNew) else null + // If no new value, restore any existing old values. + val spanY = spanYResponsive ?: SpanValue.Fixed(it.spanY.coerceIn(3, 6)) + addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber, spanY) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index 29569f8b7df5..e44d78baeb35 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -22,6 +22,7 @@ import android.content.ComponentName import android.os.UserHandle import android.os.UserManager import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.systemui.Flags.communalResponsiveGrid import com.android.systemui.Flags.communalWidgetResizing import com.android.systemui.common.data.repository.PackageChangeRepository import com.android.systemui.common.shared.model.PackageInstallSession @@ -33,6 +34,7 @@ import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason. import com.android.systemui.communal.nano.CommunalHubState import com.android.systemui.communal.proto.toCommunalHubState import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import com.android.systemui.communal.shared.model.SpanValue import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.CommunalWidgetHost import com.android.systemui.communal.widgets.WidgetConfigurator @@ -143,15 +145,21 @@ constructor( componentName = widget.componentName, rank = rank.rank, providerInfo = providers[widget.widgetId], - spanY = widget.spanY, + spanY = if (communalResponsiveGrid()) widget.spanYNew else widget.spanY, ) } } override fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) { if (!communalWidgetResizing()) return + val spanValue = + if (communalResponsiveGrid()) { + SpanValue.Responsive(spanY) + } else { + SpanValue.Fixed(spanY) + } bgScope.launch { - communalWidgetDao.resizeWidget(appWidgetId, spanY, widgetIdToRankMap) + communalWidgetDao.resizeWidget(appWidgetId, spanValue, widgetIdToRankMap) logger.i({ "Updated spanY of widget $int1 to $int2." }) { int1 = appWidgetId int2 = spanY @@ -225,7 +233,7 @@ constructor( provider = provider, rank = rank, userSerialNumber = userManager.getUserSerialNumber(user.identifier), - spanY = 3, + spanY = SpanValue.Fixed(3), ) backupManager.dataChanged() } else { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 602fe307a1fe..f9b30c6c2ba1 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -34,9 +34,9 @@ import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent import com.android.systemui.communal.shared.model.CommunalContentSize -import com.android.systemui.communal.shared.model.CommunalContentSize.FULL -import com.android.systemui.communal.shared.model.CommunalContentSize.HALF -import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD +import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL +import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.HALF +import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.THIRD import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.shared.model.EditModeState diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt index 30f580e472e8..da613f58dce8 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt @@ -22,6 +22,7 @@ import android.content.ComponentName import android.content.pm.ApplicationInfo import android.graphics.Bitmap import android.widget.RemoteViews +import com.android.systemui.Flags.communalResponsiveGrid import com.android.systemui.communal.shared.model.CommunalContentSize import java.util.UUID @@ -35,7 +36,7 @@ sealed interface CommunalContentModel { /** The minimum size content can be resized to. */ val minSize: CommunalContentSize - get() = CommunalContentSize.HALF + get() = fixedHalfOrResponsiveSize() /** * A type of communal content is ongoing / live / ephemeral, and can be sized and ordered @@ -44,7 +45,12 @@ sealed interface CommunalContentModel { sealed interface Ongoing : CommunalContentModel { override var size: CommunalContentSize override val minSize - get() = CommunalContentSize.THIRD + get() = + if (communalResponsiveGrid()) { + CommunalContentSize.Responsive(1) + } else { + CommunalContentSize.FixedSize.THIRD + } /** Timestamp in milliseconds of when the content was created. */ val createdTimestampMillis: Long @@ -100,14 +106,16 @@ sealed interface CommunalContentModel { class WidgetPlaceholder : CommunalContentModel { override val key: String = KEY.widgetPlaceholder() // Same as widget size. - override val size = CommunalContentSize.HALF + override val size: CommunalContentSize + get() = fixedHalfOrResponsiveSize() } /** A CTA tile in the glanceable hub view mode which can be dismissed. */ class CtaTileInViewMode : CommunalContentModel { override val key: String = KEY.CTA_TILE_IN_VIEW_MODE_KEY // Same as widget size. - override val size = CommunalContentSize.HALF + override val size: CommunalContentSize + get() = fixedHalfOrResponsiveSize() } class Tutorial(id: Int, override var size: CommunalContentSize) : CommunalContentModel { @@ -118,15 +126,15 @@ sealed interface CommunalContentModel { smartspaceTargetId: String, val remoteViews: RemoteViews, override val createdTimestampMillis: Long, - override var size: CommunalContentSize = CommunalContentSize.HALF, + override var size: CommunalContentSize = fixedHalfOrResponsiveSize(), ) : Ongoing { override val key = KEY.smartspace(smartspaceTargetId) } class Umo( override val createdTimestampMillis: Long, - override var size: CommunalContentSize = CommunalContentSize.HALF, - override var minSize: CommunalContentSize = CommunalContentSize.HALF, + override var size: CommunalContentSize = fixedHalfOrResponsiveSize(), + override var minSize: CommunalContentSize = fixedHalfOrResponsiveSize(), ) : Ongoing { override val key = KEY.umo() } @@ -170,3 +178,10 @@ sealed interface CommunalContentModel { fun isLiveContent() = this is Smartspace || this is Umo } + +private fun fixedHalfOrResponsiveSize() = + if (communalResponsiveGrid()) { + CommunalContentSize.Responsive(1) + } else { + CommunalContentSize.FixedSize.HALF + } diff --git a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto index 7602a7afce4e..04717d0c864b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto +++ b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto @@ -39,7 +39,10 @@ message CommunalHubState { // Serial number of the user associated with the widget. int32 user_serial_number = 4; - // The vertical span of the widget + // The vertical span of the widget, replaced by span_y_new. int32 span_y = 5; + + // The vertical span of the widget. + int32 span_y_new = 6; } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt index cf80b7d62fd5..df30716ebaf2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt @@ -16,27 +16,39 @@ package com.android.systemui.communal.shared.model +import com.android.systemui.Flags.communalResponsiveGrid + /** * Supported sizes for communal content in the layout grid. * - * @param span The span of the content in a column. For example, if FULL is 6, then 3 represents - * HALF, 2 represents THIRD, and 1 represents SIXTH. + * @property span The span of the content in a column. */ -enum class CommunalContentSize(val span: Int) { - /** Content takes the full height of the column. */ - FULL(6), +sealed interface CommunalContentSize { + val span: Int + + @Deprecated("Use Responsive size instead") + enum class FixedSize(override val span: Int) : CommunalContentSize { + /** Content takes the full height of the column. */ + FULL(6), - /** Content takes half of the height of the column. */ - HALF(3), + /** Content takes half of the height of the column. */ + HALF(3), + + /** Content takes a third of the height of the column. */ + THIRD(2), + } - /** Content takes a third of the height of the column. */ - THIRD(2); + @JvmInline value class Responsive(override val span: Int) : CommunalContentSize companion object { /** Converts from span to communal content size. */ fun toSize(span: Int): CommunalContentSize { - return entries.find { it.span == span } - ?: throw IllegalArgumentException("$span is not a valid span size") + return if (communalResponsiveGrid()) { + Responsive(span) + } else { + FixedSize.entries.find { it.span == span } + ?: throw IllegalArgumentException("$span is not a valid span size") + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.kt new file mode 100644 index 000000000000..15cc6b0ad2c8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.shared.model + +/** Models possible span values for different grid formats. */ +sealed interface SpanValue { + val value: Int + + @Deprecated("Use Responsive sizes instead") + @JvmInline + value class Fixed(override val value: Int) : SpanValue + + @JvmInline value class Responsive(override val value: Int) : SpanValue +} + +fun SpanValue.toResponsive(): SpanValue.Responsive = + when (this) { + is SpanValue.Responsive -> this + is SpanValue.Fixed -> SpanValue.Responsive((this.value / 3).coerceAtMost(1)) + } + +fun SpanValue.toFixed(): SpanValue.Fixed = + when (this) { + is SpanValue.Fixed -> this + is SpanValue.Responsive -> SpanValue.Fixed((this.value * 3).coerceIn(3, 6)) + } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt index 14016783a478..37433ca4faf3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt @@ -16,7 +16,9 @@ package com.android.systemui.keyboard.shortcut.shared.model -data class ShortcutSubCategory(val label: String, val shortcuts: List<Shortcut>) +data class ShortcutSubCategory(val label: String, val shortcuts: List<Shortcut>) { + val containsCustomShortcuts: Boolean = shortcuts.any { it.containsCustomShortcutCommands } +} class ShortcutSubCategoryBuilder(val label: String) { private val shortcuts = mutableListOf<Shortcut>() diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt index bd0430bf96c3..274fa59045d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt @@ -53,15 +53,18 @@ constructor( override suspend fun onActivated(): Nothing { viewModel.shortcutCustomizationUiState.collect { uiState -> - val shouldShowAddDialog = uiState is AddShortcutDialog && !uiState.isDialogShowing - val shouldShowDeleteDialog = uiState is DeleteShortcutDialog && !uiState.isDialogShowing - val shouldShowResetDialog = uiState is ResetShortcutDialog && !uiState.isDialogShowing - if (shouldShowDeleteDialog || shouldShowAddDialog || shouldShowResetDialog) { - dialog = createDialog().also { it.show() } - viewModel.onDialogShown() - } else if (uiState is ShortcutCustomizationUiState.Inactive) { - dialog?.dismiss() - dialog = null + when(uiState){ + is AddShortcutDialog, + is DeleteShortcutDialog, + is ResetShortcutDialog -> { + if (dialog == null){ + dialog = createDialog().also { it.show() } + } + } + is ShortcutCustomizationUiState.Inactive -> { + dialog?.dismiss() + dialog = null + } } } awaitCancellation() diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt index 79293077bc4c..2fdcf8767ae5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyboard.shortcut.ui.composable import android.graphics.drawable.Icon import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -56,6 +57,7 @@ import androidx.compose.material.icons.automirrored.filled.OpenInNew import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.DeleteOutline import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Tune import androidx.compose.material3.CenterAlignedTopAppBar @@ -113,7 +115,6 @@ import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachIndexed import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.painter.rememberDrawablePainter -import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo @@ -125,6 +126,7 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState import com.android.systemui.res.R import kotlinx.coroutines.delay +import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel @Composable fun ShortcutHelper( @@ -187,6 +189,7 @@ private fun ActiveShortcutHelper( onKeyboardSettingsClicked, shortcutsUiState.isShortcutCustomizerFlagEnabled, onCustomizationRequested, + shortcutsUiState.shouldShowResetButton ) } } @@ -377,6 +380,7 @@ private fun ShortcutHelperTwoPane( onKeyboardSettingsClicked: () -> Unit, isShortcutCustomizerFlagEnabled: Boolean, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, + shouldShowResetButton: Boolean ) { val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType } var isCustomizing by remember { mutableStateOf(false) } @@ -389,11 +393,14 @@ private fun ShortcutHelperTwoPane( TitleBar(isCustomizing) } if (isShortcutCustomizerFlagEnabled) { - if (isCustomizing) { - DoneButton(onClick = { isCustomizing = false }) - } else { - CustomizeButton(onClick = { isCustomizing = true }) - } + CustomizationButtonsContainer( + isCustomizing = isCustomizing, + onToggleCustomizationMode = { isCustomizing = !isCustomizing }, + onReset = { + onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset) + }, + shouldShowResetButton = shouldShowResetButton, + ) } else { Spacer(modifier = Modifier.width(if (isCustomizing) 69.dp else 133.dp)) } @@ -422,6 +429,38 @@ private fun ShortcutHelperTwoPane( } @Composable +private fun CustomizationButtonsContainer( + isCustomizing: Boolean, + shouldShowResetButton: Boolean, + onToggleCustomizationMode: () -> Unit, + onReset: () -> Unit, +) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + if (isCustomizing) { + if (shouldShowResetButton) { + ResetButton(onClick = onReset) + } + DoneButton(onClick = onToggleCustomizationMode) + } else { + CustomizeButton(onClick = onToggleCustomizationMode) + } + } +} + +@Composable +private fun ResetButton(onClick: () -> Unit) { + ShortcutHelperButton( + onClick = onClick, + color = Color.Transparent, + width = 99.dp, + iconSource = IconSource(imageVector = Icons.Default.Refresh), + text = stringResource(id = R.string.shortcut_helper_reset_button_text), + contentColor = MaterialTheme.colorScheme.primary, + border = BorderStroke(color = MaterialTheme.colorScheme.outlineVariant, width = 1.dp), + ) +} + +@Composable private fun CustomizeButton(onClick: () -> Unit) { ShortcutHelperButton( onClick = onClick, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt index 58ce194694df..55c0fe297bcb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt @@ -228,74 +228,8 @@ fun ShortcutHelperButton( contentColor: Color, contentPaddingHorizontal: Dp = 16.dp, contentPaddingVertical: Dp = 10.dp, -) { - ClickableShortcutSurface( - onClick = onClick, - shape = shape, - color = color, - modifier = modifier.semantics { role = Role.Button }.width(width).height(height), - interactionsConfig = - InteractionsConfig( - hoverOverlayColor = MaterialTheme.colorScheme.onSurface, - hoverOverlayAlpha = 0.11f, - pressedOverlayColor = MaterialTheme.colorScheme.onSurface, - pressedOverlayAlpha = 0.15f, - focusOutlineColor = MaterialTheme.colorScheme.secondary, - focusOutlineStrokeWidth = 3.dp, - focusOutlinePadding = 2.dp, - surfaceCornerRadius = 28.dp, - focusOutlineCornerRadius = 33.dp, - ), - ) { - Row( - modifier = - Modifier.padding( - horizontal = contentPaddingHorizontal, - vertical = contentPaddingVertical, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - if (iconSource.imageVector != null) { - Icon( - tint = contentColor, - imageVector = iconSource.imageVector, - contentDescription = null, - modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center), - ) - } - - if (iconSource.imageVector != null && text != null) { - Spacer(modifier = Modifier.weight(1f)) - } - - if (text != null) { - Text( - text, - color = contentColor, - fontSize = 14.sp, - style = MaterialTheme.typography.labelLarge, - modifier = Modifier.wrapContentSize(Alignment.Center), - ) - } - } - } -} - -@Composable -fun ShortcutHelperButton( - modifier: Modifier = Modifier, - onClick: () -> Unit, - shape: Shape = RoundedCornerShape(360.dp), - color: Color, - width: Dp, - height: Dp = 40.dp, - iconSource: IconSource = IconSource(), - text: String? = null, - contentColor: Color, - contentPaddingHorizontal: Dp = 16.dp, - contentPaddingVertical: Dp = 10.dp, enabled: Boolean = true, + border: BorderStroke? = null, ) { ShortcutHelperButtonSurface( onClick = onClick, @@ -305,6 +239,7 @@ fun ShortcutHelperButton( enabled = enabled, width = width, height = height, + border = border, ) { Row( modifier = @@ -342,7 +277,7 @@ fun ShortcutHelperButton( } @Composable -fun ShortcutHelperButtonSurface( +private fun ShortcutHelperButtonSurface( onClick: () -> Unit, shape: Shape, color: Color, @@ -350,6 +285,7 @@ fun ShortcutHelperButtonSurface( enabled: Boolean, width: Dp, height: Dp, + border: BorderStroke?, content: @Composable () -> Unit, ) { if (enabled) { @@ -357,6 +293,7 @@ fun ShortcutHelperButtonSurface( onClick = onClick, shape = shape, color = color, + border = border, modifier = modifier.semantics { role = Role.Button }.width(width).height(height), interactionsConfig = InteractionsConfig( diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt index f5d478b5855d..27287721b333 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt @@ -31,4 +31,6 @@ data class ShortcutCategoryUi( iconSource: IconSource, shortcutCategory: ShortcutCategory, ) : this(label, iconSource, shortcutCategory.type, shortcutCategory.subCategories) + + val containsCustomShortcuts: Boolean = subCategories.any { it.containsCustomShortcuts } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt index bfc9486552a5..36c5ae0717be 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt @@ -23,17 +23,12 @@ sealed interface ShortcutCustomizationUiState { val shortcutLabel: String, val errorMessage: String = "", val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon, - val isDialogShowing: Boolean = false, val pressedKeys: List<ShortcutKey> = emptyList(), ) : ShortcutCustomizationUiState - data class DeleteShortcutDialog( - val isDialogShowing: Boolean = false - ) : ShortcutCustomizationUiState + data object DeleteShortcutDialog : ShortcutCustomizationUiState - data class ResetShortcutDialog( - val isDialogShowing: Boolean = false - ) : ShortcutCustomizationUiState + data object ResetShortcutDialog : ShortcutCustomizationUiState data object Inactive : ShortcutCustomizationUiState } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt index 02b0b43ea979..52ab157a0e70 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt @@ -25,6 +25,7 @@ sealed interface ShortcutsUiState { val shortcutCategories: List<ShortcutCategoryUi>, val defaultSelectedCategory: ShortcutCategoryType?, val isShortcutCustomizerFlagEnabled: Boolean = false, + val shouldShowResetButton: Boolean = false, ) : ShortcutsUiState data object Inactive : ShortcutsUiState diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt index 76a2e6095f01..92e25929fe4f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt @@ -73,32 +73,22 @@ constructor( shortcutLabel = requestInfo.label, defaultCustomShortcutModifierKey = shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(), - isDialogShowing = false, pressedKeys = emptyList(), ) shortcutCustomizationInteractor.onCustomizationRequested(requestInfo) } is ShortcutCustomizationRequestInfo.Delete -> { - _shortcutCustomizationUiState.value = DeleteShortcutDialog(isDialogShowing = false) + _shortcutCustomizationUiState.value = DeleteShortcutDialog shortcutCustomizationInteractor.onCustomizationRequested(requestInfo) } ShortcutCustomizationRequestInfo.Reset -> { - _shortcutCustomizationUiState.value = ResetShortcutDialog(isDialogShowing = false) + _shortcutCustomizationUiState.value = ResetShortcutDialog } } } - fun onDialogShown() { - _shortcutCustomizationUiState.update { uiState -> - (uiState as? AddShortcutDialog)?.copy(isDialogShowing = true) - ?: (uiState as? DeleteShortcutDialog)?.copy(isDialogShowing = true) - ?: (uiState as? ResetShortcutDialog)?.copy(isDialogShowing = true) - ?: uiState - } - } - fun onDialogDismissed() { _shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive shortcutCustomizationInteractor.onCustomizationRequested(null) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt index 08fd0af81006..0f5f07393114 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel import android.app.role.RoleManager +import android.content.Context import android.content.pm.PackageManager.NameNotFoundException import android.util.Log import androidx.compose.material.icons.Icons @@ -40,7 +41,6 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState import com.android.systemui.res.R import com.android.systemui.settings.UserTracker -import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -51,10 +51,12 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext +import javax.inject.Inject class ShortcutHelperViewModel @Inject constructor( + private val context: Context, private val roleManager: RoleManager, private val userTracker: UserTracker, @Background private val backgroundScope: CoroutineScope, @@ -88,6 +90,7 @@ constructor( shortcutCategories = shortcutCategoriesUi, defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories), isShortcutCustomizerFlagEnabled = keyboardShortcutHelperShortcutCustomizer(), + shouldShowResetButton = shouldShowResetButton(shortcutCategoriesUi) ) } } @@ -97,6 +100,10 @@ constructor( initialValue = ShortcutsUiState.Inactive, ) + private fun shouldShowResetButton(categoriesUi: List<ShortcutCategoryUi>): Boolean { + return categoriesUi.any { it.containsCustomShortcuts } + } + private fun convertCategoriesModelToUiModel( categories: List<ShortcutCategory> ): List<ShortcutCategoryUi> { @@ -136,13 +143,13 @@ constructor( private fun getShortcutCategoryLabel(type: ShortcutCategoryType): String = when (type) { ShortcutCategoryType.System -> - userContext.getString(R.string.shortcut_helper_category_system) + context.getString(R.string.shortcut_helper_category_system) ShortcutCategoryType.MultiTasking -> - userContext.getString(R.string.shortcut_helper_category_multitasking) + context.getString(R.string.shortcut_helper_category_multitasking) ShortcutCategoryType.InputMethodEditor -> - userContext.getString(R.string.shortcut_helper_category_input) + context.getString(R.string.shortcut_helper_category_input) ShortcutCategoryType.AppCategories -> - userContext.getString(R.string.shortcut_helper_category_app_shortcuts) + context.getString(R.string.shortcut_helper_category_app_shortcuts) is CurrentApp -> getApplicationLabelForCurrentApp(type) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt index 1def7b393435..636f703ac65a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt @@ -19,7 +19,6 @@ package com.android.systemui.qs.panels.data.repository import android.content.res.Resources import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.util.kotlin.emitOnStart @@ -30,7 +29,7 @@ import kotlinx.coroutines.flow.map class QuickQuickSettingsRowRepository @Inject constructor( - @Main private val resources: Resources, + @ShadeDisplayAware private val resources: Resources, @ShadeDisplayAware configurationRepository: ConfigurationRepository, ) { val rows = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt index 75debb6c4032..9efdd98df4cb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -9,6 +9,7 @@ import android.provider.AlarmClock import android.service.quicksettings.Tile import android.text.TextUtils import android.text.format.DateFormat +import android.widget.Button import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger @@ -70,7 +71,10 @@ constructor( } override fun newTileState(): QSTile.State { - return QSTile.State().apply { handlesLongClick = false } + return QSTile.State().apply { + handlesLongClick = false + expandedAccessibilityClassName = Button::class.java.name + } } override fun handleClick(expandable: Expandable?) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 404ace18d0f9..7213f7a60da0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Handler import android.os.Looper import android.service.quicksettings.Tile +import android.widget.Button import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.animation.Expandable @@ -163,6 +164,7 @@ constructor( } else { state.state = Tile.STATE_UNAVAILABLE } + state.expandedAccessibilityClassName = Button::class.java.name } override fun getMetricsCategory(): Int { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java index 374bcda5f46a..e37ed16133e5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java @@ -32,6 +32,7 @@ import android.service.dreams.IDreamManager; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; +import android.widget.Switch; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -176,6 +177,7 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { } else { state.state = isDreaming() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } + state.expandedAccessibilityClassName = Switch.class.getName(); } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java index 48b39dcf29ba..74563fff8775 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java @@ -24,6 +24,7 @@ import android.os.Looper; import android.os.UserManager; import android.provider.Settings; import android.service.quicksettings.Tile; +import android.widget.Button; import androidx.annotation.Nullable; @@ -124,6 +125,7 @@ public class HearingDevicesTile extends QSTileImpl<BooleanState> { state.state = Tile.STATE_INACTIVE; state.secondaryLabel = ""; } + state.expandedAccessibilityClassName = Button.class.getName(); } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index e9c5f4ae9b10..0a5952997893 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -30,7 +30,7 @@ import android.service.quicksettings.Tile; import android.text.Html; import android.text.TextUtils; import android.util.Log; -import android.widget.Switch; +import android.widget.Button; import androidx.annotation.Nullable; @@ -136,7 +136,7 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> { } @Override - public void secondaryClick(@Nullable Expandable expandable) { + public void handleSecondaryClick(@Nullable Expandable expandable) { // TODO(b/358352265): Figure out the correct action for the secondary click // Toggle Wifi mWifiStateWorker.setWifiEnabled(!mWifiStateWorker.isWifiEnabled()); @@ -577,7 +577,7 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> { state.contentDescription = minimalContentDescription.toString(); state.dualLabelContentDescription = r.getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); - state.expandedAccessibilityClassName = Switch.class.getName(); + state.expandedAccessibilityClassName = Button.class.getName(); if (DEBUG) { Log.d(TAG, "handleUpdateWifiState: " + "BooleanState = " + state.toString()); } @@ -594,7 +594,7 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> { boolean mobileDataEnabled = mDataController.isMobileDataSupported() && mDataController.isMobileDataEnabled(); state.value = mobileDataEnabled; - state.expandedAccessibilityClassName = Switch.class.getName(); + state.expandedAccessibilityClassName = Button.class.getName(); if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) { state.state = Tile.STATE_INACTIVE; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt index 7225800e25e3..6d3e5d07c251 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt @@ -20,7 +20,7 @@ import android.content.Intent import android.os.Handler import android.os.Looper import android.provider.Settings -import android.widget.Switch +import android.widget.Button import com.android.internal.logging.MetricsLogger import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Background @@ -71,7 +71,7 @@ constructor( metricsLogger, statusBarStateController, activityStarter, - qsLogger + qsLogger, ) { private var model: InternetTileModel = viewModel.tileModel.value @@ -110,7 +110,7 @@ constructor( return InternetDetailsViewModel { longClick(null) } } - override fun secondaryClick(expandable: Expandable?) { + override fun handleSecondaryClick(expandable: Expandable?) { // TODO(b/358352265): Figure out the correct action for the secondary click // Toggle wifi wifiStateWorker.isWifiEnabled = !wifiStateWorker.isWifiEnabled @@ -118,7 +118,7 @@ constructor( override fun handleUpdateState(state: QSTile.BooleanState, arg: Any?) { state.label = mContext.resources.getString(R.string.quick_settings_internet_label) - state.expandedAccessibilityClassName = Switch::class.java.name + state.expandedAccessibilityClassName = Button::class.java.name model.applyTo(state, mContext) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java index 93a51cfacf02..467233d5f71f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java @@ -21,6 +21,7 @@ import android.os.Handler; import android.os.Looper; import android.service.quicksettings.Tile; import android.util.Log; +import android.widget.Button; import androidx.annotation.Nullable; @@ -126,6 +127,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { // would go to "Unavailable" state only when GMS core is updating. state.secondaryLabel = state.state == Tile.STATE_UNAVAILABLE ? mContext.getString(R.string.qr_code_scanner_updating_secondary_label) : null; + state.expandedAccessibilityClassName = Button.class.getName(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 3d039e6ef824..6deb19257591 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -40,6 +40,7 @@ import android.service.quickaccesswallet.QuickAccessWalletClient; import android.service.quickaccesswallet.WalletCard; import android.service.quicksettings.Tile; import android.util.Log; +import android.widget.Button; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -142,8 +143,16 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE); mUiHandler.post( - () -> mController.startQuickAccessUiIntent( - mActivityStarter, animationController, mSelectedCard != null)); + () -> { + if (android.service.quickaccesswallet.Flags.launchSelectedCardFromQsTile() + && mSelectedCard != null) { + mController.startWalletCardPendingIntent( + mSelectedCard, mActivityStarter, animationController); + } else { + mController.startQuickAccessUiIntent( + mActivityStarter, animationController, mSelectedCard != null); + } + }); } @Override @@ -178,6 +187,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { state.secondaryLabel = null; state.sideViewCustomDrawable = null; } + state.expandedAccessibilityClassName = Button.class.getName(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt index 87f542e6ab39..aeb6cef162b5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.base.viewmodel import android.os.UserHandle +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Dumpable import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics @@ -58,11 +59,8 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest -import com.android.app.tracing.coroutines.launchTraced as launch -import kotlinx.coroutines.withContext /** * Provides a hassle-free way to implement new tiles according to current System UI architecture @@ -90,36 +88,35 @@ class QSTileViewModelImpl<DATA_TYPE>( private val users: MutableStateFlow<UserHandle> = MutableStateFlow(userRepository.getSelectedUserInfo().userHandle) + private val userInputs: MutableSharedFlow<QSTileUserAction> = MutableSharedFlow() + private val forceUpdates: MutableSharedFlow<Unit> = MutableSharedFlow() private val spec get() = config.tileSpec - private val tileData: SharedFlow<DATA_TYPE> = createTileDataFlow() + private val tileData: SharedFlow<DATA_TYPE?> = createTileDataFlow() override val state: StateFlow<QSTileState?> = tileData .map { data -> - withContext(uiBackgroundDispatcher) { mapper().map(config, data) } - .also { state -> qsTileLogger.logStateUpdate(spec, state, data) } + data?.let { + mapper().map(config, it).also { state -> + qsTileLogger.logStateUpdate(spec, state, it) + } + } } - .stateIn( - tileScope, - SharingStarted.WhileSubscribed(), - null, - ) + .flowOn(uiBackgroundDispatcher) + .stateIn(tileScope, SharingStarted.WhileSubscribed(), null) + override val isAvailable: StateFlow<Boolean> = users .flatMapLatest { tileDataInteractor().availability(it) } .flowOn(backgroundDispatcher) - .stateIn( - tileScope, - SharingStarted.WhileSubscribed(), - true, - ) + .stateIn(tileScope, SharingStarted.WhileSubscribed(), true) override fun forceUpdate() { - tileScope.launch { forceUpdates.emit(Unit) } + tileScope.launch(context = backgroundDispatcher) { forceUpdates.emit(Unit) } } override fun onUserChanged(user: UserHandle) { @@ -131,9 +128,9 @@ class QSTileViewModelImpl<DATA_TYPE>( userAction, spec, tileData.replayCache.isNotEmpty(), - state.replayCache.isNotEmpty() + state.replayCache.isNotEmpty(), ) - tileScope.launch { userInputs.emit(userAction) } + tileScope.launch(context = backgroundDispatcher) { userInputs.emit(userAction) } } override fun destroy() { @@ -147,7 +144,7 @@ class QSTileViewModelImpl<DATA_TYPE>( println(state.replayCache.lastOrNull().toString()) } - private fun createTileDataFlow(): SharedFlow<DATA_TYPE> = + private fun createTileDataFlow(): SharedFlow<DATA_TYPE?> = users .transformLatest { user -> coroutineScope { @@ -159,6 +156,7 @@ class QSTileViewModelImpl<DATA_TYPE>( .onEach { qsTileLogger.logForceUpdate(spec) }, ) .onStart { qsTileLogger.logInitialRequest(spec) } + .flowOn(backgroundDispatcher) .stateIn(this, SharingStarted.Eagerly, DataUpdateTrigger.InitialRequest) tileDataInteractor() .tileData(user, updateTriggers) @@ -171,11 +169,8 @@ class QSTileViewModelImpl<DATA_TYPE>( } } .distinctUntilChanged() - .shareIn( - tileScope, - SharingStarted.WhileSubscribed(), - replay = 1, // we only care about the most recent value - ) + .flowOn(backgroundDispatcher) + .stateIn(tileScope, SharingStarted.WhileSubscribed(), null) /** * Creates a user input flow which: diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt index 8f870d468997..4806c3f83224 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.tiles.impl.custom.domain.interactor import android.os.UserHandle import android.service.quicksettings.Tile -import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor @@ -28,6 +28,7 @@ import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePacka import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileScope import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -44,7 +45,6 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn -import com.android.app.tracing.coroutines.launchTraced as launch @QSTileScope @OptIn(ExperimentalCoroutinesApi::class) @@ -64,7 +64,7 @@ constructor( private val bindingFlow = mutableUserFlow .flatMapLatest { user -> - ConflatedCallbackFlow.conflatedCallbackFlow { + conflatedCallbackFlow { serviceInteractor.setUser(user) // Wait for the CustomTileInteractor to become initialized first, because @@ -79,7 +79,7 @@ constructor( defaultsRepository.requestNewDefaults( user, tileSpec.componentName, - true + true, ) } .launchIn(this) @@ -99,7 +99,7 @@ constructor( override fun tileData( user: UserHandle, - triggers: Flow<DataUpdateTrigger> + triggers: Flow<DataUpdateTrigger>, ): Flow<CustomTileDataModel> { tileScope.launch { mutableUserFlow.emit(user) } return bindingFlow.combine(triggers) { _, _ -> }.flatMapLatest { dataFlow(user) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index ab3862b75ee8..f9a1ad5d8424 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -25,6 +25,7 @@ import com.android.systemui.Dumpable import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.QSHost import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon @@ -35,14 +36,17 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.io.PrintWriter import java.util.concurrent.CopyOnWriteArraySet +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectIndexed import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.launch // TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout class QSTileViewModelAdapter @@ -51,6 +55,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val qsHost: QSHost, @Assisted private val qsTileViewModel: QSTileViewModel, + @UiBackground private val uiBgDispatcher: CoroutineDispatcher, ) : QSTile, Dumpable { private val context @@ -162,19 +167,25 @@ constructor( override fun setListening(client: Any?, listening: Boolean) { client ?: return if (listening) { - val clientWasNotAlreadyListening = listeningClients.add(client) - if (clientWasNotAlreadyListening && listeningClients.size == 1) { - stateJob = - qsTileViewModel.state - .filterNotNull() - .map { mapState(context, it, qsTileViewModel.config) } - .onEach { legacyState -> - val changed = legacyState.copyTo(cachedState) - if (changed) { - callbacks.forEach { it.onStateChanged(legacyState) } + applicationScope.launch(uiBgDispatcher) { + val shouldStartMappingJob = + listeningClients.add(client) // new client + && listeningClients.size == 1 // first client + + if (shouldStartMappingJob) { + stateJob = + qsTileViewModel.state + .filterNotNull() + .map { mapState(context, it, qsTileViewModel.config) } + .onEach { legacyState -> + val changed = legacyState.copyTo(cachedState) + if (changed) { + callbacks.forEach { it.onStateChanged(legacyState) } + } } - } - .launchIn(applicationScope) + .flowOn(uiBgDispatcher) + .launchIn(applicationScope) + } } } else { listeningClients.remove(client) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 88522d559c30..3a6c250e55f1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -172,6 +172,7 @@ import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirr import com.android.systemui.shade.data.repository.FlingInfo; import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; +import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.GestureRecorder; @@ -2269,7 +2270,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } float getDisplayDensity() { - return mCentralSurfaces.getDisplayDensity(); + if (ShadeWindowGoesAround.isEnabled()) { + return mView.getContext().getResources().getConfiguration().densityDpi; + } else { + return mCentralSurfaces.getDisplayDensity(); + } } /** Return whether a touch is near the gesture handle at the bottom of screen */ @@ -3830,7 +3835,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /* screenOnFromTouch=*/ getWakefulness().isAwakeFromTapOrGesture()); // Log collapse gesture if on lock screen. if (!expand && onKeyguard) { - float displayDensity = mCentralSurfaces.getDisplayDensity(); + float displayDensity = getDisplayDensity(); int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity); int velocityDp = (int) Math.abs(vel / displayDensity); mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt index bb0467f10e16..2eae3eb4fc19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt @@ -23,6 +23,8 @@ import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository +import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -37,17 +39,30 @@ class CallChipInteractor @Inject constructor( @Application private val scope: CoroutineScope, + ongoingCallInteractor: OngoingCallInteractor, repository: OngoingCallRepository, @StatusBarChipsLog private val logger: LogBuffer, ) { val ongoingCallState: StateFlow<OngoingCallModel> = - repository.ongoingCallState + (if (StatusBarChipsModernization.isEnabled) + ongoingCallInteractor.ongoingCallState + else + repository.ongoingCallState) .onEach { - logger.log(TAG, LogLevel.INFO, { str1 = it::class.simpleName }, { "State: $str1" }) + logger.log( + TAG, + LogLevel.INFO, + { str1 = it::class.simpleName }, + { "State: $str1" } + ) } - .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall) + .stateIn( + scope, + SharingStarted.Lazily, + OngoingCallModel.NoCall + ) companion object { private val TAG = "OngoingCall".pad() } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index b8cdd2587774..6f491e7beec8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -59,7 +59,8 @@ constructor( interactor.ongoingCallState .map { state -> when (state) { - is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden() + is OngoingCallModel.NoCall, + is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Hidden() is OngoingCallModel.InCall -> { val icon = if ( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index 47b695e50a0e..2588c7ae2363 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl import com.android.systemui.statusbar.phone.StatusBarSignalPolicy import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore @@ -63,11 +64,6 @@ interface StatusBarModule { @Binds @IntoMap - @ClassKey(OngoingCallController::class) - fun bindOngoingCallController(impl: OngoingCallController): CoreStartable - - @Binds - @IntoMap @ClassKey(LightBarController::class) fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable @@ -90,6 +86,18 @@ interface StatusBarModule { ): LightBarController.Factory companion object { + @Provides + @SysUISingleton + @IntoMap + @ClassKey(OngoingCallController::class) + fun ongoingCallController( + controller: OngoingCallController + ): CoreStartable = + if (StatusBarChipsModernization.isEnabled) { + CoreStartable.NOP + } else { + controller + } @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 6de4928cd0c1..e4768e83f7d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarCompone import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent.Startable; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization; import com.android.systemui.statusbar.phone.ui.DarkIconManager; import com.android.systemui.statusbar.phone.ui.StatusBarIconController; import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder; @@ -960,7 +961,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mOngoingCallController.addCallback(mOngoingCallListener); } // TODO(b/364653005): Do we also need to set the secondary activity chip? - mOngoingCallController.setChipView(mPrimaryOngoingActivityChip); + if (!StatusBarChipsModernization.isEnabled()) { + mOngoingCallController.setChipView(mPrimaryOngoingActivityChip); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 78926c78a368..216630409160 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -60,7 +60,10 @@ import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -/** A controller to handle the ongoing call chip in the collapsed status bar. */ +/** A controller to handle the ongoing call chip in the collapsed status bar. + * @deprecated Use [OngoingCallInteractor] instead, which follows recommended architecture patterns + */ +@Deprecated("Use OngoingCallInteractor instead") @SysUISingleton class OngoingCallController @Inject @@ -165,6 +168,9 @@ constructor( } override fun start() { + if (StatusBarChipsModernization.isEnabled) + return + dumpManager.registerDumpable(this) if (Flags.statusBarUseReposForCallChip()) { @@ -201,6 +207,8 @@ constructor( * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment]. */ fun setChipView(chipView: View) { + StatusBarChipsModernization.assertInLegacyMode() + tearDownChipView() this.chipView = chipView val backgroundView: ChipBackgroundContainer? = @@ -217,6 +225,8 @@ constructor( * Returns true if there's an active ongoing call that should be displayed in a status bar chip. */ fun hasOngoingCall(): Boolean { + StatusBarChipsModernization.assertInLegacyMode() + return callNotificationInfo?.isOngoing == true && // When the user is in the phone app, don't show the chip. !uidObserver.isCallAppVisible @@ -224,6 +234,8 @@ constructor( /** Creates the right [OngoingCallModel] based on the call state. */ private fun getOngoingCallModel(): OngoingCallModel { + StatusBarChipsModernization.assertInLegacyMode() + if (hasOngoingCall()) { val currentInfo = callNotificationInfo @@ -255,6 +267,8 @@ constructor( } override fun addCallback(listener: OngoingCallListener) { + StatusBarChipsModernization.assertInLegacyMode() + synchronized(mListeners) { if (!mListeners.contains(listener)) { mListeners.add(listener) @@ -263,10 +277,14 @@ constructor( } override fun removeCallback(listener: OngoingCallListener) { + StatusBarChipsModernization.assertInLegacyMode() + synchronized(mListeners) { mListeners.remove(listener) } } private fun updateInfoFromNotifModel(notifModel: ActiveNotificationModel?) { + StatusBarChipsModernization.assertInLegacyMode() + if (notifModel == null) { logger.log(TAG, LogLevel.DEBUG, {}, { "NotifInteractorCallModel: null" }) removeChip() @@ -310,6 +328,8 @@ constructor( } private fun updateChip() { + StatusBarChipsModernization.assertInLegacyMode() + val currentCallNotificationInfo = callNotificationInfo ?: return val currentChipView = chipView @@ -360,6 +380,8 @@ constructor( } private fun updateChipClickListener() { + StatusBarChipsModernization.assertInLegacyMode() + if (Flags.statusBarScreenSharingChips()) { return } @@ -386,10 +408,14 @@ constructor( /** Returns true if the given [procState] represents a process that's visible to the user. */ private fun isProcessVisibleToUser(procState: Int): Boolean { + StatusBarChipsModernization.assertInLegacyMode() + return procState <= ActivityManager.PROCESS_STATE_TOP } private fun updateGestureListening() { + StatusBarChipsModernization.assertInLegacyMode() + if ( callNotificationInfo == null || callNotificationInfo?.statusBarSwipedAway == true || @@ -404,6 +430,8 @@ constructor( } private fun removeChip() { + StatusBarChipsModernization.assertInLegacyMode() + callNotificationInfo = null if (!Flags.statusBarScreenSharingChips()) { tearDownChipView() @@ -432,6 +460,8 @@ constructor( * detected. */ private fun onSwipeAwayGestureDetected() { + StatusBarChipsModernization.assertInLegacyMode() + logger.log(TAG, LogLevel.DEBUG, {}, { "Swipe away gesture detected" }) callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true) statusBarWindowControllerStore.defaultDisplay.setOngoingProcessRequiresStatusBarVisible( @@ -441,6 +471,8 @@ constructor( } private fun sendStateChangeEvent() { + StatusBarChipsModernization.assertInLegacyMode() + ongoingCallRepository.setOngoingCallState(getOngoingCallModel()) mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt new file mode 100644 index 000000000000..2ab2b68bbb2e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt @@ -0,0 +1,52 @@ +/* +* Copyright (C) 2024 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package com.android.systemui.statusbar.phone.ongoingcall + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the status_bar_use_interactor_for_call_chip flag state. */ +@Suppress("NOTHING_TO_INLINE") +object StatusBarChipsModernization { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.statusBarChipsModernization() && Flags.statusBarRootModernization() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt index f16371ae7e21..b932c71ab426 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt @@ -20,6 +20,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -33,7 +34,9 @@ import kotlinx.coroutines.flow.asStateFlow * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController] and * [com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore]. Instead, those two * classes both refer to this repository. + * @deprecated Use [OngoingCallInteractor] instead. */ +@Deprecated("Use OngoingCallInteractor instead") @SysUISingleton class OngoingCallRepository @Inject @@ -49,6 +52,8 @@ constructor( * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController]. */ fun setOngoingCallState(state: OngoingCallModel) { + StatusBarChipsModernization.assertInLegacyMode() + logger.log( TAG, LogLevel.DEBUG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt new file mode 100644 index 000000000000..1f7bd1418543 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor + +import com.android.systemui.activity.data.repository.ActivityManagerRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog +import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn + +/** + * Interactor for determining whether to show a chip in the status bar for ongoing phone calls. + * + * This class monitors call notifications and the visibility of call apps to determine the appropriate + * chip state. It emits: + * * - [OngoingCallModel.NoCall] when there is no call notification + * * - [OngoingCallModel.InCallWithVisibleApp] when there is a call notification but the call app is visible + * * - [OngoingCallModel.InCall] when there is a call notification and the call app is not visible + * */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class OngoingCallInteractor @Inject constructor( + @Application private val scope: CoroutineScope, + activityManagerRepository: ActivityManagerRepository, + activeNotificationsInteractor: ActiveNotificationsInteractor, + @OngoingCallLog private val logBuffer: LogBuffer, +) { + private val logger = Logger(logBuffer, TAG) + + /** + * The current state of ongoing calls. + */ + val ongoingCallState: StateFlow<OngoingCallModel> = + activeNotificationsInteractor.ongoingCallNotification + .flatMapLatest { notificationModel -> + when (notificationModel) { + null -> { + logger.d("No active call notification - hiding chip") + flowOf(OngoingCallModel.NoCall) + } + + else -> combine( + flowOf(notificationModel), + activityManagerRepository.createIsAppVisibleFlow( + creationUid = notificationModel.uid, + logger = logger, + identifyingLogTag = TAG, + ), + ) { model, isVisible -> + when { + isVisible -> { + logger.d({ "Call app is visible: uid=$int1" }) { + int1 = model.uid + } + OngoingCallModel.InCallWithVisibleApp + } + + else -> { + logger.d({ "Active call detected: startTime=$long1 hasIcon=$bool1" }) { + long1 = model.whenTime + bool1 = model.statusBarChipIconView != null + } + OngoingCallModel.InCall( + startTimeMs = model.whenTime, + notificationIconView = model.statusBarChipIconView, + intent = model.contentIntent, + ) + } + } + } + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingCallModel.NoCall) + + companion object { + private val TAG = "OngoingCall" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt index 34bff80ea919..c2c91b27d074 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt @@ -25,6 +25,12 @@ sealed interface OngoingCallModel { data object NoCall : OngoingCallModel /** + * There is an ongoing call but the call app is currently visible, so we don't need to show + * the chip. + */ + data object InCallWithVisibleApp : OngoingCallModel + + /** * There *is* an ongoing call. * * @property startTimeMs the time that the phone call started, based on the notification's diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt index 1e8b0166409c..812e0eb0908f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarView import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ui.DarkIconManager import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder @@ -150,12 +151,11 @@ fun StatusBarRoot( ) iconController.addIconGroup(darkIconManager) - // TODO(b/372657935): This won't be needed once OngoingCallController is - // implemented in recommended architecture - ongoingCallController.setChipView( - phoneStatusBarView.requireViewById(R.id.ongoing_activity_chip_primary) - ) - + if (!StatusBarChipsModernization.isEnabled) { + ongoingCallController.setChipView( + phoneStatusBarView.requireViewById(R.id.ongoing_activity_chip_primary) + ) + } // For notifications, first inflate the [NotificationIconContainer] val notificationIconArea = phoneStatusBarView.requireViewById<ViewGroup>(R.id.notification_icon_area) diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java index 1d32a4fd69d6..389b6fb4b0ef 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java @@ -32,6 +32,7 @@ import android.provider.Settings; import android.service.quickaccesswallet.GetWalletCardsRequest; import android.service.quickaccesswallet.QuickAccessWalletClient; import android.service.quickaccesswallet.QuickAccessWalletClientImpl; +import android.service.quickaccesswallet.WalletCard; import android.util.Log; import com.android.systemui.animation.ActivityTransitionAnimator; @@ -268,6 +269,23 @@ public class QuickAccessWalletController { }); } + /** + * Starts the {@link android.app.PendingIntent} for a {@link WalletCard}. + * + * This should be used to open a selected card from the QuickAccessWallet UI or + * the settings tile. + * + * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent. + * @param animationController an {@link ActivityTransitionAnimator.Controller} to provide a + * smooth animation for the activity launch. + */ + public void startWalletCardPendingIntent(WalletCard card, + ActivityStarter activityStarter, + ActivityTransitionAnimator.Controller animationController) { + activityStarter.postStartActivityDismissingKeyguard( + card.getPendingIntent(), animationController); + } + private Intent getSysUiWalletIntent() { return new Intent(mContext, WalletActivity.class) .setAction(Intent.ACTION_VIEW); diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt index 4ca84c58f6d9..50fad3bf697e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.communal.data.backup.CommunalBackupUtilsTest.FakeWid import com.android.systemui.communal.data.db.CommunalDatabase import com.android.systemui.communal.data.db.CommunalWidgetDao import com.android.systemui.communal.proto.toCommunalHubState +import com.android.systemui.communal.shared.model.SpanValue import com.android.systemui.lifecycle.InstantTaskExecutorRule import com.google.common.truth.Truth.assertThat import java.io.File @@ -120,21 +121,32 @@ class CommunalBackupHelperTest : SysuiTestCase() { componentName = "com.android.fakePackage1/fakeWidget1", rank = 0, userSerialNumber = 0, + spanY = SpanValue.Responsive(1), ), FakeWidgetMetadata( widgetId = 12, componentName = "com.android.fakePackage2/fakeWidget2", rank = 1, userSerialNumber = 0, + spanY = SpanValue.Responsive(2), ), FakeWidgetMetadata( widgetId = 13, componentName = "com.android.fakePackage3/fakeWidget3", rank = 2, userSerialNumber = 10, + spanY = SpanValue.Responsive(3), ), ) - .onEach { dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber) } + .onEach { + dao.addWidget( + widgetId = it.widgetId, + componentName = it.componentName, + rank = it.rank, + userSerialNumber = it.userSerialNumber, + spanY = it.spanY, + ) + } } private fun getBackupDataInputStream(): BackupDataInputStream { diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt index edc8c837bf78..d31e4664d4a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt @@ -23,6 +23,9 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.db.CommunalDatabase import com.android.systemui.communal.data.db.CommunalWidgetDao import com.android.systemui.communal.nano.CommunalHubState +import com.android.systemui.communal.shared.model.SpanValue +import com.android.systemui.communal.shared.model.toFixed +import com.android.systemui.communal.shared.model.toResponsive import com.android.systemui.lifecycle.InstantTaskExecutorRule import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat @@ -71,22 +74,25 @@ class CommunalBackupUtilsTest : SysuiTestCase() { componentName = "com.android.fakePackage1/fakeWidget1", rank = 0, userSerialNumber = 0, + spanY = SpanValue.Responsive(1), ), FakeWidgetMetadata( widgetId = 12, componentName = "com.android.fakePackage2/fakeWidget2", rank = 1, userSerialNumber = 0, + spanY = SpanValue.Responsive(2), ), FakeWidgetMetadata( widgetId = 13, componentName = "com.android.fakePackage3/fakeWidget3", rank = 2, userSerialNumber = 10, + spanY = SpanValue.Responsive(3), ), ) expectedWidgets.forEach { - dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber) + dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber, it.spanY) } // Get communal hub state @@ -150,6 +156,7 @@ class CommunalBackupUtilsTest : SysuiTestCase() { val componentName: String, val rank: Int, val userSerialNumber: Int, + val spanY: SpanValue, ) companion object { @@ -163,7 +170,9 @@ class CommunalBackupUtilsTest : SysuiTestCase() { actual?.widgetId == expected?.widgetId && actual?.componentName == expected?.componentName && actual?.rank == expected?.rank && - actual?.userSerialNumber == expected?.userSerialNumber + actual?.userSerialNumber == expected?.userSerialNumber && + actual?.spanY == expected?.spanY?.toFixed()?.value && + actual?.spanYNew == expected?.spanY?.toResponsive()?.value }, "represents", ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt index 7d5a334b45ea..1466e32b1296 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt @@ -22,6 +22,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.shared.model.SpanValue +import com.android.systemui.communal.shared.model.toResponsive import com.android.systemui.lifecycle.InstantTaskExecutorRule import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -173,6 +175,49 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() { databaseV4.verifyWidgetsV4(fakeWidgetsV3.map { it.getV4() }) } + @Test + fun migrate4To5_addNewSpanYColumn() { + val databaseV4 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 4) + + val fakeWidgetsV4 = + listOf( + FakeCommunalWidgetItemV4( + widgetId = 1, + componentName = "test_widget_1", + itemId = 11, + userSerialNumber = 0, + spanY = 3, + ), + FakeCommunalWidgetItemV4( + widgetId = 2, + componentName = "test_widget_2", + itemId = 12, + userSerialNumber = 10, + spanY = 6, + ), + FakeCommunalWidgetItemV4( + widgetId = 3, + componentName = "test_widget_3", + itemId = 13, + userSerialNumber = 0, + spanY = 0, + ), + ) + databaseV4.insertWidgetsV4(fakeWidgetsV4) + + databaseV4.verifyWidgetsV4(fakeWidgetsV4) + + val databaseV5 = + migrationTestHelper.runMigrationsAndValidate( + name = DATABASE_NAME, + version = 5, + validateDroppedTables = false, + CommunalDatabase.MIGRATION_4_5, + ) + + databaseV5.verifyWidgetsV5(fakeWidgetsV4.map { it.getV5() }) + } + private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) { widgets.forEach { widget -> execSQL( @@ -198,6 +243,24 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() { } } + private fun SupportSQLiteDatabase.insertWidgetsV4(widgets: List<FakeCommunalWidgetItemV4>) { + widgets.forEach { widget -> + execSQL( + "INSERT INTO communal_widget_table(" + + "widget_id, " + + "component_name, " + + "item_id, " + + "user_serial_number, " + + "span_y) " + + "VALUES(${widget.widgetId}, " + + "'${widget.componentName}', " + + "${widget.itemId}, " + + "${widget.userSerialNumber}," + + "${widget.spanY})" + ) + } + } + private fun SupportSQLiteDatabase.verifyWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) { val cursor = query("SELECT * FROM communal_widget_table") assertThat(cursor.moveToFirst()).isTrue() @@ -270,6 +333,27 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() { assertThat(cursor.isAfterLast).isTrue() } + private fun SupportSQLiteDatabase.verifyWidgetsV5(widgets: List<FakeCommunalWidgetItemV5>) { + val cursor = query("SELECT * FROM communal_widget_table") + assertThat(cursor.moveToFirst()).isTrue() + + widgets.forEach { widget -> + assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId) + assertThat(cursor.getString(cursor.getColumnIndex("component_name"))) + .isEqualTo(widget.componentName) + assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId) + assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number"))) + .isEqualTo(widget.userSerialNumber) + assertThat(cursor.getInt(cursor.getColumnIndex("span_y"))).isEqualTo(widget.spanY) + assertThat(cursor.getInt(cursor.getColumnIndex("span_y_new"))) + .isEqualTo(widget.spanYNew) + + cursor.moveToNext() + } + + assertThat(cursor.isAfterLast).isTrue() + } + private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) { ranks.forEach { rank -> execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})") @@ -334,6 +418,27 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() { val spanY: Int, ) + private fun FakeCommunalWidgetItemV4.getV5(): FakeCommunalWidgetItemV5 { + val spanYFixed = SpanValue.Fixed(spanY) + return FakeCommunalWidgetItemV5( + widgetId = widgetId, + componentName = componentName, + itemId = itemId, + userSerialNumber = userSerialNumber, + spanY = spanYFixed.value, + spanYNew = spanYFixed.toResponsive().value, + ) + } + + private data class FakeCommunalWidgetItemV5( + val widgetId: Int, + val componentName: String, + val itemId: Int, + val userSerialNumber: Int, + val spanY: Int, + val spanYNew: Int, + ) + private data class FakeCommunalItemRank(val rank: Int) companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt index 2312bbd2d7f8..2acb7750273b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt @@ -22,7 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.nano.CommunalHubState -import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.shared.model.SpanValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.lifecycle.InstantTaskExecutorRule import com.google.common.truth.Truth.assertThat @@ -68,12 +68,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() { @Test fun addWidget_readValueInDb() = testScope.runTest { - val (widgetId, provider, rank, userSerialNumber) = widgetInfo1 + val (widgetId, provider, rank, userSerialNumber, spanY) = widgetInfo1 communalWidgetDao.addWidget( widgetId = widgetId, provider = provider, rank = rank, userSerialNumber = userSerialNumber, + spanY = spanY, ) val entry = communalWidgetDao.getWidgetByIdNow(id = 1) assertThat(entry).isEqualTo(communalWidgetItemEntry1) @@ -82,12 +83,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() { @Test fun deleteWidget_notInDb_returnsFalse() = testScope.runTest { - val (widgetId, provider, rank, userSerialNumber) = widgetInfo1 + val (widgetId, provider, rank, userSerialNumber, spanY) = widgetInfo1 communalWidgetDao.addWidget( widgetId = widgetId, provider = provider, rank = rank, userSerialNumber = userSerialNumber, + spanY = spanY, ) assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse() } @@ -98,12 +100,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() { val widgetsToAdd = listOf(widgetInfo1, widgetInfo2) val widgets = collectLastValue(communalWidgetDao.getWidgets()) widgetsToAdd.forEach { - val (widgetId, provider, rank, userSerialNumber) = it + val (widgetId, provider, rank, userSerialNumber, spanY) = it communalWidgetDao.addWidget( widgetId = widgetId, provider = provider, rank = rank, userSerialNumber = userSerialNumber, + spanY = spanY, ) } assertThat(widgets()) @@ -126,11 +129,12 @@ class CommunalWidgetDaoTest : SysuiTestCase() { // Add widgets one by one without specifying rank val widgetsToAdd = listOf(widgetInfo1, widgetInfo2, widgetInfo3) widgetsToAdd.forEach { - val (widgetId, provider, _, userSerialNumber) = it + val (widgetId, provider, _, userSerialNumber, spanY) = it communalWidgetDao.addWidget( widgetId = widgetId, provider = provider, userSerialNumber = userSerialNumber, + spanY = spanY, ) } @@ -153,12 +157,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() { val widgets = collectLastValue(communalWidgetDao.getWidgets()) widgetsToAdd.forEach { - val (widgetId, provider, rank, userSerialNumber) = it + val (widgetId, provider, rank, userSerialNumber, spanY) = it communalWidgetDao.addWidget( widgetId = widgetId, provider = provider, rank = rank, userSerialNumber = userSerialNumber, + spanY = spanY, ) } assertThat(widgets()) @@ -180,12 +185,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() { val widgets = collectLastValue(communalWidgetDao.getWidgets()) widgetsToAdd.forEach { - val (widgetId, provider, rank, userSerialNumber) = it + val (widgetId, provider, rank, userSerialNumber, spanY) = it communalWidgetDao.addWidget( widgetId = widgetId, provider = provider, rank = rank, userSerialNumber = userSerialNumber, + spanY = spanY, ) } assertThat(widgets()) @@ -217,12 +223,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() { val widgets = collectLastValue(communalWidgetDao.getWidgets()) existingWidgets.forEach { - val (widgetId, provider, rank, userSerialNumber) = it + val (widgetId, provider, rank, userSerialNumber, spanY) = it communalWidgetDao.addWidget( widgetId = widgetId, provider = provider, rank = rank, userSerialNumber = userSerialNumber, + spanY = spanY, ) } assertThat(widgets()) @@ -242,6 +249,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { provider = ComponentName("pk_name", "cls_name_4"), rank = 1, userSerialNumber = 0, + spanY = SpanValue.Responsive(1), ) val newRankEntry = CommunalItemRank(uid = 4L, rank = 1) @@ -253,6 +261,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { itemId = 4L, userSerialNumber = 0, spanY = 3, + spanYNew = 1, ) assertThat(widgets()) .containsExactly( @@ -279,21 +288,21 @@ class CommunalWidgetDaoTest : SysuiTestCase() { provider = ComponentName("pkg_name", "cls_name_1"), rank = 0, userSerialNumber = 0, - spanY = CommunalContentSize.FULL.span, + spanY = SpanValue.Responsive(1), ) communalWidgetDao.addWidget( widgetId = 2, provider = ComponentName("pkg_name", "cls_name_2"), rank = 1, userSerialNumber = 0, - spanY = CommunalContentSize.HALF.span, + spanY = SpanValue.Responsive(2), ) communalWidgetDao.addWidget( widgetId = 3, provider = ComponentName("pkg_name", "cls_name_3"), rank = 2, userSerialNumber = 0, - spanY = CommunalContentSize.THIRD.span, + spanY = SpanValue.Fixed(3), ) // Verify that the widgets have the correct spanY values @@ -306,7 +315,8 @@ class CommunalWidgetDaoTest : SysuiTestCase() { componentName = "pkg_name/cls_name_1", itemId = 1L, userSerialNumber = 0, - spanY = CommunalContentSize.FULL.span, + spanY = 3, + spanYNew = 1, ), CommunalItemRank(uid = 2L, rank = 1), CommunalWidgetItem( @@ -315,7 +325,8 @@ class CommunalWidgetDaoTest : SysuiTestCase() { componentName = "pkg_name/cls_name_2", itemId = 2L, userSerialNumber = 0, - spanY = CommunalContentSize.HALF.span, + spanY = 6, + spanYNew = 2, ), CommunalItemRank(uid = 3L, rank = 2), CommunalWidgetItem( @@ -324,7 +335,8 @@ class CommunalWidgetDaoTest : SysuiTestCase() { componentName = "pkg_name/cls_name_3", itemId = 3L, userSerialNumber = 0, - spanY = CommunalContentSize.THIRD.span, + spanY = 3, + spanYNew = 1, ), ) .inOrder() @@ -352,7 +364,8 @@ class CommunalWidgetDaoTest : SysuiTestCase() { componentName = fakeWidget.componentName, itemId = rank.uid, userSerialNumber = fakeWidget.userSerialNumber, - spanY = 3, + spanY = fakeWidget.spanY.coerceAtLeast(3), + spanYNew = fakeWidget.spanYNew.coerceAtLeast(1), ) expected[rank] = widget } @@ -366,6 +379,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { provider = metadata.provider, rank = rank ?: metadata.rank, userSerialNumber = metadata.userSerialNumber, + spanY = metadata.spanY, ) } @@ -374,6 +388,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { val provider: ComponentName, val rank: Int, val userSerialNumber: Int, + val spanY: SpanValue, ) companion object { @@ -383,6 +398,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { provider = ComponentName("pk_name", "cls_name_1"), rank = 0, userSerialNumber = 0, + spanY = SpanValue.Responsive(1), ) val widgetInfo2 = FakeWidgetMetadata( @@ -390,6 +406,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { provider = ComponentName("pk_name", "cls_name_2"), rank = 1, userSerialNumber = 0, + spanY = SpanValue.Responsive(1), ) val widgetInfo3 = FakeWidgetMetadata( @@ -397,6 +414,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { provider = ComponentName("pk_name", "cls_name_3"), rank = 2, userSerialNumber = 10, + spanY = SpanValue.Responsive(1), ) val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.rank) val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.rank) @@ -409,6 +427,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { itemId = communalItemRankEntry1.uid, userSerialNumber = widgetInfo1.userSerialNumber, spanY = 3, + spanYNew = 1, ) val communalWidgetItemEntry2 = CommunalWidgetItem( @@ -418,6 +437,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { itemId = communalItemRankEntry2.uid, userSerialNumber = widgetInfo2.userSerialNumber, spanY = 3, + spanYNew = 1, ) val communalWidgetItemEntry3 = CommunalWidgetItem( @@ -427,6 +447,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() { itemId = communalItemRankEntry3.uid, userSerialNumber = widgetInfo3.userSerialNumber, spanY = 3, + spanYNew = 1, ) val fakeState = CommunalHubState().apply { @@ -437,12 +458,14 @@ class CommunalWidgetDaoTest : SysuiTestCase() { componentName = "pk_name/fake_widget_1" rank = 1 userSerialNumber = 0 + spanY = 3 }, CommunalHubState.CommunalWidgetItem().apply { widgetId = 2 componentName = "pk_name/fake_widget_2" rank = 2 userSerialNumber = 10 + spanYNew = 1 }, ) .toTypedArray() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index d1e4f646a382..3e24fbe6cd8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization; import com.android.systemui.statusbar.phone.ui.DarkIconManager; import com.android.systemui.statusbar.phone.ui.StatusBarIconController; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewBinder; @@ -155,9 +156,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { any(StatusBarWindowStateListener.class)); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testDisableNone() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testDisableNone() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -166,9 +167,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getClockView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testDisableSystemInfo_systemAnimationIdle_doesHide() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testDisableSystemInfo_systemAnimationIdle_doesHide() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); @@ -184,9 +185,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); @@ -214,9 +215,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(1, getEndSideContentView().getAlpha(), 0.01); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); @@ -231,9 +232,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); assertEquals(1, getEndSideContentView().getAlpha(), 0.01); @@ -247,9 +248,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(0, getEndSideContentView().getAlpha(), 0.01); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); assertEquals(1, getEndSideContentView().getAlpha(), 0.01); @@ -271,9 +272,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(1, getEndSideContentView().getAlpha(), 0.01); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testDisableNotifications() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testDisableNotifications() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); @@ -307,9 +308,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testDisableClock() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testDisableClock() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false); @@ -343,10 +344,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getClockView().getVisibility()); } - @Test - @DisableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_shadeOpenAndShouldHide_everythingHidden() { + @Test + @DisableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_shadeOpenAndShouldHide_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN the shade is open and configured to hide the status bar icons @@ -361,10 +362,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } - @Test - @DisableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_shadeOpenButNotShouldHide_everythingShown() { + @Test + @DisableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_shadeOpenButNotShouldHide_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN the shade is open but *not* configured to hide the status bar icons @@ -379,11 +380,11 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } - /** Regression test for b/279790651. */ - @Test - @DisableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() { + /** Regression test for b/279790651. */ + @Test + @DisableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN the shade is open and configured to hide the status bar icons @@ -409,9 +410,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_notTransitioningToOccluded_everythingShown() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_notTransitioningToOccluded_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(false); @@ -424,10 +425,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } - @Test - @DisableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_isTransitioningToOccluded_everythingHidden() { + @Test + @DisableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_isTransitioningToOccluded_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(true); @@ -440,10 +441,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } - @Test - @DisableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() { + @Test + @DisableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN the transition is occurring @@ -472,9 +473,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getUserChipView().getVisibility()); } - @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) - public void disable_noOngoingCall_chipHidden() { + @Test + @DisableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void disable_noOngoingCall_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(false); @@ -484,9 +489,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) - public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() { + @Test + @DisableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); @@ -497,9 +506,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } - @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) - public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() { + @Test + @DisableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); @@ -510,9 +523,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) - public void disable_hasOngoingCallButAlsoHun_chipHidden() { + @Test + @DisableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void disable_hasOngoingCallButAlsoHun_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); @@ -523,9 +540,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) - public void disable_ongoingCallEnded_chipHidden() { + @Test + @DisableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void disable_ongoingCallEnded_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Ongoing call started @@ -547,9 +568,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) - public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() { + @Test + @DisableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating fragment.enableAnimationsForTesting(); @@ -564,9 +589,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } - @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) - public void screenSharingChipsDisabled_ignoresNewCallback() { + @Test + @DisableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void screenSharingChipsDisabled_ignoresNewCallback() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN there *is* an ongoing call via old callback @@ -597,10 +626,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void noOngoingActivity_chipHidden() { + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void noOngoingActivity_chipHidden() { resumeAndGetFragment(); // TODO(b/332662551): We *should* be able to just set a value on @@ -615,10 +644,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() { + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() { resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -630,12 +659,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } - @Test - @EnableFlags({ - FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, - StatusBarNotifChips.FLAG_NAME, - StatusBarRootModernization.FLAG_NAME}) - public void hasPrimaryOngoingActivity_viewsUnchangedWhenRootModernizationFlagOn() { + @Test + @EnableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + StatusBarNotifChips.FLAG_NAME, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void hasPrimaryOngoingActivity_viewsUnchangedWhenRootModernizationFlagOn() { resumeAndGetFragment(); assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); @@ -658,10 +689,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); } - @Test - @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) - public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() { + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({ + StatusBarNotifChips.FLAG_NAME, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() { resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -672,10 +707,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() { + @Test + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() { resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -687,10 +722,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } - @Test - @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) - public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() { + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({ + StatusBarNotifChips.FLAG_NAME, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -704,10 +743,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() { + @Test + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -722,10 +761,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) - public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() { + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({ + StatusBarNotifChips.FLAG_NAME, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -739,10 +782,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() { + @Test + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -757,10 +800,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) - public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() { + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({ + StatusBarNotifChips.FLAG_NAME, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() { resumeAndGetFragment(); // Ongoing activity started @@ -780,10 +827,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() { + @Test + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() { resumeAndGetFragment(); // Ongoing activity started @@ -803,10 +850,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void secondaryOngoingActivityEnded_chipHidden() { + @Test + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void secondaryOngoingActivityEnded_chipHidden() { resumeAndGetFragment(); // Secondary ongoing activity started @@ -826,10 +873,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) - public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() { + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({ + StatusBarNotifChips.FLAG_NAME, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating fragment.enableAnimationsForTesting(); @@ -845,10 +896,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } - @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() { + @Test + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating fragment.enableAnimationsForTesting(); @@ -864,10 +915,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } - @Test - @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) - public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() { + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({ + StatusBarNotifChips.FLAG_NAME, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME + }) + public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN there *is* an ongoing call via old callback @@ -897,10 +952,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() { + @Test + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN there *is* an ongoing call via old callback @@ -931,10 +986,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getSecondaryOngoingActivityChipView().getVisibility()); } - @Test - @EnableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void isHomeStatusBarAllowedByScene_false_everythingHidden() { + @Test + @EnableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void isHomeStatusBarAllowedByScene_false_everythingHidden() { resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(false); @@ -945,10 +1000,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } - @Test - @EnableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void isHomeStatusBarAllowedByScene_true_everythingShown() { + @Test + @EnableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void isHomeStatusBarAllowedByScene_true_everythingShown() { resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(true); @@ -959,10 +1014,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } - @Test - @EnableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() { + @Test + @EnableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN the scene doesn't allow the status bar @@ -977,10 +1032,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } - @Test - @EnableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() { + @Test + @EnableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN the scene does allow the status bar @@ -995,10 +1050,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } - @Test - @DisableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() { + @Test + @DisableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() { resumeAndGetFragment(); // Even if the scene says to hide the home status bar @@ -1010,9 +1065,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_isDozing_clockAndSystemInfoVisible() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_isDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); @@ -1022,9 +1077,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getClockView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_NotDozing_clockAndSystemInfoVisible() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_NotDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(false); @@ -1034,9 +1089,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getClockView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); @@ -1045,9 +1100,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.GONE, getClockView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false); @@ -1098,10 +1153,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertFalse(contains); } - @Test - @DisableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testStatusBarIcons_hiddenThroughoutCameraLaunch() { + @Test + @DisableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testStatusBarIcons_hiddenThroughoutCameraLaunch() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mockSecureCameraLaunch(fragment, true /* launched */); @@ -1121,10 +1176,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getClockView().getVisibility()); } - @Test - @DisableSceneContainer - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() { + @Test + @DisableSceneContainer + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN a transition to dream has started @@ -1158,9 +1213,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getClockView().getVisibility()); } - @Test - @DisableFlags(StatusBarRootModernization.FLAG_NAME) - public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() { + @Test + @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) + public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN a transition to dream has started but we're *not* dreaming diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 153a8be06adc..3e44364dc6a0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -118,6 +118,7 @@ public abstract class SysuiTestCase { android.net.platform.flags.Flags.class, android.os.Flags.class, android.service.controls.flags.Flags.class, + android.service.quickaccesswallet.Flags.class, com.android.internal.telephony.flags.Flags.class, com.android.server.notification.Flags.class, com.android.systemui.Flags.class); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index 3b175853de7d..1f7f3bc4be2b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -55,7 +55,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) : rank: Int = 0, category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, userId: Int = 0, - spanY: Int = CommunalContentSize.HALF.span, + spanY: Int = CommunalContentSize.FixedSize.HALF.span, ) { fakeDatabase[appWidgetId] = CommunalWidgetContentModel.Available( @@ -87,7 +87,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) : componentName = ComponentName.unflattenFromString(componentName)!!, icon = icon, user = UserHandle(userId), - spanY = CommunalContentSize.HALF.span, + spanY = CommunalContentSize.FixedSize.HALF.span, ) updateListFromDatabase() } @@ -143,7 +143,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) : appWidgetId = id, providerInfo = providerInfo, rank = rank, - spanY = CommunalContentSize.HALF.span, + spanY = CommunalContentSize.FixedSize.HALF.span, ) updateListFromDatabase() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 2bff0c66889f..1828da5c0790 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -160,6 +160,7 @@ val Kosmos.shortcutHelperCategoriesInteractor by val Kosmos.shortcutHelperViewModel by Kosmos.Fixture { ShortcutHelperViewModel( + applicationContext, mockRoleManager, userTracker, applicationCoroutineScope, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt index a90876551d20..de9f629ef787 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles.viewmodel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.util.mockito.mock val Kosmos.qsTileViewModelAdaperFactory by @@ -28,6 +29,7 @@ val Kosmos.qsTileViewModelAdaperFactory by applicationCoroutineScope, mock(), qsTileViewModel, + testDispatcher, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt index fcd14d8512fb..d8d4d2b65eff 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt @@ -20,12 +20,14 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.statusbar.chips.statusBarChipsLogger import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository +import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.ongoingCallInteractor val Kosmos.callChipInteractor: CallChipInteractor by Kosmos.Fixture { CallChipInteractor( scope = applicationCoroutineScope, repository = ongoingCallRepository, + ongoingCallInteractor = ongoingCallInteractor, logger = statusBarChipsLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt new file mode 100644 index 000000000000..51fb36fb2ead --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor + +import com.android.systemui.activity.data.repository.activityManagerRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor + +val Kosmos.ongoingCallInteractor: OngoingCallInteractor by + Kosmos.Fixture { + OngoingCallInteractor( + scope = applicationCoroutineScope, + activeNotificationsInteractor = activeNotificationsInteractor, + activityManagerRepository = activityManagerRepository, + logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"), + ) + } diff --git a/services/core/Android.bp b/services/core/Android.bp index 08206150cebb..ffa259b536ec 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -221,6 +221,7 @@ java_library_static { "securebox", "apache-commons-math", "battery_saver_flag_lib", + "guava", "notification_flags_lib", "power_hint_flags_lib", "biometrics_flags_lib", diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 43774bbc51ca..b0dae6a1f306 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -90,6 +90,7 @@ public abstract class PackageManagerInternal { */ public static final int RESOLVE_NON_RESOLVER_ONLY = 0x00000002; + @Deprecated @IntDef(value = { INTEGRITY_VERIFICATION_ALLOW, INTEGRITY_VERIFICATION_REJECT, @@ -97,18 +98,10 @@ public abstract class PackageManagerInternal { @Retention(RetentionPolicy.SOURCE) public @interface IntegrityVerificationResult {} - /** - * Used as the {@code verificationCode} argument for - * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the - * integrity component allows the install to proceed. - */ + @Deprecated public static final int INTEGRITY_VERIFICATION_ALLOW = 1; - /** - * Used as the {@code verificationCode} argument for - * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the - * integrity component does not allow install to proceed. - */ + @Deprecated public static final int INTEGRITY_VERIFICATION_REJECT = 0; /** @@ -1131,17 +1124,13 @@ public abstract class PackageManagerInternal { public abstract boolean isPermissionUpgradeNeeded(@UserIdInt int userId); /** - * Allows the integrity component to respond to the - * {@link Intent#ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION package verification - * broadcast} to respond to the package manager. The response must include - * the {@code verificationCode} which is one of - * {@link #INTEGRITY_VERIFICATION_ALLOW} and {@link #INTEGRITY_VERIFICATION_REJECT}. + * Used to allow the integrity component to respond to the + * ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION package verification + * broadcast to respond to the package manager. * - * @param verificationId pending package identifier as passed via the - * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra. - * @param verificationResult either {@link #INTEGRITY_VERIFICATION_ALLOW} - * or {@link #INTEGRITY_VERIFICATION_REJECT}. + * Deprecated. */ + @Deprecated public abstract void setIntegrityVerificationResult(int verificationId, @IntegrityVerificationResult int verificationResult); diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index a132876b72a3..0914b7e3eeb2 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -93,29 +93,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { mContext = context; mPackageManagerInternal = packageManagerInternal; mHandler = handler; - - IntentFilter integrityVerificationFilter = new IntentFilter(); - integrityVerificationFilter.addAction(ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION); - try { - integrityVerificationFilter.addDataType(PACKAGE_MIME_TYPE); - } catch (IntentFilter.MalformedMimeTypeException e) { - throw new RuntimeException("Mime type malformed: should never happen.", e); - } - - mContext.registerReceiver( - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (!ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION.equals( - intent.getAction())) { - return; - } - mHandler.post(() -> handleIntegrityVerification(intent)); - } - }, - integrityVerificationFilter, - /* broadcastPermission= */ null, - mHandler); } @Override @@ -157,10 +134,4 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { public List<String> getWhitelistedRuleProviders() { return Collections.emptyList(); } - - private void handleIntegrityVerification(Intent intent) { - int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1); - mPackageManagerInternal.setIntegrityVerificationResult( - verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); - } } diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index 65d0ab337400..14eeb3dc78f8 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -35,6 +35,9 @@ import android.util.Log; import com.android.server.SystemService; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + import org.json.JSONException; import org.json.JSONObject; @@ -43,6 +46,7 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; +import java.util.UUID; /** * This service manage picture profile and sound profile for TV setting. Also communicates with the @@ -54,10 +58,14 @@ public class MediaQualityService extends SystemService { private static final String TAG = "MediaQualityService"; private final Context mContext; private final MediaQualityDbHelper mMediaQualityDbHelper; + private final BiMap<Long, String> mPictureProfileTempIdMap; + private final BiMap<Long, String> mSoundProfileTempIdMap; public MediaQualityService(Context context) { super(context); mContext = context; + mPictureProfileTempIdMap = HashBiMap.create(); + mSoundProfileTempIdMap = HashBiMap.create(); mMediaQualityDbHelper = new MediaQualityDbHelper(mContext); mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true); mMediaQualityDbHelper.setIdleConnectionTimeout(30); @@ -80,11 +88,14 @@ public class MediaQualityService extends SystemService { values.put(BaseParameters.PARAMETER_NAME, pp.getName()); values.put(BaseParameters.PARAMETER_PACKAGE, pp.getPackageName()); values.put(BaseParameters.PARAMETER_INPUT_ID, pp.getInputId()); - values.put(mMediaQualityDbHelper.SETTINGS, bundleToJson(pp.getParameters())); + values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(pp.getParameters())); // id is auto-generated by SQLite upon successful insertion of row - long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, null, values); - return new PictureProfile.Builder(pp).setProfileId(Long.toString(id)).build(); + Long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, + null, values); + populateTempIdMap(mPictureProfileTempIdMap, id); + pp.setProfileId(mPictureProfileTempIdMap.get(id)); + return pp; } @Override @@ -94,26 +105,27 @@ public class MediaQualityService extends SystemService { @Override public void removePictureProfile(String id, int userId) { - // TODO: implement + Long intId = mPictureProfileTempIdMap.inverse().get(id); + if (intId != null) { + SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); + String selection = BaseParameters.PARAMETER_ID + " = ?"; + String[] selectionArgs = {Long.toString(intId)}; + db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection, + selectionArgs); + mPictureProfileTempIdMap.remove(intId); + } } @Override public PictureProfile getPictureProfile(int type, String name, int userId) { - SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase(); - String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " + BaseParameters.PARAMETER_NAME + " = ?"; String[] selectionArguments = {Integer.toString(type), name}; try ( - Cursor cursor = db.query( + Cursor cursor = getCursorAfterQuerying( mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, - getAllPictureProfileColumns(), - selection, - selectionArguments, - /*groupBy=*/ null, - /*having=*/ null, - /*orderBy=*/ null) + getAllMediaProfileColumns(), selection, selectionArguments) ) { int count = cursor.getCount(); if (count == 0) { @@ -122,93 +134,19 @@ public class MediaQualityService extends SystemService { if (count > 1) { Log.wtf(TAG, String.format(Locale.US, "%d entries found for type=%d and name=%s" + " in %s. Should only ever be 0 or 1.", count, type, name, - mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME)); + mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME)); return null; } cursor.moveToFirst(); - return getPictureProfileFromCursor(cursor); - } - } - - private String bundleToJson(PersistableBundle bundle) { - JSONObject jsonObject = new JSONObject(); - if (bundle == null) { - return jsonObject.toString(); - } - for (String key : bundle.keySet()) { - try { - jsonObject.put(key, bundle.getString(key)); - } catch (JSONException e) { - Log.e(TAG, "Unable to serialize ", e); - } - } - return jsonObject.toString(); - } - - private PersistableBundle jsonToBundle(String jsonString) { - JSONObject jsonObject = null; - PersistableBundle bundle = new PersistableBundle(); - - try { - jsonObject = new JSONObject(jsonString); - - Iterator<String> keys = jsonObject.keys(); - while (keys.hasNext()) { - String key = keys.next(); - Object value = jsonObject.get(key); - - if (value instanceof String) { - bundle.putString(key, (String) value); - } else if (value instanceof Integer) { - bundle.putInt(key, (Integer) value); - } else if (value instanceof Boolean) { - bundle.putBoolean(key, (Boolean) value); - } else if (value instanceof Double) { - bundle.putDouble(key, (Double) value); - } else if (value instanceof Long) { - bundle.putLong(key, (Long) value); - } - } - } catch (JSONException e) { - throw new RuntimeException(e); + return getPictureProfileWithTempIdFromCursor(cursor); } - - return bundle; - } - - private String[] getAllPictureProfileColumns() { - return new String[]{ - BaseParameters.PARAMETER_ID, - BaseParameters.PARAMETER_TYPE, - BaseParameters.PARAMETER_NAME, - BaseParameters.PARAMETER_INPUT_ID, - BaseParameters.PARAMETER_PACKAGE, - mMediaQualityDbHelper.SETTINGS - }; - } - - private PictureProfile getPictureProfileFromCursor(Cursor cursor) { - String returnId = cursor.getString(cursor.getColumnIndexOrThrow( - BaseParameters.PARAMETER_ID)); - int type = cursor.getInt(cursor.getColumnIndexOrThrow( - BaseParameters.PARAMETER_TYPE)); - String name = cursor.getString(cursor.getColumnIndexOrThrow( - BaseParameters.PARAMETER_NAME)); - String inputId = cursor.getString(cursor.getColumnIndexOrThrow( - BaseParameters.PARAMETER_INPUT_ID)); - String packageName = cursor.getString(cursor.getColumnIndexOrThrow( - BaseParameters.PARAMETER_PACKAGE)); - String settings = cursor.getString( - cursor.getColumnIndexOrThrow(mMediaQualityDbHelper.SETTINGS)); - return new PictureProfile(returnId, type, name, inputId, - packageName, jsonToBundle(settings)); } @Override public List<PictureProfile> getPictureProfilesByPackage(String packageName, int userId) { String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; String[] selectionArguments = {packageName}; - return getPictureProfilesBasedOnConditions(getAllPictureProfileColumns(), selection, + return getPictureProfilesBasedOnConditions(getAllMediaProfileColumns(), selection, selectionArguments); } @@ -219,36 +157,15 @@ public class MediaQualityService extends SystemService { @Override public List<String> getPictureProfilePackageNames(int userId) { - String [] column = {BaseParameters.PARAMETER_NAME}; + String [] column = {BaseParameters.PARAMETER_PACKAGE}; List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column, null, null); return pictureProfiles.stream() - .map(PictureProfile::getName) + .map(PictureProfile::getPackageName) + .distinct() .collect(Collectors.toList()); } - private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns, - String selection, String[] selectionArguments) { - SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase(); - - try ( - Cursor cursor = db.query( - mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, - columns, - selection, - selectionArguments, - /*groupBy=*/ null, - /*having=*/ null, - /*orderBy=*/ null) - ) { - List<PictureProfile> pictureProfiles = new ArrayList<>(); - while (cursor.moveToNext()) { - pictureProfiles.add(getPictureProfileFromCursor(cursor)); - } - return pictureProfiles; - } - } - @Override public PictureProfileHandle getPictureProfileHandle(String id, int userId) { return null; @@ -259,13 +176,18 @@ public class MediaQualityService extends SystemService { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); + values.put(BaseParameters.PARAMETER_TYPE, sp.getProfileType()); values.put(BaseParameters.PARAMETER_NAME, sp.getName()); values.put(BaseParameters.PARAMETER_PACKAGE, sp.getPackageName()); values.put(BaseParameters.PARAMETER_INPUT_ID, sp.getInputId()); - values.put(mMediaQualityDbHelper.SETTINGS, bundleToJson(sp.getParameters())); + values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(sp.getParameters())); - long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values); - return new SoundProfile.Builder(sp).setProfileId(Long.toString(id)).build(); + // id is auto-generated by SQLite upon successful insertion of row + Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, + null, values); + populateTempIdMap(mSoundProfileTempIdMap, id); + sp.setProfileId(mSoundProfileTempIdMap.get(id)); + return sp; } @Override @@ -275,28 +197,27 @@ public class MediaQualityService extends SystemService { @Override public void removeSoundProfile(String id, int userId) { - SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); - String selection = BaseParameters.PARAMETER_ID + " = ?"; - String[] selectionArgs = {id}; - db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection, selectionArgs); + Long intId = mSoundProfileTempIdMap.inverse().get(id); + if (intId != null) { + SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); + String selection = BaseParameters.PARAMETER_ID + " = ?"; + String[] selectionArgs = {Long.toString(intId)}; + db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection, + selectionArgs); + mSoundProfileTempIdMap.remove(intId); + } } @Override public SoundProfile getSoundProfile(int type, String id, int userId) { - SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase(); - - String selection = BaseParameters.PARAMETER_ID + " = ?"; - String[] selectionArguments = {id}; + String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " + + BaseParameters.PARAMETER_NAME + " = ?"; + String[] selectionArguments = {String.valueOf(type), id}; try ( - Cursor cursor = db.query( + Cursor cursor = getCursorAfterQuerying( mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, - getAllSoundProfileColumns(), - selection, - selectionArguments, - /*groupBy=*/ null, - /*having=*/ null, - /*orderBy=*/ null) + getAllMediaProfileColumns(), selection, selectionArguments) ) { int count = cursor.getCount(); if (count == 0) { @@ -309,7 +230,7 @@ public class MediaQualityService extends SystemService { return null; } cursor.moveToFirst(); - return getSoundProfileFromCursor(cursor); + return getSoundProfileWithTempIdFromCursor(cursor); } } @@ -317,7 +238,7 @@ public class MediaQualityService extends SystemService { public List<SoundProfile> getSoundProfilesByPackage(String packageName, int userId) { String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; String[] selectionArguments = {packageName}; - return getSoundProfilesBasedOnConditions(getAllSoundProfileColumns(), selection, + return getSoundProfilesBasedOnConditions(getAllMediaProfileColumns(), selection, selectionArguments); } @@ -332,13 +253,79 @@ public class MediaQualityService extends SystemService { List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column, null, null); return soundProfiles.stream() - .map(SoundProfile::getName) + .map(SoundProfile::getPackageName) + .distinct() .collect(Collectors.toList()); } - private String[] getAllSoundProfileColumns() { + private void populateTempIdMap(BiMap<Long, String> map, Long id) { + if (id != null && map.get(id) == null) { + String uuid = UUID.randomUUID().toString(); + while (map.inverse().containsKey(uuid)) { + uuid = UUID.randomUUID().toString(); + } + map.put(id, uuid); + } + } + + private String persistableBundleToJson(PersistableBundle bundle) { + JSONObject json = new JSONObject(); + for (String key : bundle.keySet()) { + Object value = bundle.get(key); + try { + if (value instanceof String) { + json.put(key, bundle.getString(key)); + } else if (value instanceof Integer) { + json.put(key, bundle.getInt(key)); + } else if (value instanceof Long) { + json.put(key, bundle.getLong(key)); + } else if (value instanceof Boolean) { + json.put(key, bundle.getBoolean(key)); + } else if (value instanceof Double) { + json.put(key, bundle.getDouble(key)); + } + } catch (JSONException e) { + Log.e(TAG, "Unable to serialize ", e); + } + } + return json.toString(); + } + + private PersistableBundle jsonToBundle(String jsonString) { + PersistableBundle bundle = new PersistableBundle(); + if (jsonString != null) { + JSONObject jsonObject = null; + try { + jsonObject = new JSONObject(jsonString); + + Iterator<String> keys = jsonObject.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object value = jsonObject.get(key); + + if (value instanceof String) { + bundle.putString(key, (String) value); + } else if (value instanceof Integer) { + bundle.putInt(key, (Integer) value); + } else if (value instanceof Boolean) { + bundle.putBoolean(key, (Boolean) value); + } else if (value instanceof Double) { + bundle.putDouble(key, (Double) value); + } else if (value instanceof Long) { + bundle.putLong(key, (Long) value); + } + } + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + return bundle; + } + + private String[] getAllMediaProfileColumns() { return new String[]{ BaseParameters.PARAMETER_ID, + BaseParameters.PARAMETER_TYPE, BaseParameters.PARAMETER_NAME, BaseParameters.PARAMETER_INPUT_ID, BaseParameters.PARAMETER_PACKAGE, @@ -346,40 +333,92 @@ public class MediaQualityService extends SystemService { }; } - private SoundProfile getSoundProfileFromCursor(Cursor cursor) { - String returnId = cursor.getString( - cursor.getColumnIndexOrThrow(BaseParameters.PARAMETER_ID)); - int type = cursor.getInt( - cursor.getColumnIndexOrThrow(BaseParameters.PARAMETER_TYPE)); - String name = cursor.getString( - cursor.getColumnIndexOrThrow(BaseParameters.PARAMETER_NAME)); - String inputId = cursor.getString( - cursor.getColumnIndexOrThrow(BaseParameters.PARAMETER_INPUT_ID)); - String packageName = cursor.getString( - cursor.getColumnIndexOrThrow(BaseParameters.PARAMETER_PACKAGE)); - String settings = cursor.getString( - cursor.getColumnIndexOrThrow(mMediaQualityDbHelper.SETTINGS)); - return new SoundProfile(returnId, type, name, inputId, packageName, - jsonToBundle(settings)); + private PictureProfile getPictureProfileWithTempIdFromCursor(Cursor cursor) { + return new PictureProfile( + getTempId(mPictureProfileTempIdMap, cursor), + getType(cursor), + getName(cursor), + getInputId(cursor), + getPackageName(cursor), + jsonToBundle(getSettingsString(cursor)) + ); } - private List<SoundProfile> getSoundProfilesBasedOnConditions(String[] columns, - String selection, String[] selectionArguments) { + private SoundProfile getSoundProfileWithTempIdFromCursor(Cursor cursor) { + return new SoundProfile( + getTempId(mSoundProfileTempIdMap, cursor), + getType(cursor), + getName(cursor), + getInputId(cursor), + getPackageName(cursor), + jsonToBundle(getSettingsString(cursor)) + ); + } + + private String getTempId(BiMap<Long, String> map, Cursor cursor) { + int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID); + Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null; + populateTempIdMap(map, dbId); + return map.get(dbId); + } + + private int getType(Cursor cursor) { + int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_TYPE); + return colIndex != -1 ? cursor.getInt(colIndex) : 0; + } + + private String getName(Cursor cursor) { + int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_NAME); + return colIndex != -1 ? cursor.getString(colIndex) : null; + } + + private String getInputId(Cursor cursor) { + int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_INPUT_ID); + return colIndex != -1 ? cursor.getString(colIndex) : null; + } + + private String getPackageName(Cursor cursor) { + int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_PACKAGE); + return colIndex != -1 ? cursor.getString(colIndex) : null; + } + + private String getSettingsString(Cursor cursor) { + int colIndex = cursor.getColumnIndex(mMediaQualityDbHelper.SETTINGS); + return colIndex != -1 ? cursor.getString(colIndex) : null; + } + + private Cursor getCursorAfterQuerying(String table, String[] columns, String selection, + String[] selectionArgs) { SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase(); + return db.query(table, columns, selection, selectionArgs, + /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null); + } + private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns, + String selection, String[] selectionArguments) { try ( - Cursor cursor = db.query( - mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, - columns, - selection, - selectionArguments, - /*groupBy=*/ null, - /*having=*/ null, - /*orderBy=*/ null) + Cursor cursor = getCursorAfterQuerying( + mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, columns, selection, + selectionArguments) + ) { + List<PictureProfile> pictureProfiles = new ArrayList<>(); + while (cursor.moveToNext()) { + pictureProfiles.add(getPictureProfileWithTempIdFromCursor(cursor)); + } + return pictureProfiles; + } + } + + private List<SoundProfile> getSoundProfilesBasedOnConditions(String[] columns, + String selection, String[] selectionArguments) { + try ( + Cursor cursor = getCursorAfterQuerying( + mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, columns, selection, + selectionArguments) ) { List<SoundProfile> soundProfiles = new ArrayList<>(); while (cursor.moveToNext()) { - soundProfiles.add(getSoundProfileFromCursor(cursor)); + soundProfiles.add(getSoundProfileWithTempIdFromCursor(cursor)); } return soundProfiles; } diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java index 0a0882d80cc1..4ea405441030 100644 --- a/services/core/java/com/android/server/pm/PackageHandler.java +++ b/services/core/java/com/android/server/pm/PackageHandler.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; -import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION; import static com.android.server.pm.PackageManagerService.CHECK_PENDING_VERIFICATION; import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; import static com.android.server.pm.PackageManagerService.DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD; @@ -29,7 +28,6 @@ import static com.android.server.pm.PackageManagerService.DOMAIN_VERIFICATION; import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_STATUS; import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_TIMEOUT; import static com.android.server.pm.PackageManagerService.INSTANT_APP_RESOLUTION_PHASE_TWO; -import static com.android.server.pm.PackageManagerService.INTEGRITY_VERIFICATION_COMPLETE; import static com.android.server.pm.PackageManagerService.PACKAGE_VERIFIED; import static com.android.server.pm.PackageManagerService.POST_INSTALL; import static com.android.server.pm.PackageManagerService.PRUNE_UNUSED_STATIC_SHARED_LIBRARIES; @@ -149,42 +147,6 @@ final class PackageHandler extends Handler { break; } - case CHECK_PENDING_INTEGRITY_VERIFICATION: { - final int verificationId = msg.arg1; - final PackageVerificationState state = mPm.mPendingVerification.get(verificationId); - - if (state != null && !state.isIntegrityVerificationComplete()) { - final VerifyingSession verifyingSession = state.getVerifyingSession(); - final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile); - - String errorMsg = "Integrity verification timed out for " + originUri; - Slog.i(TAG, errorMsg); - - state.setIntegrityVerificationResult( - getDefaultIntegrityVerificationResponse()); - - if (getDefaultIntegrityVerificationResponse() - == PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW) { - Slog.i(TAG, "Integrity check times out, continuing with " + originUri); - } else { - verifyingSession.setReturnCode( - PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, - errorMsg); - } - - if (state.areAllVerificationsComplete()) { - mPm.mPendingVerification.remove(verificationId); - } - - Trace.asyncTraceEnd( - TRACE_TAG_PACKAGE_MANAGER, - "integrity_verification", - verificationId); - - verifyingSession.handleIntegrityVerificationFinished(); - } - break; - } case PACKAGE_VERIFIED: { final int verificationId = msg.arg1; @@ -205,42 +167,6 @@ final class PackageHandler extends Handler { break; } - case INTEGRITY_VERIFICATION_COMPLETE: { - final int verificationId = msg.arg1; - - final PackageVerificationState state = mPm.mPendingVerification.get(verificationId); - if (state == null) { - Slog.w(TAG, "Integrity verification with id " + verificationId - + " not found. It may be invalid or overridden by verifier"); - break; - } - - final int response = (Integer) msg.obj; - final VerifyingSession verifyingSession = state.getVerifyingSession(); - final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile); - - state.setIntegrityVerificationResult(response); - - if (response == PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW) { - Slog.i(TAG, "Integrity check passed for " + originUri); - } else { - verifyingSession.setReturnCode( - PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, - "Integrity check failed for " + originUri); - } - - if (state.areAllVerificationsComplete()) { - mPm.mPendingVerification.remove(verificationId); - } - - Trace.asyncTraceEnd( - TRACE_TAG_PACKAGE_MANAGER, - "integrity_verification", - verificationId); - - verifyingSession.handleIntegrityVerificationFinished(); - break; - } case INSTANT_APP_RESOLUTION_PHASE_TWO: { InstantAppResolver.doInstantAppResolutionPhaseTwo(mPm.mContext, mPm.snapshotComputer(), diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ab26f024a18a..65bb701563a8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -923,8 +923,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService static final int ENABLE_ROLLBACK_TIMEOUT = 22; static final int DEFERRED_NO_KILL_POST_DELETE = 23; static final int DEFERRED_NO_KILL_INSTALL_OBSERVER = 24; - static final int INTEGRITY_VERIFICATION_COMPLETE = 25; - static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26; + // static final int UNUSED = 25; + // static final int UNUSED = 26; static final int DOMAIN_VERIFICATION = 27; static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28; static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER = 29; @@ -7124,12 +7124,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService return mSettings.isPermissionUpgradeNeeded(userId); } + @Deprecated @Override public void setIntegrityVerificationResult(int verificationId, int verificationResult) { - final Message msg = mHandler.obtainMessage(INTEGRITY_VERIFICATION_COMPLETE); - msg.arg1 = verificationId; - msg.obj = verificationResult; - mHandler.sendMessage(msg); + // Do nothing. } @Override diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java index 0b6ccc41d956..63c2ee2e5454 100644 --- a/services/core/java/com/android/server/pm/PackageVerificationState.java +++ b/services/core/java/com/android/server/pm/PackageVerificationState.java @@ -43,8 +43,6 @@ class PackageVerificationState { private boolean mRequiredVerificationPassed; - private boolean mIntegrityVerificationComplete; - /** * Create a new package verification state where {@code requiredVerifierUid} is the user ID for * the package that must reply affirmative before things can continue. @@ -213,15 +211,7 @@ class PackageVerificationState { return mExtendedTimeoutUids.get(uid, false); } - void setIntegrityVerificationResult(int code) { - mIntegrityVerificationComplete = true; - } - - boolean isIntegrityVerificationComplete() { - return mIntegrityVerificationComplete; - } - boolean areAllVerificationsComplete() { - return mIntegrityVerificationComplete && isVerificationComplete(); + return isVerificationComplete(); } } diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index f7eb29fe3ee9..542ae8eb9207 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -28,7 +28,6 @@ import static android.os.PowerWhitelistManager.REASON_PACKAGE_VERIFIER; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; -import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION; import static com.android.server.pm.PackageManagerService.CHECK_PENDING_VERIFICATION; import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY; @@ -87,11 +86,6 @@ final class VerifyingSession { * Whether verification is enabled by default. */ private static final boolean DEFAULT_VERIFY_ENABLE = true; - - /** - * Whether integrity verification is enabled by default. - */ - private static final boolean DEFAULT_INTEGRITY_VERIFY_ENABLE = true; /** * The default maximum time to wait for the integrity verification to return in * milliseconds. @@ -129,7 +123,6 @@ final class VerifyingSession { private final boolean mUserActionRequired; private final int mUserActionRequiredType; private boolean mWaitForVerificationToComplete; - private boolean mWaitForIntegrityVerificationToComplete; private boolean mWaitForEnableRollbackToComplete; private int mRet = PackageManager.INSTALL_SUCCEEDED; private String mErrorMessage = null; @@ -217,7 +210,6 @@ final class VerifyingSession { new PackageVerificationState(this); mPm.mPendingVerification.append(verificationId, verificationState); - sendIntegrityVerificationRequest(verificationId, pkgLite, verificationState); sendPackageVerificationRequest( verificationId, pkgLite, verificationState); @@ -270,89 +262,6 @@ final class VerifyingSession { mPm.mHandler.sendMessageDelayed(msg, rollbackTimeout); } - /** - * Send a request to check the integrity of the package. - */ - void sendIntegrityVerificationRequest( - int verificationId, - PackageInfoLite pkgLite, - PackageVerificationState verificationState) { - if (!isIntegrityVerificationEnabled()) { - // Consider the integrity check as passed. - verificationState.setIntegrityVerificationResult( - PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); - return; - } - - final Intent integrityVerification = - new Intent(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION); - - integrityVerification.setDataAndType(Uri.fromFile(new File(mOriginInfo.mResolvedPath)), - PACKAGE_MIME_TYPE); - - final int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_RECEIVER_REGISTERED_ONLY - | Intent.FLAG_RECEIVER_FOREGROUND; - integrityVerification.addFlags(flags); - - integrityVerification.putExtra(EXTRA_VERIFICATION_ID, verificationId); - integrityVerification.putExtra(EXTRA_PACKAGE_NAME, pkgLite.packageName); - integrityVerification.putExtra(EXTRA_VERSION_CODE, pkgLite.versionCode); - integrityVerification.putExtra(EXTRA_LONG_VERSION_CODE, pkgLite.getLongVersionCode()); - populateInstallerExtras(integrityVerification); - - // send to integrity component only. - integrityVerification.setPackage("android"); - - final BroadcastOptions options = BroadcastOptions.makeBasic(); - - mPm.mContext.sendOrderedBroadcastAsUser(integrityVerification, UserHandle.SYSTEM, - /* receiverPermission= */ null, - /* appOp= */ AppOpsManager.OP_NONE, - /* options= */ options.toBundle(), - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final Message msg = - mPm.mHandler.obtainMessage(CHECK_PENDING_INTEGRITY_VERIFICATION); - msg.arg1 = verificationId; - mPm.mHandler.sendMessageDelayed(msg, getIntegrityVerificationTimeout()); - } - }, /* scheduler= */ null, - /* initialCode= */ 0, - /* initialData= */ null, - /* initialExtras= */ null); - - Trace.asyncTraceBegin( - TRACE_TAG_PACKAGE_MANAGER, "integrity_verification", verificationId); - - // stop the copy until verification succeeds. - mWaitForIntegrityVerificationToComplete = true; - } - - - /** - * Get the integrity verification timeout. - * - * @return verification timeout in milliseconds - */ - private long getIntegrityVerificationTimeout() { - long timeout = Settings.Global.getLong(mPm.mContext.getContentResolver(), - Settings.Global.APP_INTEGRITY_VERIFICATION_TIMEOUT, - DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT); - // The setting can be used to increase the timeout but not decrease it, since that is - // equivalent to disabling the integrity component. - return Math.max(timeout, DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT); - } - - /** - * Check whether or not integrity verification has been enabled. - */ - private boolean isIntegrityVerificationEnabled() { - // We are not exposing this as a user-configurable setting because we don't want to provide - // an easy way to get around the integrity check. - return DEFAULT_INTEGRITY_VERIFY_ENABLE; - } /** * Send a request to verifier(s) to verify the package if necessary. @@ -827,11 +736,6 @@ final class VerifyingSession { handleReturnCode(); } - void handleIntegrityVerificationFinished() { - mWaitForIntegrityVerificationToComplete = false; - handleReturnCode(); - } - void handleRollbackEnabled() { // TODO(b/112431924): Consider halting the install if we // couldn't enable rollback. @@ -840,7 +744,7 @@ final class VerifyingSession { } void handleReturnCode() { - if (mWaitForVerificationToComplete || mWaitForIntegrityVerificationToComplete + if (mWaitForVerificationToComplete || mWaitForEnableRollbackToComplete) { return; } diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 0f6688f73c22..f1f1e6031718 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -41,6 +41,7 @@ import android.hardware.power.GpuHeadroomResult; import android.hardware.power.IPower; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; +import android.hardware.power.SupportInfo; import android.hardware.power.WorkDuration; import android.os.Binder; import android.os.CpuHeadroomParamsInternal; @@ -103,7 +104,6 @@ public final class HintManagerService extends SystemService { // The minimum interval between the headroom calls as rate limiting. private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000; private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000; - private static final int HEADROOM_INTERVAL_UNSUPPORTED = -1; @VisibleForTesting final long mHintSessionPreferredRate; @@ -181,6 +181,7 @@ public final class HintManagerService extends SystemService { private final IPower mPowerHal; private int mPowerHalVersion; + private SupportInfo mSupportInfo = null; private final PackageManager mPackageManager; private boolean mUsesFmq; @@ -248,13 +249,11 @@ public final class HintManagerService extends SystemService { @GuardedBy("mCpuHeadroomLock") private final HeadroomCache<CpuHeadroomParams, CpuHeadroomResult> mCpuHeadroomCache; - private final long mCpuHeadroomIntervalMillis; private final Object mGpuHeadroomLock = new Object(); @GuardedBy("mGpuHeadroomLock") private final HeadroomCache<GpuHeadroomParams, GpuHeadroomResult> mGpuHeadroomCache; - private final long mGpuHeadroomIntervalMillis; // these are set to default values in CpuHeadroomParamsInternal and GpuHeadroomParamsInternal private final int mDefaultCpuHeadroomCalculationWindowMillis; @@ -296,79 +295,70 @@ public final class HintManagerService extends SystemService { mPowerHal = injector.createIPower(); mPowerHalVersion = 0; mUsesFmq = false; - long cpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED; - long gpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED; if (mPowerHal != null) { - try { - mPowerHalVersion = mPowerHal.getInterfaceVersion(); - if (mPowerHal.getInterfaceVersion() >= 6) { - if (SystemProperties.getBoolean(PROPERTY_USE_HAL_HEADROOMS, true)) { - cpuHeadroomIntervalMillis = checkCpuHeadroomSupport(); - gpuHeadroomIntervalMillis = checkGpuHeadroomSupport(); - } - } - } catch (RemoteException e) { - throw new IllegalStateException("Could not contact PowerHAL!", e); - } + mSupportInfo = getSupportInfo(); } - mCpuHeadroomIntervalMillis = cpuHeadroomIntervalMillis; mDefaultCpuHeadroomCalculationWindowMillis = new CpuHeadroomParamsInternal().calculationWindowMillis; mDefaultGpuHeadroomCalculationWindowMillis = new GpuHeadroomParamsInternal().calculationWindowMillis; - mGpuHeadroomIntervalMillis = gpuHeadroomIntervalMillis; - if (mCpuHeadroomIntervalMillis > 0) { - mCpuHeadroomCache = new HeadroomCache<>(2, mCpuHeadroomIntervalMillis); + if (mSupportInfo.headroom.isCpuSupported) { + mCpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.cpuMinIntervalMillis); } else { mCpuHeadroomCache = null; } - if (mGpuHeadroomIntervalMillis > 0) { - mGpuHeadroomCache = new HeadroomCache<>(2, mGpuHeadroomIntervalMillis); + if (mSupportInfo.headroom.isGpuSupported) { + mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis); } else { mGpuHeadroomCache = null; } } - private long checkCpuHeadroomSupport() { - final CpuHeadroomParams params = new CpuHeadroomParams(); - params.tids = new int[]{Process.myPid()}; - try { - synchronized (mCpuHeadroomLock) { - final CpuHeadroomResult ret = mPowerHal.getCpuHeadroom(params); - if (ret != null && ret.getTag() == CpuHeadroomResult.globalHeadroom - && !Float.isNaN(ret.getGlobalHeadroom())) { - return Math.max( - DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS, - mPowerHal.getCpuHeadroomMinIntervalMillis()); - } - } - - } catch (UnsupportedOperationException e) { - Slog.w(TAG, "getCpuHeadroom HAL API is not supported, params: " + params, e); - } catch (RemoteException e) { - Slog.e(TAG, "getCpuHeadroom HAL API fails, disabling the API, params: " + params, e); - } - return HEADROOM_INTERVAL_UNSUPPORTED; - } - - private long checkGpuHeadroomSupport() { - final GpuHeadroomParams params = new GpuHeadroomParams(); + SupportInfo getSupportInfo() { try { - synchronized (mGpuHeadroomLock) { - final GpuHeadroomResult ret = mPowerHal.getGpuHeadroom(params); - if (ret != null && ret.getTag() == GpuHeadroomResult.globalHeadroom && !Float.isNaN( - ret.getGlobalHeadroom())) { - return Math.max( - DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS, - mPowerHal.getGpuHeadroomMinIntervalMillis()); - } + mPowerHalVersion = mPowerHal.getInterfaceVersion(); + if (mPowerHalVersion >= 6) { + return mPowerHal.getSupportInfo(); } - } catch (UnsupportedOperationException e) { - Slog.w(TAG, "getGpuHeadroom HAL API is not supported, params: " + params, e); } catch (RemoteException e) { - Slog.e(TAG, "getGpuHeadroom HAL API fails, disabling the API, params: " + params, e); - } - return HEADROOM_INTERVAL_UNSUPPORTED; + throw new IllegalStateException("Could not contact PowerHAL!", e); + } + + SupportInfo supportInfo = new SupportInfo(); + supportInfo.usesSessions = isHintSessionSupported(); + // Global boosts & modes aren't currently relevant for HMS clients + supportInfo.boosts = 0; + supportInfo.modes = 0; + supportInfo.sessionHints = 0; + supportInfo.sessionModes = 0; + supportInfo.sessionTags = 0; + if (isHintSessionSupported()) { + if (mPowerHalVersion == 4) { + // Assume we support the V4 hints & modes unless specified + // otherwise; this is to avoid breaking backwards compat + // since we historically just assumed they were. + supportInfo.sessionHints = 31; // first 5 bits are ones + } + if (mPowerHalVersion == 5) { + // Assume we support the V5 hints & modes unless specified + // otherwise; this is to avoid breaking backwards compat + // since we historically just assumed they were. + + // Hal V5 has 8 modes, all of which it assumes are supported, + // so we represent that by having the first 8 bits set + supportInfo.sessionHints = 255; // first 8 bits are ones + // Hal V5 has 1 mode which it assumes is supported, so we + // represent that by having the first bit set + supportInfo.sessionModes = 1; + // Hal V5 has 5 tags, all of which it assumes are supported, + // so we represent that by having the first 5 bits set + supportInfo.sessionTags = 31; + } + } + supportInfo.headroom = new SupportInfo.HeadroomSupportInfo(); + supportInfo.headroom.isCpuSupported = false; + supportInfo.headroom.isGpuSupported = false; + return supportInfo; } private ServiceThread createCleanUpThread() { @@ -557,7 +547,7 @@ public final class HintManagerService extends SystemService { return targetDurations; } } - private boolean isHalSupported() { + private boolean isHintSessionSupported() { return mHintSessionPreferredRate != -1; } @@ -1267,7 +1257,7 @@ public final class HintManagerService extends SystemService { public IHintSession createHintSessionWithConfig(@NonNull IBinder token, @SessionTag int tag, SessionCreationConfig creationConfig, SessionConfig config) { - if (!isHalSupported()) { + if (!isHintSessionSupported()) { throw new UnsupportedOperationException("PowerHAL is not supported!"); } @@ -1488,14 +1478,13 @@ public final class HintManagerService extends SystemService { @Override public CpuHeadroomResult getCpuHeadroom(@NonNull CpuHeadroomParamsInternal params) { - if (mCpuHeadroomIntervalMillis <= 0) { + if (!mSupportInfo.headroom.isCpuSupported) { throw new UnsupportedOperationException(); } final CpuHeadroomParams halParams = new CpuHeadroomParams(); halParams.tids = new int[]{Binder.getCallingPid()}; halParams.calculationType = params.calculationType; halParams.calculationWindowMillis = params.calculationWindowMillis; - halParams.selectionType = params.selectionType; if (params.usesDeviceHeadroom) { halParams.tids = new int[]{}; } else if (params.tids != null && params.tids.length > 0) { @@ -1544,7 +1533,7 @@ public final class HintManagerService extends SystemService { @Override public GpuHeadroomResult getGpuHeadroom(@NonNull GpuHeadroomParamsInternal params) { - if (mGpuHeadroomIntervalMillis <= 0) { + if (!mSupportInfo.headroom.isGpuSupported) { throw new UnsupportedOperationException(); } final GpuHeadroomParams halParams = new GpuHeadroomParams(); @@ -1579,18 +1568,18 @@ public final class HintManagerService extends SystemService { @Override public long getCpuHeadroomMinIntervalMillis() { - if (mCpuHeadroomIntervalMillis <= 0) { + if (!mSupportInfo.headroom.isCpuSupported) { throw new UnsupportedOperationException(); } - return mCpuHeadroomIntervalMillis; + return mSupportInfo.headroom.cpuMinIntervalMillis; } @Override public long getGpuHeadroomMinIntervalMillis() { - if (mGpuHeadroomIntervalMillis <= 0) { + if (!mSupportInfo.headroom.isGpuSupported) { throw new UnsupportedOperationException(); } - return mGpuHeadroomIntervalMillis; + return mSupportInfo.headroom.gpuMinIntervalMillis; } @Override @@ -1609,7 +1598,7 @@ public final class HintManagerService extends SystemService { } pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate); pw.println("MaxGraphicsPipelineThreadsCount: " + MAX_GRAPHICS_PIPELINE_THREADS_COUNT); - pw.println("HAL Support: " + isHalSupported()); + pw.println("HAL Support: " + isHintSessionSupported()); pw.println("Active Sessions:"); synchronized (mLock) { for (int i = 0; i < mActiveSessions.size(); i++) { @@ -1625,20 +1614,13 @@ public final class HintManagerService extends SystemService { } } } - pw.println("CPU Headroom Interval: " + mCpuHeadroomIntervalMillis); - pw.println("GPU Headroom Interval: " + mGpuHeadroomIntervalMillis); + pw.println("CPU Headroom Interval: " + mSupportInfo.headroom.cpuMinIntervalMillis); + pw.println("GPU Headroom Interval: " + mSupportInfo.headroom.gpuMinIntervalMillis); try { CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); - params.selectionType = CpuHeadroomParams.SelectionType.ALL; params.usesDeviceHeadroom = true; CpuHeadroomResult ret = getCpuHeadroom(params); pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom())); - params = new CpuHeadroomParamsInternal(); - params.selectionType = CpuHeadroomParams.SelectionType.PER_CORE; - params.usesDeviceHeadroom = true; - ret = getCpuHeadroom(params); - pw.println("CPU headroom per core: " + (ret == null ? "N/A" - : Arrays.toString(ret.getPerCoreHeadroom()))); } catch (Exception e) { Slog.d(TAG, "Failed to dump CPU headroom", e); pw.println("CPU headroom: N/A"); diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java index 15c3099511f9..1b6ce9dacfa9 100644 --- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java @@ -119,6 +119,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override public void finishSession() { + Slog.d(TAG, "Session finish requested, ending vibration session..."); // Do not abort session in HAL, wait for ongoing vibration requests to complete. // This might take a while to end the session, but it can be aborted by cancelSession. requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true); @@ -126,6 +127,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override public void cancelSession() { + Slog.d(TAG, "Session cancel requested, aborting vibration session..."); // Always abort session in HAL while cancelling it. // This might be triggered after finishSession was already called. requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true, @@ -228,13 +230,14 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override public void notifySessionCallback() { synchronized (mLock) { + Slog.d(TAG, "Session callback received, ending vibration session..."); // If end was not requested then the HAL has cancelled the session. maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON, /* isVendorRequest= */ false); maybeSetStatusToRequestedLocked(); clearVibrationConductor(); + mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId)); } - mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId)); } @Override diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java index a93e8ad93756..97f1bd46678f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java @@ -574,57 +574,16 @@ public class PackageVerificationStateTest extends AndroidTestCase { assertTrue(state.isInstallAllowed()); } - public void testAreAllVerificationsComplete_onlyVerificationPasses() { + public void testAreAllVerificationsComplete() { PackageVerificationState state = new PackageVerificationState(null); state.addRequiredVerifierUid(REQUIRED_UID_1); assertFalse(state.areAllVerificationsComplete()); state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW); - assertFalse(state.areAllVerificationsComplete()); - } - - public void testAreAllVerificationsComplete_onlyIntegrityCheckPasses() { - PackageVerificationState state = new PackageVerificationState(null); - state.addRequiredVerifierUid(REQUIRED_UID_1); - assertFalse(state.areAllVerificationsComplete()); - - state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); - - assertFalse(state.areAllVerificationsComplete()); - } - - public void testAreAllVerificationsComplete_bothPasses() { - PackageVerificationState state = new PackageVerificationState(null); - state.addRequiredVerifierUid(REQUIRED_UID_1); - assertFalse(state.areAllVerificationsComplete()); - - state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); - state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW); - assertTrue(state.areAllVerificationsComplete()); } - public void testAreAllVerificationsComplete_onlyVerificationFails() { - PackageVerificationState state = new PackageVerificationState(null); - state.addRequiredVerifierUid(REQUIRED_UID_1); - assertFalse(state.areAllVerificationsComplete()); - - state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_REJECT); - - assertFalse(state.areAllVerificationsComplete()); - } - - public void testAreAllVerificationsComplete_onlyIntegrityCheckFails() { - PackageVerificationState state = new PackageVerificationState(null); - state.addRequiredVerifierUid(REQUIRED_UID_1); - assertFalse(state.areAllVerificationsComplete()); - - state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT); - - assertFalse(state.areAllVerificationsComplete()); - } - private void processOnTimeout(PackageVerificationState state, int code, int uid) { // CHECK_PENDING_VERIFICATION handler. assertFalse("Verification should not be marked as complete yet", diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java index 313c01d5ce58..a9b4ca1e7963 100644 --- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -58,6 +58,7 @@ import android.hardware.power.GpuHeadroomResult; import android.hardware.power.IPower; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; +import android.hardware.power.SupportInfo; import android.hardware.power.WorkDuration; import android.os.Binder; import android.os.CpuHeadroomParamsInternal; @@ -159,6 +160,7 @@ public class HintManagerServiceTest { private HintManagerService mService; private ChannelConfig mConfig; + private SupportInfo mSupportInfo; private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { return new Answer<Long>() { @@ -179,6 +181,18 @@ public class HintManagerServiceTest { mConfig.eventFlagDescriptor = new MQDescriptor<Byte, Byte>(); ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.category = ApplicationInfo.CATEGORY_GAME; + mSupportInfo = new SupportInfo(); + mSupportInfo.usesSessions = true; + mSupportInfo.sessionHints = 5; + mSupportInfo.sessionModes = 1; + mSupportInfo.modes = 3; + mSupportInfo.boosts = 3; + mSupportInfo.sessionTags = 63; + mSupportInfo.headroom = new SupportInfo.HeadroomSupportInfo(); + mSupportInfo.headroom.isCpuSupported = true; + mSupportInfo.headroom.cpuMinIntervalMillis = 2000; + mSupportInfo.headroom.isGpuSupported = true; + mSupportInfo.headroom.gpuMinIntervalMillis = 2000; when(mContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME); when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt())) @@ -205,6 +219,7 @@ public class HintManagerServiceTest { SESSION_IDS[2])); when(mIPowerMock.getInterfaceVersion()).thenReturn(6); + when(mIPowerMock.getSupportInfo()).thenReturn(mSupportInfo); when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock); @@ -1247,28 +1262,22 @@ public class HintManagerServiceTest { @Test public void testCpuHeadroomCache() throws Exception { - when(mIPowerMock.getCpuHeadroomMinIntervalMillis()).thenReturn(2000L); CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal(); CpuHeadroomParams halParams1 = new CpuHeadroomParams(); halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN; - halParams1.selectionType = CpuHeadroomParams.SelectionType.ALL; halParams1.tids = new int[]{Process.myPid()}; CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal(); params2.usesDeviceHeadroom = true; params2.calculationType = CpuHeadroomParams.CalculationType.MIN; - params2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE; CpuHeadroomParams halParams2 = new CpuHeadroomParams(); halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN; - halParams2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE; halParams2.tids = new int[]{}; CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal(); params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; - params3.selectionType = CpuHeadroomParams.SelectionType.ALL; CpuHeadroomParams halParams3 = new CpuHeadroomParams(); halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; - halParams3.selectionType = CpuHeadroomParams.SelectionType.ALL; halParams3.tids = new int[]{Process.myPid()}; // this params should not be cached as the window is not default @@ -1276,15 +1285,14 @@ public class HintManagerServiceTest { params4.calculationWindowMillis = 123; CpuHeadroomParams halParams4 = new CpuHeadroomParams(); halParams4.calculationType = CpuHeadroomParams.CalculationType.MIN; - halParams4.selectionType = CpuHeadroomParams.SelectionType.ALL; halParams4.calculationWindowMillis = 123; halParams4.tids = new int[]{Process.myPid()}; float headroom1 = 0.1f; CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1); when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1); - float[] headroom2 = new float[] {0.2f, 0.2f}; - CpuHeadroomResult halRet2 = CpuHeadroomResult.perCoreHeadroom(headroom2); + float headroom2 = 0.2f; + CpuHeadroomResult halRet2 = CpuHeadroomResult.globalHeadroom(headroom2); when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(halRet2); float headroom3 = 0.3f; CpuHeadroomResult halRet3 = CpuHeadroomResult.globalHeadroom(headroom3); @@ -1296,8 +1304,6 @@ public class HintManagerServiceTest { HintManagerService service = createService(); clearInvocations(mIPowerMock); - service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis(); - verify(mIPowerMock, times(0)).getCpuHeadroomMinIntervalMillis(); assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1)); verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1)); assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2)); @@ -1348,7 +1354,6 @@ public class HintManagerServiceTest { @Test public void testGpuHeadroomCache() throws Exception { - when(mIPowerMock.getGpuHeadroomMinIntervalMillis()).thenReturn(2000L); GpuHeadroomParamsInternal params1 = new GpuHeadroomParamsInternal(); GpuHeadroomParams halParams1 = new GpuHeadroomParams(); halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN; @@ -1369,8 +1374,6 @@ public class HintManagerServiceTest { HintManagerService service = createService(); clearInvocations(mIPowerMock); - service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis(); - verify(mIPowerMock, times(0)).getGpuHeadroomMinIntervalMillis(); assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1)); assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2)); verify(mIPowerMock, times(2)).getGpuHeadroom(any()); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index 9cd3186f99f3..605fed09dd9f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; @@ -92,6 +93,8 @@ public class AuthServiceTest { private static final String TEST_OP_PACKAGE_NAME = "test_package"; + private final @UserIdInt int mUserId = UserHandle.getCallingUserId(); + private AuthService mAuthService; @Rule @@ -257,12 +260,11 @@ public class AuthServiceTest { final Binder token = new Binder(); final PromptInfo promptInfo = new PromptInfo(); final long sessionId = 0; - final int userId = 0; mAuthService.mImpl.authenticate( token, sessionId, - userId, + mUserId, mReceiver, TEST_OP_PACKAGE_NAME, promptInfo); @@ -270,7 +272,7 @@ public class AuthServiceTest { verify(mBiometricService).authenticate( eq(token), eq(sessionId), - eq(userId), + eq(mUserId), eq(mReceiver), eq(TEST_OP_PACKAGE_NAME), eq(promptInfo)); @@ -286,12 +288,11 @@ public class AuthServiceTest { final Binder token = new Binder(); final PromptInfo promptInfo = new PromptInfo(); final long sessionId = 0; - final int userId = 0; mAuthService.mImpl.authenticate( token, sessionId, - userId, + mUserId, mReceiver, TEST_OP_PACKAGE_NAME, promptInfo); @@ -299,7 +300,7 @@ public class AuthServiceTest { verify(mBiometricService, never()).authenticate( eq(token), eq(sessionId), - eq(userId), + eq(mUserId), eq(mReceiver), eq(TEST_OP_PACKAGE_NAME), eq(promptInfo)); @@ -313,12 +314,11 @@ public class AuthServiceTest { final PromptInfo promptInfo = new PromptInfo(); final long sessionId = 0; - final int userId = 0; mAuthService.mImpl.authenticate( null /* token */, sessionId, - userId, + mUserId, mReceiver, TEST_OP_PACKAGE_NAME, promptInfo); @@ -338,7 +338,7 @@ public class AuthServiceTest { mAuthService.mImpl.authenticate( token, 0, /* sessionId */ - 0, /* userId */ + mUserId, mReceiver, TEST_OP_PACKAGE_NAME, new PromptInfo()); @@ -356,7 +356,7 @@ public class AuthServiceTest { mAuthService.mImpl.authenticate( token, 0, /* sessionId */ - 0, /* userId */ + mUserId, mReceiver, TEST_OP_PACKAGE_NAME, new PromptInfo()); @@ -414,20 +414,19 @@ public class AuthServiceTest { mAuthService = new AuthService(mContext, mInjector); mAuthService.onStart(); - final int userId = 0; final int expectedResult = BIOMETRIC_SUCCESS; final int authenticators = 0; when(mBiometricService.canAuthenticate(anyString(), anyInt(), anyInt(), anyInt())) .thenReturn(expectedResult); final int result = mAuthService.mImpl - .canAuthenticate(TEST_OP_PACKAGE_NAME, userId, authenticators); + .canAuthenticate(TEST_OP_PACKAGE_NAME, mUserId, authenticators); assertEquals(expectedResult, result); waitForIdle(); verify(mBiometricService).canAuthenticate( eq(TEST_OP_PACKAGE_NAME), - eq(userId), + eq(mUserId), eq(UserHandle.getCallingUserId()), eq(authenticators)); } @@ -440,18 +439,17 @@ public class AuthServiceTest { mAuthService = new AuthService(mContext, mInjector); mAuthService.onStart(); - final int userId = 0; final boolean expectedResult = true; when(mBiometricService.hasEnrolledBiometrics(anyInt(), anyString())).thenReturn( expectedResult); - final boolean result = mAuthService.mImpl.hasEnrolledBiometrics(userId, + final boolean result = mAuthService.mImpl.hasEnrolledBiometrics(mUserId, TEST_OP_PACKAGE_NAME); assertEquals(expectedResult, result); waitForIdle(); verify(mBiometricService).hasEnrolledBiometrics( - eq(userId), + eq(mUserId), eq(TEST_OP_PACKAGE_NAME)); } @@ -528,13 +526,12 @@ public class AuthServiceTest { mAuthService = new AuthService(mContext, mInjector); mAuthService.onStart(); - final int userId = 0; final int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG; - mAuthService.mImpl.getLastAuthenticationTime(userId, authenticators); + mAuthService.mImpl.getLastAuthenticationTime(mUserId, authenticators); waitForIdle(); - verify(mBiometricService).getLastAuthenticationTime(eq(userId), eq(authenticators)); + verify(mBiometricService).getLastAuthenticationTime(eq(mUserId), eq(authenticators)); } private static void setInternalAndTestBiometricPermissions( diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java deleted file mode 100644 index fd221185bacf..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity; - -import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS; -import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE; -import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS; -import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED; -import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID; -import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE; -import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.IntentSender; -import android.content.integrity.AppInstallMetadata; -import android.content.integrity.AtomicFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; -import android.content.pm.ParceledListSlice; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Handler; -import android.os.Message; -import android.provider.Settings; - -import androidx.test.InstrumentationRegistry; - -import com.android.internal.R; -import com.android.server.compat.PlatformCompat; -import com.android.server.testutils.TestUtils; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -/** Unit test for {@link com.android.server.integrity.AppIntegrityManagerServiceImpl} */ -@RunWith(JUnit4.class) -public class AppIntegrityManagerServiceImplTest { - private static final String TEST_APP_PATH = - "AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk"; - - private static final String TEST_APP_TWO_CERT_PATH = - "AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk"; - - private static final String TEST_APP_SOURCE_STAMP_PATH = - "AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk"; - - private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; - private static final String VERSION = "version"; - private static final String TEST_FRAMEWORK_PACKAGE = "com.android.frameworks.servicestests"; - - private static final String PACKAGE_NAME = "com.test.app"; - - private static final long VERSION_CODE = 100; - private static final String INSTALLER = "com.long.random.test.installer.name"; - - // These are obtained by running the test and checking logcat. - private static final String APP_CERT = - "F14CFECF5070874C05D3D2FA98E046BE20BDE02A0DC74BAF6B59C6A0E4C06850"; - // We use SHA256 for package names longer than 32 characters. - private static final String INSTALLER_SHA256 = - "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227"; - private static final String SOURCE_STAMP_CERTIFICATE_HASH = - "C6E737809CEF2B08CC6694892215F82A5E8FBC3C2A0F6212770310B90622D2D9"; - - private static final String DUMMY_APP_TWO_CERTS_CERT_1 = - "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94"; - private static final String DUMMY_APP_TWO_CERTS_CERT_2 = - "94366E0A80F3A3F0D8171A15760B88E228CD6E1101F0414C98878724FBE70147"; - - private static final String PLAY_STORE_PKG = "com.android.vending"; - private static final String ADB_INSTALLER = "adb"; - private static final String PLAY_STORE_CERT = "play_store_cert"; - - @org.junit.Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - - @Mock PackageManagerInternal mPackageManagerInternal; - @Mock PlatformCompat mPlatformCompat; - @Mock Context mMockContext; - @Mock Resources mMockResources; - @Mock Handler mHandler; - - private final Context mRealContext = InstrumentationRegistry.getTargetContext(); - - private PackageManager mSpyPackageManager; - private File mTestApk; - private File mTestApkTwoCerts; - private File mTestApkSourceStamp; - - // under test - private AppIntegrityManagerServiceImpl mService; - - @Before - public void setup() throws Exception { - mTestApk = File.createTempFile("AppIntegrity", ".apk"); - try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_PATH)) { - Files.copy(inputStream, mTestApk.toPath(), REPLACE_EXISTING); - } - - mTestApkTwoCerts = File.createTempFile("AppIntegrityTwoCerts", ".apk"); - try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_TWO_CERT_PATH)) { - Files.copy(inputStream, mTestApkTwoCerts.toPath(), REPLACE_EXISTING); - } - - mTestApkSourceStamp = File.createTempFile("AppIntegritySourceStamp", ".apk"); - try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_SOURCE_STAMP_PATH)) { - Files.copy(inputStream, mTestApkSourceStamp.toPath(), REPLACE_EXISTING); - } - - mService = - new AppIntegrityManagerServiceImpl( - mMockContext, - mPackageManagerInternal, - mHandler); - - mSpyPackageManager = spy(mRealContext.getPackageManager()); - // setup mocks to prevent NPE - when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager); - when(mMockContext.getResources()).thenReturn(mMockResources); - when(mMockResources.getStringArray(anyInt())).thenReturn(new String[] {}); - // These are needed to override the Settings.Global.get result. - when(mMockContext.getContentResolver()).thenReturn(mRealContext.getContentResolver()); - setIntegrityCheckIncludesRuleProvider(true); - } - - @After - public void tearDown() throws Exception { - mTestApk.delete(); - mTestApkTwoCerts.delete(); - mTestApkSourceStamp.delete(); - } - - @Test - public void broadcastReceiverRegistration() throws Exception { - allowlistUsAsRuleProvider(); - makeUsSystemApp(); - ArgumentCaptor<IntentFilter> intentFilterCaptor = - ArgumentCaptor.forClass(IntentFilter.class); - - verify(mMockContext).registerReceiver(any(), intentFilterCaptor.capture(), any(), any()); - assertEquals(1, intentFilterCaptor.getValue().countActions()); - assertEquals( - Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION, - intentFilterCaptor.getValue().getAction(0)); - assertEquals(1, intentFilterCaptor.getValue().countDataTypes()); - assertEquals(PACKAGE_MIME_TYPE, intentFilterCaptor.getValue().getDataType(0)); - } - - @Test - public void handleBroadcast_allow() throws Exception { - allowlistUsAsRuleProvider(); - makeUsSystemApp(); - ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mMockContext) - .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); - Intent intent = makeVerificationIntent(); - - broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); - runJobInHandler(); - - verify(mPackageManagerInternal) - .setIntegrityVerificationResult( - 1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); - } - - private void allowlistUsAsRuleProvider() { - Resources mockResources = mock(Resources.class); - when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages)) - .thenReturn(new String[] {TEST_FRAMEWORK_PACKAGE}); - when(mMockContext.getResources()).thenReturn(mockResources); - } - - private void runJobInHandler() { - ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); - // sendMessageAtTime is the first non-final method in the call chain when "post" is invoked. - verify(mHandler).sendMessageAtTime(messageCaptor.capture(), anyLong()); - messageCaptor.getValue().getCallback().run(); - } - - private void makeUsSystemApp() throws Exception { - makeUsSystemApp(true); - } - - private void makeUsSystemApp(boolean isSystemApp) throws Exception { - PackageInfo packageInfo = - mRealContext.getPackageManager().getPackageInfo(TEST_FRAMEWORK_PACKAGE, 0); - if (isSystemApp) { - packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; - } else { - packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; - } - doReturn(packageInfo) - .when(mSpyPackageManager) - .getPackageInfo(eq(TEST_FRAMEWORK_PACKAGE), anyInt()); - when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager); - } - - private Intent makeVerificationIntent() throws Exception { - PackageInfo packageInfo = - mRealContext - .getPackageManager() - .getPackageInfo( - TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNING_CERTIFICATES); - doReturn(packageInfo).when(mSpyPackageManager).getPackageInfo(eq(INSTALLER), anyInt()); - doReturn(1).when(mSpyPackageManager).getPackageUid(eq(INSTALLER), anyInt()); - doReturn(new String[]{INSTALLER}).when(mSpyPackageManager).getPackagesForUid(anyInt()); - return makeVerificationIntent(INSTALLER); - } - - private Intent makeVerificationIntent(String installer) throws Exception { - Intent intent = new Intent(); - intent.setDataAndType(Uri.fromFile(mTestApk), PACKAGE_MIME_TYPE); - intent.setAction(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION); - intent.putExtra(EXTRA_VERIFICATION_ID, 1); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, PACKAGE_NAME); - intent.putExtra(EXTRA_VERIFICATION_INSTALLER_PACKAGE, installer); - intent.putExtra( - EXTRA_VERIFICATION_INSTALLER_UID, - mMockContext.getPackageManager().getPackageUid(installer, /* flags= */ 0)); - intent.putExtra(Intent.EXTRA_LONG_VERSION_CODE, VERSION_CODE); - return intent; - } - - private void setIntegrityCheckIncludesRuleProvider(boolean shouldInclude) throws Exception { - int value = shouldInclude ? 1 : 0; - Settings.Global.putInt( - mRealContext.getContentResolver(), - Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, - value); - assertThat( - Settings.Global.getInt( - mRealContext.getContentResolver(), - Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, - -1) - == 1) - .isEqualTo(shouldInclude); - } -} |