diff options
79 files changed, 1244 insertions, 435 deletions
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 4b1afa517122..01b2953362b5 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -146,6 +146,7 @@ interface IActivityTaskManager { int getFrontActivityScreenCompatMode(); void setFrontActivityScreenCompatMode(int mode); void setFocusedTask(int taskId); + boolean setTaskIsPerceptible(int taskId, boolean isPerceptible); boolean removeTask(int taskId); void removeAllVisibleRecentTasks(); List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, boolean filterOnlyVisibleRecents, diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 08719fc549f8..500f7585b673 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -14200,6 +14200,9 @@ public class DevicePolicyManager { * <li>Manifest.permission.ACTIVITY_RECOGNITION</li> * <li>Manifest.permission.BODY_SENSORS</li> * </ul> + * On devices running {@link android.os.Build.VERSION_CODES#BAKLAVA}, the + * {@link android.health.connect.HealthPermissions} are also included in the + * restricted list. * <p> * A profile owner may not grant these permissions (i.e. call this method with any of the * permissions listed above and {@code grantState} of {@code #PERMISSION_GRANT_STATE_GRANTED}), diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 3727a3468c1f..eec30f38b415 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -529,6 +529,7 @@ public class ResourcesImpl { for (int i = 0; i < locales.size(); i++) { selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag()); } + defaultLocale = adjustLanguageTag(lc.getDefaultLocale().toLanguageTag()); } else { selectedLocales = new String[]{ adjustLanguageTag(locales.get(0).toLanguageTag())}; diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index b44620f12bbb..4ba97384192f 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -105,6 +105,7 @@ public enum DesktopModeFlags { ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true), ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE( Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true), + ENABLE_TASKBAR_OVERFLOW(Flags::enableTaskbarOverflow, false), ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true), ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true), ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true), diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 3e3b8e100d6d..8265d2523d6f 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -705,6 +705,13 @@ flag { } flag { + name: "enable_activity_embedding_support_for_connected_displays" + namespace: "lse_desktop_experience" + description: "Enables activity embedding support for connected displays, including enabling AE optimization for Settings." + bug: "369438353" +} + +flag { name: "enable_full_screen_window_on_removing_split_screen_stage_bugfix" namespace: "lse_desktop_experience" description: "Enables clearing the windowing mode of a freeform window when removing the task from the split screen stage." @@ -764,3 +771,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_taskbar_overflow" + namespace: "lse_desktop_experience" + description: "Show recent apps in the taskbar overflow." + bug: "368119679" +} diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java index b57acf3d97fd..b19967a47001 100644 --- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java +++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java @@ -261,7 +261,12 @@ public class AconfigFlags { // Default value is false for when the flag is not found. // Note: Unlike with the old storage, with AconfigPackage, we don't have a way to // know if the flag is not found or if it's found but the value is false. - value = aconfigPackage.getBooleanFlagValue(flagName, false); + try { + value = aconfigPackage.getBooleanFlagValue(flagName, false); + } catch (Exception e) { + Slog.e(LOG_TAG, "Failed to read Aconfig flag value for " + flagPackageAndName, e); + return null; + } } if (DEBUG) { Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value); diff --git a/core/jni/android_app_PropertyInvalidatedCache.cpp b/core/jni/android_app_PropertyInvalidatedCache.cpp index 12585d5f8137..6267522f114f 100644 --- a/core/jni/android_app_PropertyInvalidatedCache.cpp +++ b/core/jni/android_app_PropertyInvalidatedCache.cpp @@ -35,7 +35,7 @@ int NonceStore::getMaxNonce() const { return kMaxNonce; } -size_t NonceStore::getMaxByte() const { +int32_t NonceStore::getMaxByte() const { return kMaxByte; } @@ -68,13 +68,13 @@ int32_t NonceStore::getHash() const { } // Copy the byte block to the target and return the current hash. -int32_t NonceStore::getByteBlock(block_t* block, size_t len) const { +int32_t NonceStore::getByteBlock(block_t* block, int32_t len) const { memcpy(block, (void*) byteBlock(), std::min(kMaxByte, len)); return mByteHash; } // Set the byte block and the hash. -void NonceStore::setByteBlock(int hash, const block_t* block, size_t len) { +void NonceStore::setByteBlock(int hash, const block_t* block, int32_t len) { memcpy((void*) byteBlock(), block, len = std::min(kMaxByte, len)); mByteHash = hash; } diff --git a/core/jni/android_app_PropertyInvalidatedCache.h b/core/jni/android_app_PropertyInvalidatedCache.h index 54a4ac65fce2..1d75182356c6 100644 --- a/core/jni/android_app_PropertyInvalidatedCache.h +++ b/core/jni/android_app_PropertyInvalidatedCache.h @@ -27,8 +27,12 @@ namespace android::app::PropertyInvalidatedCache { * location. Fields with a variable location are found via offsets. The offsets make this * object position-independent, which is required because it is in shared memory and would be * mapped into different virtual addresses for different processes. + * + * This structure is shared between 64-bit and 32-bit processes. Therefore it is imperative + * that the structure not use any datatypes that are architecture-dependent (like size_t). + * Additionally, care must be taken to avoid unexpected padding in the structure. */ -class NonceStore { +class alignas(8) NonceStore { protected: // A convenient typedef. The jbyteArray element type is jbyte, which the compiler treats as // signed char. @@ -43,23 +47,27 @@ class NonceStore { // The value of an unset field. static constexpr int UNSET = 0; - // The size of the nonce array. + // The size of the nonce array. This and the following sizes are int32_t to + // be ABI independent. const int32_t kMaxNonce; // The size of the byte array. - const size_t kMaxByte; + const int32_t kMaxByte; // The offset to the nonce array. - const size_t mNonceOffset; + const int32_t mNonceOffset; // The offset to the byte array. - const size_t mByteOffset; + const int32_t mByteOffset; // The byte block hash. This is fixed and at a known offset, so leave it in the base class. volatile std::atomic<int32_t> mByteHash; + // A 4-byte padd that makes the size of this structure a multiple of 8 bytes. + const int32_t _pad = 0; + // The constructor is protected! It only makes sense when called from a subclass. - NonceStore(int kMaxNonce, size_t kMaxByte, volatile nonce_t* nonce, volatile block_t* block) : + NonceStore(int kMaxNonce, int kMaxByte, volatile nonce_t* nonce, volatile block_t* block) : kMaxNonce(kMaxNonce), kMaxByte(kMaxByte), mNonceOffset(offset(this, const_cast<nonce_t*>(nonce))), @@ -70,7 +78,7 @@ class NonceStore { // These provide run-time access to the sizing parameters. int getMaxNonce() const; - size_t getMaxByte() const; + int getMaxByte() const; // Fetch a nonce, returning UNSET if the index is out of range. This method specifically // does not throw or generate an error if the index is out of range; this allows the method @@ -86,10 +94,10 @@ class NonceStore { int32_t getHash() const; // Copy the byte block to the target and return the current hash. - int32_t getByteBlock(block_t* block, size_t len) const; + int32_t getByteBlock(block_t* block, int32_t len) const; // Set the byte block and the hash. - void setByteBlock(int hash, const block_t* block, size_t len); + void setByteBlock(int hash, const block_t* block, int32_t len); private: @@ -113,6 +121,12 @@ class NonceStore { } }; +// Assert that the size of the object is fixed, independent of the CPU architecture. There are +// four int32_t fields and one atomic<int32_t>, which sums to 20 bytes total. This assertion +// uses a constant instead of computing the size of the objects in the compiler, to avoid +// different answers on different architectures. +static_assert(sizeof(NonceStore) == 24); + /** * A cache nonce block contains an array of std::atomic<int64_t> and an array of bytes. The * byte array has an associated hash. This class provides methods to read and write the fields @@ -126,20 +140,22 @@ class NonceStore { * The template is parameterized by the number of nonces it supports and the number of bytes in * the string block. */ -template<int maxNonce, size_t maxByte> class CacheNonce : public NonceStore { +template<int MAX_NONCE, int MAX_BYTE> class CacheNonce : public NonceStore { // The array of nonces - volatile nonce_t mNonce[maxNonce]; + volatile nonce_t mNonce[MAX_NONCE]; // The byte array. This is not atomic but it is guarded by the mByteHash. - volatile block_t mByteBlock[maxByte]; + volatile block_t mByteBlock[MAX_BYTE]; public: + // Export the parameters for use in compiler assertions. + static constexpr int kMaxNonceCount = MAX_NONCE; + static constexpr int kMaxByteCount = MAX_BYTE; + // Construct and initialize the memory. - CacheNonce() : - NonceStore(maxNonce, maxByte, &mNonce[0], &mByteBlock[0]) - { - for (int i = 0; i < maxNonce; i++) { + CacheNonce() : NonceStore(MAX_NONCE, MAX_BYTE, &mNonce[0], &mByteBlock[0]) { + for (int i = 0; i < MAX_NONCE; i++) { mNonce[i] = UNSET; } mByteHash = UNSET; @@ -155,4 +171,10 @@ template<int maxNonce, size_t maxByte> class CacheNonce : public NonceStore { typedef CacheNonce</* max nonce */ 128, /* byte block size */ 8192> SystemCacheNonce; // LINT.ThenChange(/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java:system_nonce_config) +// Verify that there is no padding in the final class. +static_assert(sizeof(SystemCacheNonce) == + sizeof(NonceStore) + + SystemCacheNonce::kMaxNonceCount*8 + + SystemCacheNonce::kMaxByteCount); + } // namespace android.app.PropertyInvalidatedCache diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index b99b0ef7f24e..a8e51a7c1fe8 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -765,28 +765,28 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong std::vector<int32_t> dimensions; std::vector<int32_t> sizes; std::vector<int32_t> samplingKeys; - int32_t fd = -1; + base::unique_fd fd; if (jdimensionArray) { jsize numLuts = env->GetArrayLength(jdimensionArray); - ScopedIntArrayRW joffsets(env, joffsetArray); + ScopedIntArrayRO joffsets(env, joffsetArray); if (joffsets.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray"); + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from joffsetArray"); return; } - ScopedIntArrayRW jdimensions(env, jdimensionArray); + ScopedIntArrayRO jdimensions(env, jdimensionArray); if (jdimensions.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray"); + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jdimensionArray"); return; } - ScopedIntArrayRW jsizes(env, jsizeArray); + ScopedIntArrayRO jsizes(env, jsizeArray); if (jsizes.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray"); + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsizeArray"); return; } - ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray); + ScopedIntArrayRO jsamplingKeys(env, jsamplingKeyArray); if (jsamplingKeys.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray"); + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsamplingKeyArray"); return; } @@ -796,15 +796,15 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong sizes = std::vector<int32_t>(jsizes.get(), jsizes.get() + numLuts); samplingKeys = std::vector<int32_t>(jsamplingKeys.get(), jsamplingKeys.get() + numLuts); - ScopedFloatArrayRW jbuffers(env, jbufferArray); + ScopedFloatArrayRO jbuffers(env, jbufferArray); if (jbuffers.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray"); + jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRO from jbufferArray"); return; } // create the shared memory and copy jbuffers size_t bufferSize = jbuffers.size() * sizeof(float); - fd = ashmem_create_region("lut_shared_mem", bufferSize); + fd.reset(ashmem_create_region("lut_shared_mem", bufferSize)); if (fd < 0) { jniThrowRuntimeException(env, "ashmem_create_region() failed"); return; @@ -820,7 +820,7 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong } } - transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys); + transaction->setLuts(ctrl, std::move(fd), offsets, dimensions, sizes, samplingKeys); } static void nativeSetPictureProfileId(JNIEnv* env, jclass clazz, jlong transactionObj, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl index 59add47fc79d..5f45192569e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl @@ -18,13 +18,11 @@ package com.android.wm.shell.desktopmode; /** * Defines the state of desks on a display whose ID is `displayId`, which is: - * - `canCreateDesks`: whether it's possible to create new desks on this display. * - `activeDeskId`: the currently active desk Id, or `-1` if none is active. * - `deskId`: the list of desk Ids of the available desks on this display. */ parcelable DisplayDeskState { int displayId; - boolean canCreateDesk; int activeDeskId; int[] deskIds; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl index 7ed1581cdfdb..cefbd8947feb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl @@ -28,7 +28,7 @@ oneway interface IDesktopTaskListener { * Called once when the listener first gets connected to initialize it with the current state of * desks in Shell. */ - void onListenerConnected(in DisplayDeskState[] displayDeskStates); + void onListenerConnected(in DisplayDeskState[] displayDeskStates, boolean canCreateDesks); /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */ void onTasksVisibilityChanged(int displayId, int visibleTasksCount); @@ -49,10 +49,10 @@ oneway interface IDesktopTaskListener { void onExitDesktopModeTransitionStarted(int transitionDuration); /** - * Called when the conditions that allow the creation of a new desk on the display whose ID is - * `displayId` changes to `canCreateDesks`. It's also called when a new display is added. + * Called when the conditions that allow the creation of a new desk changes. This is a global + * state for the entire device. */ - void onCanCreateDesksChanged(int displayId, boolean canCreateDesks); + void onCanCreateDesksChanged(boolean canCreateDesks); /** Called when a desk whose ID is `deskId` is added to the display whose ID is `displayId`. */ void onDeskAdded(int displayId, int deskId); diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 4fe0b80f3951..275972495206 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -715,47 +715,45 @@ void ASurfaceTransaction_setLuts(ASurfaceTransaction* aSurfaceTransaction, sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); - int fd = -1; + base::unique_fd fd; std::vector<int32_t> offsets; std::vector<int32_t> dimensions; std::vector<int32_t> sizes; std::vector<int32_t> samplingKeys; if (luts) { - std::vector<float> buffer(luts->totalBufferSize); int32_t count = luts->offsets.size(); offsets = luts->offsets; dimensions.reserve(count); sizes.reserve(count); samplingKeys.reserve(count); - for (int32_t i = 0; i < count; i++) { - dimensions.emplace_back(luts->entries[i]->properties.dimension); - sizes.emplace_back(luts->entries[i]->properties.size); - samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey); - std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(), - buffer.begin() + offsets[i]); - } // mmap - fd = ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float)); + fd.reset(ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float))); if (fd < 0) { LOG_ALWAYS_FATAL("setLuts, ashmem_create_region() failed"); return; } - void* ptr = mmap(nullptr, luts->totalBufferSize * sizeof(float), PROT_READ | PROT_WRITE, - MAP_SHARED, fd, 0); + float* ptr = (float*)mmap(nullptr, luts->totalBufferSize * sizeof(float), + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { LOG_ALWAYS_FATAL("setLuts, Failed to map the shared memory"); return; } - memcpy(ptr, buffer.data(), luts->totalBufferSize * sizeof(float)); + for (int32_t i = 0; i < count; i++) { + dimensions.emplace_back(luts->entries[i]->properties.dimension); + sizes.emplace_back(luts->entries[i]->properties.size); + samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey); + std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(), + ptr + offsets[i]); + } + munmap(ptr, luts->totalBufferSize * sizeof(float)); } - transaction->setLuts(surfaceControl, base::unique_fd(fd), offsets, dimensions, sizes, - samplingKeys); + transaction->setLuts(surfaceControl, std::move(fd), offsets, dimensions, sizes, samplingKeys); } void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction, diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp index bd892637a5eb..853d1ad0dc94 100644 --- a/packages/CtsShim/build/Android.bp +++ b/packages/CtsShim/build/Android.bp @@ -152,34 +152,6 @@ android_app { } //########################################################## -// Variant: System app upgrade - -android_app { - name: "CtsShimUpgrade", - - sdk_version: "current", - optimize: { - enabled: false, - }, - dex_preopt: { - enabled: false, - }, - - manifest: "shim/AndroidManifestUpgrade.xml", - min_sdk_version: "24", -} - -java_genrule { - name: "generate_shim_manifest", - srcs: [ - "shim/AndroidManifest.xml", - ":CtsShimUpgrade", - ], - out: ["AndroidManifest.xml"], - cmd: "sed -e s/__HASH__/`sha512sum -b $(location :CtsShimUpgrade) | cut -d' ' -f1`/ $(location shim/AndroidManifest.xml) > $(out)", -} - -//########################################################## // Variant: System app android_app { @@ -193,7 +165,7 @@ android_app { enabled: false, }, - manifest: ":generate_shim_manifest", + manifest: "shim/AndroidManifest.xml", apex_available: [ "//apex_available:platform", "com.android.apex.cts.shim.v1", diff --git a/packages/CtsShim/build/shim/AndroidManifest.xml b/packages/CtsShim/build/shim/AndroidManifest.xml index 3b8276b66bac..1ffe56c0c76a 100644 --- a/packages/CtsShim/build/shim/AndroidManifest.xml +++ b/packages/CtsShim/build/shim/AndroidManifest.xml @@ -17,15 +17,13 @@ <!-- Manifest for the system CTS shim --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - package="com.android.cts.ctsshim" - android:sharedUserId="com.android.cts.ctsshim" > + package="com.android.cts.ctsshim" > - <uses-sdk - android:minSdkVersion="24" + <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="28" /> <restrict-update - android:hash="__HASH__" /> + android:hash="__CAN_NOT_BE_UPDATED__" /> <application android:hasCode="false" diff --git a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml b/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml deleted file mode 100644 index 7f3644a18ad6..000000000000 --- a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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. ---> - -<!-- Manifest for the system CTS shim --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - package="com.android.cts.ctsshim" > - - <uses-sdk - android:minSdkVersion="24" - android:targetSdkVersion="28" /> - - <application - android:hasCode="false" - tools:ignore="AllowBackup,MissingApplicationIcon" /> -</manifest> - diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml index dc5c9b297181..b6e80c784f10 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml @@ -18,7 +18,7 @@ <resources> <style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" > <item name="android:colorAccent">@color/settingslib_materialColorPrimary</item> - <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainerLowest</item> + <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item> <item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item> <item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item> <item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index 5e12ee1b18fa..db9035b1635b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -65,6 +65,7 @@ import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateElementFloatAsState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.thenIf +import com.android.compose.theme.colorAttr import com.android.settingslib.Utils import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -75,8 +76,6 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipBackground -import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipHighlighted import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.onScrimDim import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingHorizontal import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingVertical @@ -107,7 +106,7 @@ object ShadeHeader { object Dimensions { val CollapsedHeight = 48.dp val ExpandedHeight = 120.dp - val ChipPaddingHorizontal = 8.dp + val ChipPaddingHorizontal = 6.dp val ChipPaddingVertical = 4.dp } @@ -117,12 +116,6 @@ object ShadeHeader { val ColorScheme.onScrimDim: Color get() = Color.DarkGray - - val ColorScheme.chipBackground: Color - get() = Color.DarkGray - - val ColorScheme.chipHighlighted: Color - get() = Color.LightGray } object TestTags { @@ -165,7 +158,7 @@ fun ContentScope.CollapsedShadeHeader( VariableDayDate( longerDateText = viewModel.longerDateText, shorterDateText = viewModel.shorterDateText, - chipHighlight = viewModel.notificationsChipHighlight, + textColor = colorAttr(android.R.attr.textColorPrimary), modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart), ) } @@ -265,7 +258,7 @@ fun ContentScope.ExpandedShadeHeader( VariableDayDate( longerDateText = viewModel.longerDateText, shorterDateText = viewModel.shorterDateText, - chipHighlight = viewModel.notificationsChipHighlight, + textColor = colorAttr(android.R.attr.textColorPrimary), modifier = Modifier.widthIn(max = 90.dp), ) Spacer(modifier = Modifier.weight(1f)) @@ -310,6 +303,7 @@ fun ContentScope.OverlayShadeHeader( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = horizontalPadding), ) { + val chipHighlight = viewModel.notificationsChipHighlight if (isShadeLayoutWide) { Clock( scale = 1f, @@ -319,13 +313,13 @@ fun ContentScope.OverlayShadeHeader( Spacer(modifier = Modifier.width(5.dp)) } NotificationsChip( - chipHighlight = viewModel.notificationsChipHighlight, onClick = viewModel::onNotificationIconChipClicked, + backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme), ) { VariableDayDate( longerDateText = viewModel.longerDateText, shorterDateText = viewModel.shorterDateText, - chipHighlight = viewModel.notificationsChipHighlight, + textColor = chipHighlight.foregroundColor(MaterialTheme.colorScheme), ) } } @@ -338,14 +332,13 @@ fun ContentScope.OverlayShadeHeader( ) { val chipHighlight = viewModel.quickSettingsChipHighlight SystemIconChip( - chipHighlight = chipHighlight, + backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme), onClick = viewModel::onSystemIconChipClicked, ) { StatusIcons( viewModel = viewModel, useExpandedFormat = false, modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false), - chipHighlight = chipHighlight, ) BatteryIcon( createBatteryMeterViewController = @@ -515,6 +508,7 @@ private fun BatteryIcon( batteryIcon.setPercentShowMode( if (useExpandedFormat) BatteryMeterView.MODE_ESTIMATE else BatteryMeterView.MODE_ON ) + // TODO(b/397223606): Get the actual spec for this. if (chipHighlight is HeaderChipHighlight.Strong) { batteryIcon.updateColors(primaryColor, inverseColor, inverseColor) } else if (chipHighlight is HeaderChipHighlight.Weak) { @@ -553,7 +547,6 @@ private fun ContentScope.StatusIcons( viewModel: ShadeHeaderViewModel, useExpandedFormat: Boolean, modifier: Modifier = Modifier, - chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None, ) { val localContext = LocalContext.current val themedContext = @@ -581,6 +574,8 @@ private fun ContentScope.StatusIcons( viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS) } + val chipHighlight = viewModel.quickSettingsChipHighlight + AndroidView( factory = { context -> iconManager.setTint(primaryColor, inverseColor) @@ -617,6 +612,7 @@ private fun ContentScope.StatusIcons( iconContainer.removeIgnoredSlot(locationSlot) } + // TODO(b/397223606): Get the actual spec for this. if (chipHighlight is HeaderChipHighlight.Strong) { iconManager.setTint(inverseColor, primaryColor) } else if (chipHighlight is HeaderChipHighlight.Weak) { @@ -629,16 +625,12 @@ private fun ContentScope.StatusIcons( @Composable private fun NotificationsChip( - chipHighlight: HeaderChipHighlight, onClick: () -> Unit, modifier: Modifier = Modifier, + backgroundColor: Color = Color.Unspecified, content: @Composable BoxScope.() -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } - val chipBackground = - with(MaterialTheme.colorScheme) { - if (chipHighlight is HeaderChipHighlight.Strong) chipHighlighted else chipBackground - } Box( modifier = modifier @@ -647,7 +639,7 @@ private fun NotificationsChip( indication = null, onClick = onClick, ) - .background(chipBackground, RoundedCornerShape(25.dp)) + .background(backgroundColor, RoundedCornerShape(25.dp)) .padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical) ) { content() @@ -657,7 +649,7 @@ private fun NotificationsChip( @Composable private fun SystemIconChip( modifier: Modifier = Modifier, - chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None, + backgroundColor: Color = Color.Unspecified, onClick: (() -> Unit)? = null, content: @Composable RowScope.() -> Unit, ) { @@ -667,16 +659,12 @@ private fun SystemIconChip( with(MaterialTheme.colorScheme) { Modifier.background(onScrimDim, RoundedCornerShape(CollapsedHeight / 4)) } - val backgroundColor = - with(MaterialTheme.colorScheme) { - if (chipHighlight is HeaderChipHighlight.Strong) chipHighlighted else chipBackground - } Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier - .thenIf(chipHighlight !is HeaderChipHighlight.None) { + .thenIf(backgroundColor != Color.Unspecified) { Modifier.background(backgroundColor, RoundedCornerShape(25.dp)) .padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt index 64aada52626b..8fbd0519cbdf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt @@ -4,22 +4,16 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.Layout -import com.android.compose.theme.colorAttr -import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight @Composable fun VariableDayDate( longerDateText: String, shorterDateText: String, - chipHighlight: HeaderChipHighlight, + textColor: Color, modifier: Modifier = Modifier, ) { - val textColor = - if (chipHighlight is HeaderChipHighlight.Strong) - colorAttr(android.R.attr.textColorPrimaryInverse) - else colorAttr(android.R.attr.textColorPrimary) - Layout( contents = listOf( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt index 4e14fec8408f..943ada9346e7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt @@ -18,6 +18,9 @@ package com.android.systemui.media.controls.ui.binder import android.animation.Animator import android.animation.ObjectAnimator +import android.icu.text.MeasureFormat +import android.icu.util.Measure +import android.icu.util.MeasureUnit import android.testing.TestableLooper import android.view.View import android.widget.SeekBar @@ -30,6 +33,7 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat +import java.util.Locale import org.junit.Before import org.junit.Rule import org.junit.Test @@ -61,11 +65,11 @@ class SeekBarObserverTest : SysuiTestCase() { fun setUp() { context.orCreateTestableResources.addOverride( R.dimen.qs_media_enabled_seekbar_height, - enabledHeight + enabledHeight, ) context.orCreateTestableResources.addOverride( R.dimen.qs_media_disabled_seekbar_height, - disabledHeight + disabledHeight, ) seekBarView = SeekBar(context) @@ -110,14 +114,31 @@ class SeekBarObserverTest : SysuiTestCase() { @Test fun seekBarProgress() { + val elapsedTime = 3000 + val duration = (1.5 * 60 * 60 * 1000).toInt() // WHEN part of the track has been played - val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true) + val data = SeekBarViewModel.Progress(true, true, true, false, elapsedTime, duration, true) observer.onChanged(data) // THEN seek bar shows the progress - assertThat(seekBarView.progress).isEqualTo(3000) - assertThat(seekBarView.max).isEqualTo(120000) - - val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00") + assertThat(seekBarView.progress).isEqualTo(elapsedTime) + assertThat(seekBarView.max).isEqualTo(duration) + + val expectedProgress = + MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) + .formatMeasures(Measure(3, MeasureUnit.SECOND)) + val expectedDuration = + MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) + .formatMeasures( + Measure(1, MeasureUnit.HOUR), + Measure(30, MeasureUnit.MINUTE), + Measure(0, MeasureUnit.SECOND), + ) + val desc = + context.getString( + R.string.controls_media_seekbar_description, + expectedProgress, + expectedDuration, + ) assertThat(seekBarView.contentDescription).isEqualTo(desc) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt index 426af264da07..83e26c4220b1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection +import android.app.Notification +import android.graphics.Color import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.TestableLooper.RunWithLooper @@ -72,4 +74,35 @@ class BundleEntryTest : SysuiTestCase() { fun getKey_adapter() { assertThat(underTest.entryAdapter.key).isEqualTo("key") } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun isClearable_adapter() { + assertThat(underTest.entryAdapter.isClearable).isTrue() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getSummarization_adapter() { + assertThat(underTest.entryAdapter.summarization).isNull() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getContrastedColor_adapter() { + assertThat(underTest.entryAdapter.getContrastedColor(context, false, Color.WHITE)) + .isEqualTo(Notification.COLOR_DEFAULT) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun canPeek_adapter() { + assertThat(underTest.entryAdapter.canPeek()).isFalse() + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getWhen_adapter() { + assertThat(underTest.entryAdapter.`when`).isEqualTo(0) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 19d1224a9bf3..1f5c6722f38e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -163,9 +163,10 @@ public class NotificationEntryTest extends SysuiTestCase { @Test public void testIsExemptFromDndVisualSuppression_media() { + MediaSession session = new MediaSession(mContext, "test"); Notification.Builder n = new Notification.Builder(mContext, "") .setStyle(new Notification.MediaStyle() - .setMediaSession(mock(MediaSession.Token.class))) + .setMediaSession(session.getSessionToken())) .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") .setContentText("Text"); @@ -593,6 +594,76 @@ public class NotificationEntryTest extends SysuiTestCase { assertThat(entry.getEntryAdapter().getGroupRoot()).isEqualTo(parent.getEntryAdapter()); } + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + public void isClearable_adapter() { + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + Notification notification = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .build(); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setChannel(mChannel) + .setId(mId++) + .setNotification(notification) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + entry.setRow(row); + + assertThat(entry.getEntryAdapter().isClearable()).isEqualTo(entry.isClearable()); + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + public void getSummarization_adapter() { + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + Notification notification = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .build(); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setChannel(mChannel) + .setId(mId++) + .setNotification(notification) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + Ranking ranking = new RankingBuilder(entry.getRanking()) + .setSummarization("hello") + .build(); + entry.setRanking(ranking); + entry.setRow(row); + + assertThat(entry.getEntryAdapter().getSummarization()).isEqualTo("hello"); + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + public void getIcons_adapter() { + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + Notification notification = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .build(); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setChannel(mChannel) + .setId(mId++) + .setNotification(notification) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + entry.setRow(row); + + assertThat(entry.getEntryAdapter().getIcons()).isEqualTo(entry.getIcons()); + } + private Notification.Action createContextualAction(String title) { return new Notification.Action.Builder( Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt index 7781df1ad91f..43cb9575b609 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt @@ -16,8 +16,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification +import android.app.Notification.MediaStyle +import android.media.session.MediaSession import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings +import android.service.notification.StatusBarNotification import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.dumpManager @@ -36,6 +39,7 @@ import com.android.systemui.scene.data.repository.Idle import com.android.systemui.scene.data.repository.setTransition import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -217,6 +221,16 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu mock<ExpandableNotificationRow>().apply { whenever(isMediaRow).thenReturn(true) } + sbn = SbnBuilder().setNotification( + Notification.Builder(context, "channel").setStyle( + MediaStyle().setMediaSession( + MediaSession( + context, + "tag" + ).sessionToken + ) + ).build() + ).build() } collectionListener.onEntryAdded(fakeEntry) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt index 4c1f4f17e00c..1b8d64d5483c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt @@ -80,9 +80,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.testKosmos import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.wmshell.BubblesManager -import java.util.Optional -import kotlin.test.assertNotNull -import kotlin.test.fail import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -110,6 +107,9 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters +import java.util.Optional +import kotlin.test.assertNotNull +import kotlin.test.fail /** Tests for [NotificationGutsManager]. */ @SmallTest @@ -509,7 +509,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( .setImportance(NotificationManager.IMPORTANCE_HIGH) .build() - whenever(row.isNonblockable).thenReturn(false) whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) val statusBarNotification = entry.sbn gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -546,7 +545,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.isNonblockable).thenReturn(false) val statusBarNotification = row.entry.sbn val entry = row.entry @@ -586,7 +584,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.isNonblockable).thenReturn(false) val statusBarNotification = row.entry.sbn val entry = row.entry @@ -641,7 +638,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( ): NotificationMenuRowPlugin.MenuItem { val menuRow: NotificationMenuRowPlugin = NotificationMenuRow(mContext, peopleNotificationIdentifier) - menuRow.createMenu(row, row.entry.sbn) + menuRow.createMenu(row) val menuItem = menuRow.getLongpressMenuItem(mContext) assertNotNull(menuItem) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index 027e899e20df..9fdfca14a5b2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -72,7 +72,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { public void testAttachDetach() { NotificationMenuRowPlugin row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewUtils.attachView(row.getMenuView()); TestableLooper.get(this).processAllMessages(); ViewUtils.detachView(row.getMenuView()); @@ -83,9 +83,9 @@ public class NotificationMenuRowTest extends LeakCheckedTest { public void testRecreateMenu() { NotificationMenuRowPlugin row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); assertTrue(row.getMenuView() != null); - row.createMenu(mRow, null); + row.createMenu(mRow); assertTrue(row.getMenuView() != null); } @@ -103,7 +103,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0); NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewGroup container = (ViewGroup) row.getMenuView(); // noti blocking @@ -116,7 +116,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0); NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewGroup container = (ViewGroup) row.getMenuView(); // just for noti blocking @@ -129,7 +129,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0); NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewGroup container = (ViewGroup) row.getMenuView(); // one for snooze and one for noti blocking @@ -142,7 +142,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 1); NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - row.createMenu(mRow, null); + row.createMenu(mRow); ViewGroup container = (ViewGroup) row.getMenuView(); // Clear menu @@ -417,7 +417,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { public void testOnTouchMove() { NotificationMenuRow row = Mockito.spy( new NotificationMenuRow(mContext, mPeopleNotificationIdentifier)); - row.createMenu(mRow, null); + row.createMenu(mRow); doReturn(50f).when(row).getDismissThreshold(); doReturn(true).when(row).canBeDismissed(); doReturn(mView).when(row).getMenuView(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt index e5cb0fbc9e4b..885e71e7a7fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.testKosmos import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -49,7 +50,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { private val sectionsManager = mock<NotificationSectionsManager>() private val msdlPlayer = kosmos.fakeMSDLPlayer private var canRowBeDismissed = true - private var magneticAnimationsCancelled = false + private var magneticAnimationsCancelled = MutableList(childrenNumber) { false } private val underTest = kosmos.magneticNotificationRowManagerImpl @@ -64,8 +65,10 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { NotificationTestHelper(mContext, mDependency, kosmos.testableLooper, featureFlags) children = notificationTestHelper.createGroup(childrenNumber).childrenContainer swipedRow = children.attachedChildren[childrenNumber / 2] - configureMagneticRowListener(swipedRow) - magneticAnimationsCancelled = false + children.attachedChildren.forEachIndexed { index, row -> + row.magneticRowListener = row.magneticRowListener.asTestableListener(index) + } + magneticAnimationsCancelled.replaceAll { false } } @Test @@ -259,14 +262,14 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { underTest.onMagneticInteractionEnd(swipedRow, velocity = null) // THEN magnetic animations are cancelled - assertThat(magneticAnimationsCancelled).isTrue() + assertThat(magneticAnimationsCancelled[childrenNumber / 2]).isTrue() } @Test fun onMagneticInteractionEnd_forMagneticNeighbor_cancelsMagneticAnimations() = kosmos.testScope.runTest { - val neighborRow = children.attachedChildren[childrenNumber / 2 - 1] - configureMagneticRowListener(neighborRow) + val neighborIndex = childrenNumber / 2 - 1 + val neighborRow = children.attachedChildren[neighborIndex] // GIVEN that targets are set setTargets() @@ -275,9 +278,15 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { underTest.onMagneticInteractionEnd(neighborRow, null) // THEN magnetic animations are cancelled - assertThat(magneticAnimationsCancelled).isTrue() + assertThat(magneticAnimationsCancelled[neighborIndex]).isTrue() } + @After + fun tearDown() { + // We reset the manager so that all MagneticRowListener can cancel all animations + underTest.reset() + } + private fun setDetachedState() { val threshold = 100f underTest.setSwipeThresholdPx(threshold) @@ -302,27 +311,33 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { originalTranslation * MagneticNotificationRowManagerImpl.MAGNETIC_REDUCTION - private fun configureMagneticRowListener(row: ExpandableNotificationRow) { - val listener = - object : MagneticRowListener { - override fun setMagneticTranslation(translation: Float) { - row.translation = translation - } + private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener { + val delegate = this + return object : MagneticRowListener { + override fun setMagneticTranslation(translation: Float) { + delegate.setMagneticTranslation(translation) + } - override fun triggerMagneticForce( - endTranslation: Float, - springForce: SpringForce, - startVelocity: Float, - ) {} + override fun triggerMagneticForce( + endTranslation: Float, + springForce: SpringForce, + startVelocity: Float, + ) { + delegate.triggerMagneticForce(endTranslation, springForce, startVelocity) + } - override fun cancelMagneticAnimations() { - magneticAnimationsCancelled = true - } + override fun cancelMagneticAnimations() { + magneticAnimationsCancelled[rowIndex] = true + delegate.cancelMagneticAnimations() + } - override fun cancelTranslationAnimations() {} + override fun cancelTranslationAnimations() { + delegate.cancelTranslationAnimations() + } - override fun canRowBeDismissed(): Boolean = canRowBeDismissed + override fun canRowBeDismissed(): Boolean { + return canRowBeDismissed } - row.magneticRowListener = listener + } } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java index 94fdbae83253..9b961d2535ae 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java @@ -122,7 +122,7 @@ public interface NotificationMenuRowPlugin extends Plugin { public void setAppName(String appName); - public void createMenu(ViewGroup parent, StatusBarNotification sbn); + public void createMenu(ViewGroup parent); public void resetMenu(); @@ -215,9 +215,8 @@ public interface NotificationMenuRowPlugin extends Plugin { /** * Callback used to signal the menu that its parent notification has been updated. - * @param sbn */ - public void onNotificationUpdated(StatusBarNotification sbn); + public void onNotificationUpdated(); /** * Callback used to signal the menu that a user is moving the parent notification. diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3fc46ed6c9d1..359bd2bcb37c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3170,8 +3170,8 @@ <string name="controls_media_settings_button">Settings</string> <!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]--> <string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string> - <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] --> - <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string> + <!-- Content description for media controls progress bar [CHAR_LIMIT=NONE] --> + <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1 hour 2 minutes 30 seconds">%1$s</xliff:g> of <xliff:g id="total_time" example="4 hours 5 seconds">%2$s</xliff:g></string> <!-- Placeholder title to inform user that an app has posted media controls [CHAR_LIMIT=NONE] --> <string name="controls_media_empty_title"><xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g> is running</string> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 0f1da509468a..ae3a76e2d2ca 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -71,6 +71,7 @@ android_library { "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "//frameworks/libs/systemui:msdl", "//frameworks/libs/systemui:view_capture", + "am_flags_lib", ], resource_dirs: [ "res", diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml index f22dac4b9fb4..98e5cea0e78f 100644 --- a/packages/SystemUI/shared/res/values/bools.xml +++ b/packages/SystemUI/shared/res/values/bools.xml @@ -22,4 +22,7 @@ <resources> <!-- Whether to add padding at the bottom of the complication clock --> <bool name="dream_overlay_complication_clock_bottom_padding">false</bool> -</resources>
\ No newline at end of file + + <!-- Whether to mark tasks that are present in the UI as perceptible tasks. --> + <bool name="config_usePerceptibleTasks">false</bool> +</resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index ed9ba7aa455b..26d78bbee8e5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -46,6 +46,8 @@ import android.view.Display; import android.window.TaskSnapshot; import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.server.am.Flags; +import com.android.systemui.shared.R; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -227,6 +229,17 @@ public class ActivityManagerWrapper { } /** + * Sets whether or not the specified task is perceptible. + */ + public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) { + try { + return getService().setTaskIsPerceptible(taskId, isPerceptible); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Removes a task by id. */ public void removeTask(final int taskId) { @@ -311,6 +324,14 @@ public class ActivityManagerWrapper { } /** + * Returns true if tasks with a presence in the UI should be marked as perceptible tasks. + */ + public static boolean usePerceptibleTasks(Context context) { + return Flags.perceptibleTasks() + && context.getResources().getBoolean(R.bool.config_usePerceptibleTasks); + } + + /** * Returns true if the running task represents the home task */ public static boolean isHomeTask(RunningTaskInfo info) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java index 7c141c1b561e..5247acc94e03 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java @@ -58,11 +58,22 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi private final BiMap<Integer, AmbientVolumeSlider> mSideToSliderMap = HashBiMap.create(); private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT; + private HearingDevicesUiEventLogger mUiEventLogger; + private int mLaunchSourceId; + private final AmbientVolumeSlider.OnChangeListener mSliderOnChangeListener = (slider, value) -> { - if (mListener != null) { - final int side = mSideToSliderMap.inverse().get(slider); - mListener.onSliderValueChange(side, value); + final Integer side = mSideToSliderMap.inverse().get(slider); + if (side != null) { + if (mUiEventLogger != null) { + HearingDevicesUiEvent uiEvent = side == SIDE_UNIFIED + ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED + : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED; + mUiEventLogger.log(uiEvent, mLaunchSourceId); + } + if (mListener != null) { + mListener.onSliderValueChange(side, value); + } } }; @@ -94,6 +105,12 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi return; } setMuted(!mMuted); + if (mUiEventLogger != null) { + HearingDevicesUiEvent uiEvent = mMuted + ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_MUTE + : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_UNMUTE; + mUiEventLogger.log(uiEvent, mLaunchSourceId); + } if (mListener != null) { mListener.onAmbientVolumeIconClick(); } @@ -103,6 +120,12 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi mExpandIcon = requireViewById(R.id.ambient_expand_icon); mExpandIcon.setOnClickListener(v -> { setExpanded(!mExpanded); + if (mUiEventLogger != null) { + HearingDevicesUiEvent uiEvent = mExpanded + ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS + : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS; + mUiEventLogger.log(uiEvent, mLaunchSourceId); + } if (mListener != null) { mListener.onExpandIconClick(); } @@ -243,6 +266,11 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi updateVolumeLevel(); } + void setUiEventLogger(HearingDevicesUiEventLogger uiEventLogger, int launchSourceId) { + mUiEventLogger = uiEventLogger; + mLaunchSourceId = launchSourceId; + } + private void updateVolumeLevel() { int leftLevel, rightLevel; if (mExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 22ecb0af8c18..786d27af3994 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -382,6 +382,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private void setupAmbientControls(CachedBluetoothDevice activeHearingDevice) { final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout); + ambientLayout.setUiEventLogger(mUiEventLogger, mLaunchSourceId); mAmbientController = new AmbientVolumeUiController( mDialog.getContext(), mLocalBluetoothManager, ambientLayout); mAmbientController.setShowUiWhenLocalDataExist(false); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt index 9e77b02be495..fe1d5040c6f5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt @@ -29,7 +29,17 @@ enum class HearingDevicesUiEvent(private val id: Int) : UiEventLogger.UiEventEnu @UiEvent(doc = "Click on the device gear to enter device detail page") HEARING_DEVICES_GEAR_CLICK(1853), @UiEvent(doc = "Select a preset from preset spinner") HEARING_DEVICES_PRESET_SELECT(1854), - @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856); + @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856), + @UiEvent(doc = "Change the ambient volume with unified control") + HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED(2149), + @UiEvent(doc = "Change the ambient volume with separated control") + HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED(2150), + @UiEvent(doc = "Mute the ambient volume") HEARING_DEVICES_AMBIENT_MUTE(2151), + @UiEvent(doc = "Unmute the ambient volume") HEARING_DEVICES_AMBIENT_UNMUTE(2152), + @UiEvent(doc = "Expand the ambient volume controls") + HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS(2153), + @UiEvent(doc = "Collapse the ambient volume controls") + HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154); override fun getId(): Int = this.id } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index cce1ae1a2947..6473b1c30586 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -237,7 +237,15 @@ constructor( with(mediaHost) { expansion = MediaHostState.EXPANDED expandedMatchesParentHeight = true - showsOnlyActiveMedia = false + if (v2FlagEnabled()) { + // Only show active media to match lock screen, not resumable media, which can + // persist + // for up to 2 days. + showsOnlyActiveMedia = true + } else { + // Maintain old behavior on tablet until V2 flag rolls out. + showsOnlyActiveMedia = false + } falsingProtectionNeeded = false disablePagination = true init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt index 6d796d96ea71..3f538203aee9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt @@ -49,9 +49,10 @@ import com.android.systemui.media.controls.ui.util.MediaArtworkHelper import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.viewmodel.MediaActionViewModel import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel -import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_CENTER_ALPHA import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA +import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA +import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_ALL import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_COMPACT import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel @@ -537,18 +538,24 @@ object MediaControlViewBinder { height: Int, ): LayerDrawable { val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height) - val alpha = + val startAlpha = if (Flags.mediaControlsA11yColors()) { - MEDIA_PLAYER_SCRIM_CENTER_ALPHA - } else { MEDIA_PLAYER_SCRIM_START_ALPHA + } else { + MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY + } + val endAlpha = + if (Flags.mediaControlsA11yColors()) { + MEDIA_PLAYER_SCRIM_END_ALPHA + } else { + MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY } return MediaArtworkHelper.setUpGradientColorOnDrawable( albumArt, context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable, mutableColorScheme, - alpha, - MEDIA_PLAYER_SCRIM_END_ALPHA, + startAlpha, + endAlpha, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt index 34f7c4dcaec0..c9716be52408 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt @@ -18,6 +18,9 @@ package com.android.systemui.media.controls.ui.binder import android.animation.Animator import android.animation.ObjectAnimator +import android.icu.text.MeasureFormat +import android.icu.util.Measure +import android.icu.util.MeasureUnit import android.text.format.DateUtils import androidx.annotation.UiThread import androidx.lifecycle.Observer @@ -28,8 +31,11 @@ import com.android.systemui.media.controls.ui.drawable.SquigglyProgress import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel import com.android.systemui.res.R +import java.util.Locale private const val TAG = "SeekBarObserver" +private const val MIN_IN_SEC = 60 +private const val HOUR_IN_SEC = MIN_IN_SEC * 60 /** * Observer for changes from SeekBarViewModel. @@ -127,10 +133,9 @@ open class SeekBarObserver(private val holder: MediaViewHolder) : } holder.seekBar.setMax(data.duration) - val totalTimeString = - DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS) + val totalTimeDescription = formatTimeContentDescription(data.duration) if (data.scrubbing) { - holder.scrubbingTotalTimeView.text = totalTimeString + holder.scrubbingTotalTimeView.text = formatTimeLabel(data.duration) } data.elapsedTime?.let { @@ -148,20 +153,62 @@ open class SeekBarObserver(private val holder: MediaViewHolder) : } } - val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS) + val elapsedTimeDescription = formatTimeContentDescription(it) if (data.scrubbing) { - holder.scrubbingElapsedTimeView.text = elapsedTimeString + holder.scrubbingElapsedTimeView.text = formatTimeLabel(it) } holder.seekBar.contentDescription = holder.seekBar.context.getString( R.string.controls_media_seekbar_description, - elapsedTimeString, - totalTimeString + elapsedTimeDescription, + totalTimeDescription, ) } } + /** Returns a time string suitable for display, e.g. "12:34" */ + private fun formatTimeLabel(milliseconds: Int): CharSequence { + return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS) + } + + /** + * Returns a time string suitable for content description, e.g. "12 minutes 34 seconds" + * + * Follows same logic as Chronometer#formatDuration + */ + private fun formatTimeContentDescription(milliseconds: Int): CharSequence { + var seconds = milliseconds / DateUtils.SECOND_IN_MILLIS + + val hours = + if (seconds >= HOUR_IN_SEC) { + seconds / HOUR_IN_SEC + } else { + 0 + } + seconds -= hours * HOUR_IN_SEC + + val minutes = + if (seconds >= MIN_IN_SEC) { + seconds / MIN_IN_SEC + } else { + 0 + } + seconds -= minutes * MIN_IN_SEC + + val measures = arrayListOf<Measure>() + if (hours > 0) { + measures.add(Measure(hours, MeasureUnit.HOUR)) + } + if (minutes > 0) { + measures.add(Measure(minutes, MeasureUnit.MINUTE)) + } + measures.add(Measure(seconds, MeasureUnit.SECOND)) + + return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE) + .formatMeasures(*measures.toTypedArray()) + } + @VisibleForTesting open fun buildResetAnimator(targetTime: Int): Animator { val animator = @@ -169,7 +216,7 @@ open class SeekBarObserver(private val holder: MediaViewHolder) : holder.seekBar, "progress", holder.seekBar.progress, - targetTime + RESET_ANIMATION_DURATION_MS + targetTime + RESET_ANIMATION_DURATION_MS, ) animator.setAutoCancel(true) animator.duration = RESET_ANIMATION_DURATION_MS.toLong() diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 39c08daf53d6..694a4c7e493d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -23,7 +23,10 @@ import static com.android.systemui.Flags.communalHub; import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation; import static com.android.systemui.media.controls.domain.pipeline.MediaActionsKt.getNotificationActions; import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS; -import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_CENTER_ALPHA; +import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA; +import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY; +import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA; +import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY; import android.animation.Animator; import android.animation.AnimatorInflater; @@ -176,9 +179,7 @@ public class MediaControlPanel { protected static final int SMARTSPACE_CARD_DISMISS_EVENT = 761; private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f; - private static final float MEDIA_SCRIM_START_ALPHA = 0.25f; private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f; - private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f; private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f; private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS); @@ -1093,11 +1094,12 @@ public class MediaControlPanel { Drawable albumArt = getScaledBackground(artworkIcon, width, height); GradientDrawable gradient = (GradientDrawable) mContext.getDrawable( R.drawable.qs_media_scrim).mutate(); - float startAlpha = (Flags.mediaControlsA11yColors()) - ? MEDIA_PLAYER_SCRIM_CENTER_ALPHA - : MEDIA_SCRIM_START_ALPHA; + if (Flags.mediaControlsA11yColors()) { + return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme, + MEDIA_PLAYER_SCRIM_START_ALPHA, MEDIA_PLAYER_SCRIM_END_ALPHA); + } return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme, - startAlpha, MEDIA_PLAYER_SCRIM_END_ALPHA); + MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY, MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index 198155b3f297..b687dce20b06 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -1137,6 +1137,7 @@ constructor( ) { gutsViewHolder.gutsText.setTypeface(menuTF) gutsViewHolder.dismissText.setTypeface(menuTF) + gutsViewHolder.cancelText.setTypeface(menuTF) titleText.setTypeface(titleTF) artistText.setTypeface(artistTF) seamlessText.setTypeface(menuTF) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index 9153e17393d2..bcda485272c2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt @@ -419,8 +419,10 @@ class MediaControlViewModel( const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L @Deprecated("Remove with media_controls_a11y_colors flag") - const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.25f - const val MEDIA_PLAYER_SCRIM_CENTER_ALPHA = 0.75f - const val MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f + const val MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY = 0.25f + @Deprecated("Remove with media_controls_a11y_colors flag") + const val MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY = 1.0f + const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.65f + const val MEDIA_PLAYER_SCRIM_END_ALPHA = 0.75f } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt index 24bb16a11fe7..3a81102699f7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt @@ -27,9 +27,7 @@ object QSEvents { private set fun setLoggerForTesting(): UiEventLoggerFake { - return UiEventLoggerFake().also { - qsUiEventsLogger = it - } + return UiEventLoggerFake().also { qsUiEventsLogger = it } } fun resetLogger() { @@ -40,32 +38,28 @@ object QSEvents { enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum { @UiEvent(doc = "Tile clicked. It has an instance id and a spec (or packageName)") QS_ACTION_CLICK(387), - - @UiEvent(doc = "Tile secondary button clicked. " + - "It has an instance id and a spec (or packageName)") + @UiEvent( + doc = + "Tile secondary button clicked. " + "It has an instance id and a spec (or packageName)" + ) QS_ACTION_SECONDARY_CLICK(388), - @UiEvent(doc = "Tile long clicked. It has an instance id and a spec (or packageName)") QS_ACTION_LONG_PRESS(389), - - @UiEvent(doc = "Quick Settings panel expanded") - QS_PANEL_EXPANDED(390), - - @UiEvent(doc = "Quick Settings panel collapsed") - QS_PANEL_COLLAPSED(391), - - @UiEvent(doc = "Tile visible in Quick Settings panel. The tile may be in a different page. " + - "It has an instance id and a spec (or packageName)") + @UiEvent(doc = "Quick Settings panel expanded") QS_PANEL_EXPANDED(390), + @UiEvent(doc = "Quick Settings panel collapsed") QS_PANEL_COLLAPSED(391), + @UiEvent( + doc = + "Tile visible in Quick Settings panel. The tile may be in a different page. " + + "It has an instance id and a spec (or packageName)" + ) QS_TILE_VISIBLE(392), - - @UiEvent(doc = "Quick Quick Settings panel expanded") - QQS_PANEL_EXPANDED(393), - - @UiEvent(doc = "Quick Quick Settings panel collapsed") - QQS_PANEL_COLLAPSED(394), - - @UiEvent(doc = "Tile visible in Quick Quick Settings panel. " + - "It has an instance id and a spec (or packageName)") + @UiEvent(doc = "Quick Quick Settings panel expanded") QQS_PANEL_EXPANDED(393), + @UiEvent(doc = "Quick Quick Settings panel collapsed") QQS_PANEL_COLLAPSED(394), + @UiEvent( + doc = + "Tile visible in Quick Quick Settings panel. " + + "It has an instance id and a spec (or packageName)" + ) QQS_TILE_VISIBLE(395); override fun getId() = _id @@ -73,47 +67,32 @@ enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum { enum class QSEditEvent(private val _id: Int) : UiEventLogger.UiEventEnum { - @UiEvent(doc = "Tile removed from current tiles") - QS_EDIT_REMOVE(210), - - @UiEvent(doc = "Tile added to current tiles") - QS_EDIT_ADD(211), - - @UiEvent(doc = "Tile moved") - QS_EDIT_MOVE(212), - - @UiEvent(doc = "QS customizer open") - QS_EDIT_OPEN(213), - - @UiEvent(doc = "QS customizer closed") - QS_EDIT_CLOSED(214), - - @UiEvent(doc = "QS tiles reset") - QS_EDIT_RESET(215); + @UiEvent(doc = "Tile removed from current tiles") QS_EDIT_REMOVE(210), + @UiEvent(doc = "Tile added to current tiles") QS_EDIT_ADD(211), + @UiEvent(doc = "Tile moved") QS_EDIT_MOVE(212), + @UiEvent(doc = "QS customizer open") QS_EDIT_OPEN(213), + @UiEvent(doc = "QS customizer closed") QS_EDIT_CLOSED(214), + @UiEvent(doc = "QS tiles reset") QS_EDIT_RESET(215), + @UiEvent(doc = "QS edit mode resize tile to large") QS_EDIT_RESIZE_LARGE(2122), + @UiEvent(doc = "QS edit mode resize tile to small") QS_EDIT_RESIZE_SMALL(2123); override fun getId() = _id } /** - * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger} - * Other names for DND (Do Not Disturb) include "Zen" and "Priority mode". + * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger} Other names for DND (Do + * Not Disturb) include "Zen" and "Priority mode". */ enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum { - @UiEvent(doc = "User selected an option on the DND dialog") - QS_DND_CONDITION_SELECT(420), - + @UiEvent(doc = "User selected an option on the DND dialog") QS_DND_CONDITION_SELECT(420), @UiEvent(doc = "User increased countdown duration of DND from the DND dialog") QS_DND_TIME_UP(422), - @UiEvent(doc = "User decreased countdown duration of DND from the DND dialog") QS_DND_TIME_DOWN(423), - @UiEvent(doc = "User enabled DND from the QS DND dialog to last until manually turned off") QS_DND_DIALOG_ENABLE_FOREVER(946), - @UiEvent(doc = "User enabled DND from the QS DND dialog to last until the next alarm goes off") QS_DND_DIALOG_ENABLE_UNTIL_ALARM(947), - @UiEvent(doc = "User enabled DND from the QS DND dialog to last until countdown is done") QS_DND_DIALOG_ENABLE_UNTIL_COUNTDOWN(948); @@ -121,29 +100,17 @@ enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum { } enum class QSUserSwitcherEvent(private val _id: Int) : UiEventLogger.UiEventEnum { - @UiEvent(doc = "The current user has been switched in the detail panel") - QS_USER_SWITCH(424), - - @UiEvent(doc = "User switcher QS dialog open") - QS_USER_DETAIL_OPEN(425), - - @UiEvent(doc = "User switcher QS dialog closed") - QS_USER_DETAIL_CLOSE(426), - - @UiEvent(doc = "User switcher QS dialog more settings pressed") - QS_USER_MORE_SETTINGS(427), - - @UiEvent(doc = "The user has added a guest in the detail panel") - QS_USER_GUEST_ADD(754), - + @UiEvent(doc = "The current user has been switched in the detail panel") QS_USER_SWITCH(424), + @UiEvent(doc = "User switcher QS dialog open") QS_USER_DETAIL_OPEN(425), + @UiEvent(doc = "User switcher QS dialog closed") QS_USER_DETAIL_CLOSE(426), + @UiEvent(doc = "User switcher QS dialog more settings pressed") QS_USER_MORE_SETTINGS(427), + @UiEvent(doc = "The user has added a guest in the detail panel") QS_USER_GUEST_ADD(754), @UiEvent(doc = "The user selected 'Start over' after switching to the existing Guest user") QS_USER_GUEST_WIPE(755), - @UiEvent(doc = "The user selected 'Yes, continue' after switching to the existing Guest user") QS_USER_GUEST_CONTINUE(756), - @UiEvent(doc = "The user has pressed 'Remove guest' in the detail panel") QS_USER_GUEST_REMOVE(757); override fun getId() = _id -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt index 482cd4014acf..3f279b0f7a74 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt @@ -16,15 +16,18 @@ package com.android.systemui.qs.panels.domain.interactor +import com.android.internal.logging.UiEventLogger 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.LogLevel +import com.android.systemui.qs.QSEditEvent import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository import com.android.systemui.qs.panels.data.repository.LargeTileSpanRepository import com.android.systemui.qs.panels.shared.model.PanelsLog import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.metricSpec import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -40,6 +43,7 @@ constructor( private val repo: DefaultLargeTilesRepository, private val currentTilesInteractor: CurrentTilesInteractor, private val preferencesInteractor: QSPreferencesInteractor, + private val uiEventLogger: UiEventLogger, largeTilesSpanRepo: LargeTileSpanRepository, @PanelsLog private val logBuffer: LogBuffer, @Application private val applicationScope: CoroutineScope, @@ -70,8 +74,18 @@ constructor( val isIcon = !largeTilesSpecs.value.contains(spec) if (toIcon && !isIcon) { preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec) + uiEventLogger.log( + /* event= */ QSEditEvent.QS_EDIT_RESIZE_SMALL, + /* uid= */ 0, + /* packageName= */ spec.metricSpec, + ) } else if (!toIcon && isIcon) { preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec) + uiEventLogger.log( + /* event= */ QSEditEvent.QS_EDIT_RESIZE_LARGE, + /* uid= */ 0, + /* packageName= */ spec.metricSpec, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt index 4adc1a5ae746..20b44d73e097 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt @@ -22,7 +22,9 @@ import android.icu.text.DateFormat import android.icu.text.DisplayContext import android.provider.Settings import android.view.ViewGroup +import androidx.compose.material3.ColorScheme import androidx.compose.runtime.getValue +import androidx.compose.ui.graphics.Color import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.lifecycle.ExclusiveActivatable @@ -244,11 +246,29 @@ constructor( /** Represents the background highlight of a header icons chip. */ sealed interface HeaderChipHighlight { - data object None : HeaderChipHighlight - data object Weak : HeaderChipHighlight + fun backgroundColor(colorScheme: ColorScheme): Color - data object Strong : HeaderChipHighlight + fun foregroundColor(colorScheme: ColorScheme): Color + + data object None : HeaderChipHighlight { + override fun backgroundColor(colorScheme: ColorScheme): Color = Color.Unspecified + + override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary + } + + data object Weak : HeaderChipHighlight { + override fun backgroundColor(colorScheme: ColorScheme): Color = + colorScheme.primary.copy(alpha = 0.1f) + + override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary + } + + data object Strong : HeaderChipHighlight { + override fun backgroundColor(colorScheme: ColorScheme): Color = colorScheme.secondary + + override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.onSecondary + } } private fun getFormatFromPattern(pattern: String?): DateFormat { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java index 0e3f103c152e..24ab6959b9e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java @@ -21,9 +21,14 @@ import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; +import android.app.Notification; +import android.content.Context; +import android.os.Build; + import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.systemui.statusbar.notification.icon.IconPack; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import java.util.List; @@ -79,6 +84,43 @@ public class BundleEntry extends PipelineEntry { public EntryAdapter getGroupRoot() { return this; } + + @Override + public boolean isClearable() { + // TODO(b/394483200): check whether all of the children are clearable, when implemented + return true; + } + + @Override + public int getTargetSdk() { + return Build.VERSION_CODES.CUR_DEVELOPMENT; + } + + @Override + public String getSummarization() { + return null; + } + + @Override + public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) { + return Notification.COLOR_DEFAULT; + } + + @Override + public boolean canPeek() { + return false; + } + + @Override + public long getWhen() { + return 0; + } + + @Override + public IconPack getIcons() { + // TODO(b/396446620): implement bundle icons + return null; + } } public static final List<BundleEntry> ROOT_BUNDLES = List.of( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java index 4df81c97e21e..6431cacf2107 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.notification.collection; +import android.content.Context; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.systemui.statusbar.notification.icon.IconPack; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; /** @@ -59,9 +62,49 @@ public interface EntryAdapter { EntryAdapter getGroupRoot(); /** + * @return whether the row can be removed with the 'Clear All' action + */ + boolean isClearable(); + + /** * Returns whether the entry is attached to the current shade list */ default boolean isAttached() { return getParent() != null; } + + /** + * Returns the target sdk of the package that owns this entry. + */ + int getTargetSdk(); + + /** + * Returns the summarization for this entry, if there is one + */ + @Nullable String getSummarization(); + + /** + * Performs any steps needed to set or reset data before an inflation or reinflation. + */ + default void prepareForInflation() {} + + /** + * Gets a color that would have sufficient contrast on the given background color. + */ + int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor); + + /** + * Whether this entry can peek on screen as a heads up view + */ + boolean canPeek(); + + /** + * Returns the visible 'time', in milliseconds, of the entry + */ + long getWhen(); + + /** + * Retrieves the pack of icons associated with this entry + */ + IconPack getIcons(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 90f9525c7683..698a56363465 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -78,6 +78,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel; import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.PriorityBucket; import com.android.systemui.util.ListenerSet; @@ -309,6 +310,47 @@ public final class NotificationEntry extends ListEntry { } return null; } + + @Override + public boolean isClearable() { + return NotificationEntry.this.isClearable(); + } + + @Override + public int getTargetSdk() { + return NotificationEntry.this.targetSdk; + } + + @Override + public String getSummarization() { + return getRanking().getSummarization(); + } + + @Override + public void prepareForInflation() { + getSbn().clearPackageContext(); + } + + @Override + public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) { + return NotificationEntry.this.getContrastedColor( + context, isLowPriority, backgroundColor); + } + + @Override + public boolean canPeek() { + return isStickyAndNotDemoted(); + } + + @Override + public long getWhen() { + return getSbn().getNotification().getWhen(); + } + + @Override + public IconPack getIcons() { + return NotificationEntry.this.getIcons(); + } } public EntryAdapter getEntryAdapter() { @@ -580,6 +622,7 @@ public final class NotificationEntry extends ListEntry { } public boolean hasFinishedInitialization() { + NotificationBundleUi.assertInLegacyMode(); return initializationTime != -1 && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY; } @@ -663,10 +706,12 @@ public final class NotificationEntry extends ListEntry { } public void resetInitializationTime() { + NotificationBundleUi.assertInLegacyMode(); initializationTime = -1; } public void setInitializationTime(long time) { + NotificationBundleUi.assertInLegacyMode(); if (initializationTime == -1) { initializationTime = time; } @@ -683,9 +728,13 @@ public final class NotificationEntry extends ListEntry { * @return {@code true} if we are a media notification */ public boolean isMediaNotification() { - if (row == null) return false; + if (NotificationBundleUi.isEnabled()) { + return getSbn().getNotification().isMediaNotification(); + } else { + if (row == null) return false; - return row.isMediaRow(); + return row.isMediaRow(); + } } public boolean containsCustomViews() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 9c1d0735a65b..ac11c15e8bf8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -67,6 +67,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.util.Assert; import com.android.systemui.util.NamedListenerSet; @@ -1282,7 +1283,13 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { entry.getAttachState().setExcludingFilter(filter); if (filter != null) { // notification is removed from the list, so we reset its initialization time - entry.resetInitializationTime(); + if (NotificationBundleUi.isEnabled()) { + if (entry.getRow() != null) { + entry.getRow().resetInitializationTime(); + } + } else { + entry.resetInitializationTime(); + } } return filter != null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 64cd6174a585..473460b77e46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -46,6 +46,7 @@ import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; @@ -117,7 +118,6 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.logging.NotificationCounters; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper; @@ -169,6 +169,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); private static final SourceType BASE_VALUE = SourceType.from("BaseValue"); private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)"); + private static final long INITIALIZATION_DELAY = 400; // We don't correctly track dark mode until the content views are inflated, so always update // the background on first content update just in case it happens to be during a theme change. @@ -267,7 +268,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private NotificationContentView[] mLayouts; - private int mNotificationColor; private ExpandableNotificationRowLogger mLogger; private String mLoggingKey; private NotificationGuts mGuts; @@ -352,6 +352,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ private boolean mSaveSpaceOnLockscreen; + // indicates when this view was first attached to a window + // this value will reset when the view is completely removed from the shade (ie: filtered out) + private long initializationTime = -1; + /** * It is added for unit testing purpose. * Please do not use it for other purposes. @@ -625,26 +629,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Marks a content view as freeable, setting it so that future inflations do not reinflate - * and ensuring that the view is freed when it is safe to remove. - * - * @param inflationFlag flag corresponding to the content view to be freed - * @deprecated By default, {@link NotificationRowContentBinder#unbindContent} will tell the - * view hierarchy to only free when the view is safe to remove so this method is no longer - * needed. Will remove when all uses are gone. - */ - @Deprecated - public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { - RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); - params.markContentViewsFreeable(inflationFlag); - mRowContentBindStage.requestRebind(mEntry, null /* callback */); - } - - /** * Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif * or is in an allowList). */ public boolean getIsNonblockable() { + NotificationBundleUi.assertInLegacyMode(); if (mEntry == null) { return true; } @@ -666,9 +655,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.onNotificationUpdated(mEntry); } mShowingPublicInitialized = false; - updateNotificationColor(); if (mMenuRow != null) { - mMenuRow.onNotificationUpdated(mEntry.getSbn()); + mMenuRow.onNotificationUpdated(); mMenuRow.setAppName(mAppName); } if (mIsSummaryWithChildren) { @@ -727,15 +715,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Called when the notification's ranking was changed (but nothing else changed). - */ - public void onNotificationRankingUpdated() { - if (mMenuRow != null) { - mMenuRow.onNotificationUpdated(mEntry.getSbn()); - } - } - - /** * Call when bubble state has changed and the button on the notification should be updated. */ public void updateBubbleButton() { @@ -746,7 +725,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @VisibleForTesting void updateShelfIconColor() { - StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon(); + StatusBarIconView expandedIcon = getShelfIcon(); boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L)); boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon, ContrastColorUtil.getInstance(mContext)); @@ -767,8 +746,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (color != Notification.COLOR_INVALID) { return color; } else { - return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(), - getBackgroundColorWithoutTint()); + if (NotificationBundleUi.isEnabled()) { + return mEntryAdapter.getContrastedColor(mContext, mIsMinimized && !isExpanded(), + getBackgroundColorWithoutTint()); + } else { + return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(), + getBackgroundColorWithoutTint()); + } } } @@ -870,15 +854,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView boolean customView = contractedView != null && contractedView.getId() != com.android.internal.R.id.status_bar_latest_event_content; - boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; - boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P; - boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S; + int targetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT; + if (NotificationBundleUi.isEnabled()) { + targetSdk = mEntryAdapter.getTargetSdk(); + } else { + targetSdk = mEntry.targetSdk; + } + + boolean beforeN = targetSdk < Build.VERSION_CODES.N; + boolean beforeP = targetSdk < Build.VERSION_CODES.P; + boolean beforeS = targetSdk < Build.VERSION_CODES.S; int smallHeight; boolean isCallLayout = contractedView instanceof CallLayout; boolean isMessagingLayout = contractedView instanceof MessagingLayout || contractedView instanceof ConversationLayout; + String summarization = null; + if (NotificationBundleUi.isEnabled()) { + summarization = mEntryAdapter.getSummarization(); + } else { + summarization = mEntry.getRanking().getSummarization(); + } + if (customView && beforeS && !mIsSummaryWithChildren) { if (beforeN) { smallHeight = mMaxSmallHeightBeforeN; @@ -891,7 +889,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView smallHeight = maxExpandedHeight; } else if (NmSummarizationUiFlag.isEnabled() && isMessagingLayout - && !TextUtils.isEmpty(mEntry.getRanking().getSummarization())) { + && !TextUtils.isEmpty(summarization)) { smallHeight = mMaxSmallHeightWithSummarization; } else { smallHeight = mMaxSmallHeight; @@ -1524,7 +1522,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public boolean hasFinishedInitialization() { - return getEntry().hasFinishedInitialization(); + if (NotificationBundleUi.isEnabled()) { + return initializationTime != -1 + && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY; + } else { + return getEntry().hasFinishedInitialization(); + } + } + + public void resetInitializationTime() { + initializationTime = -1; + } + + public void setInitializationTime(long time) { + if (initializationTime == -1) { + initializationTime = time; + } } /** @@ -1539,7 +1552,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return null; } if (mMenuRow.getMenuView() == null) { - mMenuRow.createMenu(this, mEntry.getSbn()); + mMenuRow.createMenu(this); mMenuRow.setAppName(mAppName); FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); @@ -1581,7 +1594,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (oldMenu != null) { int menuIndex = indexOfChild(oldMenu); removeView(oldMenu); - mMenuRow.createMenu(ExpandableNotificationRow.this, mEntry.getSbn()); + mMenuRow.createMenu(ExpandableNotificationRow.this); mMenuRow.setAppName(mAppName); addView(mMenuRow.getMenuView(), menuIndex); } @@ -1589,12 +1602,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.reinflate(); l.reInflateViews(); } - mEntry.getSbn().clearPackageContext(); + if (NotificationBundleUi.isEnabled()) { + mEntryAdapter.prepareForInflation(); + } else { + mEntry.getSbn().clearPackageContext(); + } // TODO: Move content inflation logic out of this call RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); params.setNeedsReinflation(true); - var rebindEndCallback = mRebindingTracker.trackRebinding(mEntry.getKey()); + var rebindEndCallback = mRebindingTracker.trackRebinding(NotificationBundleUi.isEnabled() + ? mEntryAdapter.getKey() : mEntry.getKey()); mRowContentBindStage.requestRebind(mEntry, (e) -> rebindEndCallback.onFinished()); Trace.endSection(); } @@ -1658,20 +1676,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout.setSingleLineWidthIndention(indention); } - public int getNotificationColor() { - return mNotificationColor; - } - - public void updateNotificationColor() { - Configuration currentConfig = getResources().getConfiguration(); - boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - - mNotificationColor = ContrastColorUtil.resolveContrastColor(mContext, - mEntry.getSbn().getNotification().color, - getBackgroundColorWithoutTint(), nightMode); - } - public HybridNotificationView getSingleLineView() { return mPrivateLayout.getSingleLineView(); } @@ -2260,6 +2264,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mEntry = entry; } + @VisibleForTesting + protected void setEntryAdapter(EntryAdapter entry) { + mEntryAdapter = entry; + } + private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false); private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) { @@ -2497,7 +2506,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mTranslateableViews.get(i).setTranslationX(0); } invalidateOutline(); - getEntry().getIcons().getShelfIcon().setScrollX(0); + getShelfIcon().setScrollX(0); } if (mMenuRow != null) { @@ -2616,7 +2625,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // In order to keep the shelf in sync with this swiping, we're simply translating // it's icon by the same amount. The translation is already being used for the normal // positioning, so we can use the scrollX instead. - getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX); + getShelfIcon().setScrollX((int) -translationX); } if (mMenuRow != null && mMenuRow.getMenuView() != null) { @@ -2843,7 +2852,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public @NonNull StatusBarIconView getShelfIcon() { - return getEntry().getIcons().getShelfIcon(); + if (NotificationBundleUi.isEnabled()) { + return getEntryAdapter().getIcons().getShelfIcon(); + } else { + return mEntry.getIcons().getShelfIcon(); + } } @Override @@ -3072,8 +3085,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * except for legacy use cases. */ public boolean canShowHeadsUp() { + boolean canEntryHun = NotificationBundleUi.isEnabled() + ? mEntryAdapter.canPeek() + : mEntry.isStickyAndNotDemoted(); if (mOnKeyguard && !isDozing() && !isBypassEnabled() && - (!mEntry.isStickyAndNotDemoted() + (!canEntryHun || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) { return false; } @@ -3112,7 +3128,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } if (!mIsSummaryWithChildren && wasSummary) { // Reset the 'when' once the row stops being a summary - mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen()); + if (NotificationBundleUi.isEnabled()) { + mPublicLayout.setNotificationWhen(mEntryAdapter.getWhen()); + } else { + mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen()); + } } getShowingLayout().updateBackgroundColor(false /* animate */); mPrivateLayout.updateExpandButtons(isExpandable()); @@ -3381,7 +3401,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ @Override public boolean canExpandableViewBeDismissed() { - if (areGutsExposed() || !mEntry.hasFinishedInitialization()) { + if (areGutsExposed() || !hasFinishedInitialization()) { return false; } return canViewBeDismissed(); @@ -3405,7 +3425,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * clearability see {@link NotificationEntry#isClearable()}. */ public boolean canViewBeCleared() { - return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + if (NotificationBundleUi.isEnabled()) { + return mEntryAdapter.isClearable() + && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + } else { + return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + } } private boolean shouldShowPublic() { @@ -4082,6 +4107,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public boolean isMediaRow() { + NotificationBundleUi.assertInLegacyMode(); return mEntry.getSbn().getNotification().isMediaNotification(); } @@ -4204,7 +4230,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void dump(PrintWriter pwOriginal, String[] args) { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); // Skip super call; dump viewState ourselves - pw.println("Notification: " + mEntry.getKey()); + if (NotificationBundleUi.isEnabled()) { + pw.println("Notification: " + mEntryAdapter.getKey()); + } else { + pw.println("Notification: " + mEntry.getKey()); + } DumpUtilsKt.withIncreasedIndent(pw, () -> { pw.println(this); pw.print("visibility: " + getVisibility()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index b43a387a5edb..07711b6e0eb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -374,7 +374,11 @@ public class ExpandableNotificationRowController implements NotifViewController mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { - mView.getEntry().setInitializationTime(mClock.elapsedRealtime()); + if (NotificationBundleUi.isEnabled()) { + mView.setInitializationTime(mClock.elapsedRealtime()); + } else { + mView.getEntry().setInitializationTime(mClock.elapsedRealtime()); + } mPluginManager.addPluginListener(mView, NotificationMenuRowPlugin.class, false /* Allow multiple */); if (!SceneContainerFlag.isEnabled()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6e638f5de209..9a75253295d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -73,6 +73,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.row.icon.AppIconProvider; import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -436,7 +437,9 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta onNasFeedbackClick, mUiEventLogger, mDeviceProvisionedController.isDeviceProvisioned(), - row.getIsNonblockable(), + NotificationBundleUi.isEnabled() + ? !row.getEntry().isBlockable() + : row.getIsNonblockable(), mHighPriorityProvider.isHighPriority(row.getEntry()), mAssistantFeedbackController, mMetricsLogger, @@ -480,7 +483,9 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta row.getEntry(), onSettingsClick, mDeviceProvisionedController.isDeviceProvisioned(), - row.getIsNonblockable()); + NotificationBundleUi.isEnabled() + ? !row.getEntry().isBlockable() + : row.getIsNonblockable()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index b96b224a7d2e..ab382df13d10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -186,7 +186,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } @Override - public void createMenu(ViewGroup parent, StatusBarNotification sbn) { + public void createMenu(ViewGroup parent) { mParent = (ExpandableNotificationRow) parent; createMenuViews(true /* resetState */); } @@ -227,7 +227,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } @Override - public void onNotificationUpdated(StatusBarNotification sbn) { + public void onNotificationUpdated() { if (mMenuContainer == null) { // Menu hasn't been created yet, no need to do anything. return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 3d60e03d7ca4..3ff18efeae53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -6686,7 +6686,7 @@ public class NotificationStackScrollLayout static boolean canChildBeCleared(View v) { if (v instanceof ExpandableNotificationRow row) { - if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) { + if (row.areGutsExposed() || !row.hasFinishedInitialization()) { return false; } return row.canViewBeCleared(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 4ff09d3bc6af..e8b50d580c33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -45,6 +45,9 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; @@ -74,10 +77,14 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -127,6 +134,9 @@ public class ShadeListBuilderTest extends SysuiTestCase { private TestableStabilityManager mStabilityManager; private TestableNotifFilter mFinalizeFilter; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private Map<String, Integer> mNextIdMap = new ArrayMap<>(); private int mNextRank = 0; @@ -561,6 +571,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void testFilter_resetsInitalizationTime() { // GIVEN a NotifFilter that filters out a specific package NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1)); @@ -584,6 +595,31 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + public void testFilter_resetsInitializationTime_onRow() throws Exception { + // GIVEN a NotifFilter that filters out a specific package + NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1)); + mListBuilder.addFinalizeFilter(filter1); + + // GIVEN a notification that was initialized 1 second ago that will be filtered out + final NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(PACKAGE_1) + .setId(nextId(PACKAGE_1)) + .setRank(nextRank()) + .build(); + entry.setRow(new NotificationTestHelper(mContext, mDependency).createRow()); + entry.getRow().setInitializationTime(SystemClock.elapsedRealtime() - 1000); + assertTrue(entry.getRow().hasFinishedInitialization()); + + // WHEN the pipeline is kicked off + mReadyForBuildListener.onBuildList(singletonList(entry), "test"); + mPipelineChoreographer.runIfScheduled(); + + // THEN the entry's initialization time is reset + assertFalse(entry.getRow().hasFinishedInitialization()); + } + + @Test public void testNotifFiltersCanBePreempted() { // GIVEN two notif filters NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 1b5353127f25..24d8d1cc8fae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -75,6 +75,7 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -589,6 +590,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test + @DisableFlags(NotificationBundleUi.FLAG_NAME) public void testGetIsNonblockable() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 47238fedee4d..c874bc6056c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -36,6 +36,7 @@ import com.android.internal.widget.NotificationActionListLayout import com.android.internal.widget.NotificationExpandButton import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.FeedbackIcon +import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.shared.NotificationBundleUi @@ -82,6 +83,7 @@ class NotificationContentViewTest : SysuiTestCase() { fakeParent = spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE }) val mockEntry = createMockNotificationEntry() + val mockEntryAdapter = createMockNotificationEntryAdapter() row = spy( when (NotificationBundleUi.isEnabled) { @@ -92,6 +94,7 @@ class NotificationContentViewTest : SysuiTestCase() { UserHandle.CURRENT ).apply { entry = mockEntry + entryAdapter = mockEntryAdapter } } false -> { @@ -611,6 +614,7 @@ class NotificationContentViewTest : SysuiTestCase() { whenever(this.entry).thenReturn(notificationEntry) whenever(this.context).thenReturn(mContext) whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {}) + whenever(this.entryAdapter).thenReturn(createMockNotificationEntryAdapter()) } private fun createMockNotificationEntry() = @@ -624,6 +628,9 @@ class NotificationContentViewTest : SysuiTestCase() { whenever(sbnMock.user).thenReturn(userMock) } + private fun createMockNotificationEntryAdapter() = + mock<EntryAdapter>() + private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout { val outerLayout = LinearLayout(mContext) val innerLayout = LinearLayout(mContext) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index 3d4c90140adb..99b99ee1860b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -427,7 +427,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) .setImportance(NotificationManager.IMPORTANCE_HIGH) .build() - whenever(row.getIsNonblockable()).thenReturn(false) whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) val statusBarNotification = entry.sbn gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -463,7 +462,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.getIsNonblockable()).thenReturn(false) val statusBarNotification = row.entry.sbn val entry = row.entry gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -499,7 +497,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { NotificationEntryHelper.modifyRanking(row.entry) .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) .build() - whenever(row.getIsNonblockable()).thenReturn(false) val statusBarNotification = row.entry.sbn val entry = row.entry gutsManager.initializeNotificationInfo(row, notificationInfoView) @@ -566,7 +563,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { ): NotificationMenuRowPlugin.MenuItem { val menuRow: NotificationMenuRowPlugin = NotificationMenuRow(mContext, peopleNotificationIdentifier) - menuRow.createMenu(row, row!!.entry.sbn) + menuRow.createMenu(row) val menuItem = menuRow.getLongpressMenuItem(mContext) Assert.assertNotNull(menuItem) return menuItem diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 6ac20d40f2dc..955de273c426 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -675,7 +675,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testClearNotifications_clearAllInProgress() { ExpandableNotificationRow row = createClearableRow(); - when(row.getEntry().hasFinishedInitialization()).thenReturn(true); + when(row.hasFinishedInitialization()).thenReturn(true); doReturn(true).when(mStackScroller).isVisible(row); mStackScroller.addContainerView(row); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt index 8d4db8b74061..8a6f68c5da68 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.panels.domain.interactor +import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.core.FakeLogBuffer @@ -29,6 +30,7 @@ val Kosmos.iconTilesInteractor by defaultLargeTilesRepository, currentTilesInteractor, qsPreferencesInteractor, + uiEventLoggerFake, largeTileSpanRepository, FakeLogBuffer.Factory.create(), applicationCoroutineScope, diff --git a/packages/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp index edb22c0e7aa0..c53123359872 100644 --- a/packages/Vcn/framework-b/Android.bp +++ b/packages/Vcn/framework-b/Android.bp @@ -77,8 +77,7 @@ framework_connectivity_b_defaults_soong_config { ], soong_config_variables: { is_vcn_in_mainline: { - //TODO: b/380155299 Make it Baklava when aidl tool can understand it - min_sdk_version: "current", + min_sdk_version: "36", static_libs: ["android.net.vcn.flags-aconfig-java"], apex_available: ["com.android.tethering"], diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index 4fa0d506f09e..aa82df493f84 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -101,8 +101,15 @@ public class AutoclickController extends BaseEventStreamTransformation { } @Override - public void toggleAutoclickPause() { - // TODO(b/388872274): allows users to pause the autoclick. + public void toggleAutoclickPause(boolean paused) { + if (paused) { + if (mClickScheduler != null) { + mClickScheduler.cancel(); + } + if (mAutoclickIndicatorScheduler != null) { + mAutoclickIndicatorScheduler.cancel(); + } + } } }; @@ -133,7 +140,9 @@ public class AutoclickController extends BaseEventStreamTransformation { mAutoclickIndicatorScheduler); } - handleMouseMotion(event, policyFlags); + if (!isPaused()) { + handleMouseMotion(event, policyFlags); + } } else if (mClickScheduler != null) { mClickScheduler.cancel(); } @@ -216,6 +225,11 @@ public class AutoclickController extends BaseEventStreamTransformation { } } + private boolean isPaused() { + // TODO (b/397460424): Unpause when hovering over panel. + return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused(); + } + /** * Observes autoclick setting values, and updates ClickScheduler delay and indicator size * whenever the setting value changes. diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java index 23c5cc4111f6..ba3e3d14b9c6 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java @@ -79,11 +79,20 @@ public class AutoclickTypePanel { // An interface exposed to {@link AutoclickController) to handle different actions on the panel, // including changing autoclick type, pausing/resuming autoclick. public interface ClickPanelControllerInterface { - // Allows users to change a different autoclick type. + /** + * Allows users to change a different autoclick type. + * + * @param clickType The new autoclick type to use. Should be one of the values defined in + * {@link AutoclickType}. + */ void handleAutoclickTypeChange(@AutoclickType int clickType); - // Allows users to pause/resume the autoclick. - void toggleAutoclickPause(); + /** + * Allows users to pause or resume autoclick. + * + * @param paused {@code true} to pause autoclick, {@code false} to resume. + */ + void toggleAutoclickPause(boolean paused); } private final Context mContext; @@ -211,6 +220,10 @@ public class AutoclickTypePanel { mWindowManager.removeView(mContentView); } + public boolean isPaused() { + return mPaused; + } + /** Toggles the panel expanded or collapsed state. */ private void togglePanelExpansion(@AutoclickType int clickType) { final LinearLayout button = getButtonFromClickType(clickType); @@ -234,6 +247,7 @@ public class AutoclickTypePanel { private void togglePause() { mPaused = !mPaused; + mClickPanelController.toggleAutoclickPause(mPaused); ImageButton imageButton = (ImageButton) mPauseButton.getChildAt(/* index= */ 0); if (mPaused) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 61c5501a7b5a..13d367a95942 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -446,6 +446,8 @@ public class OomAdjuster { private static final int CACHING_UI_SERVICE_CLIENT_ADJ_THRESHOLD = Flags.raiseBoundUiServiceThreshold() ? SERVICE_ADJ : PERCEPTIBLE_APP_ADJ; + static final long PERCEPTIBLE_TASK_TIMEOUT_MILLIS = 5 * 60 * 1000; + @VisibleForTesting public static class Injector { boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId, @@ -1847,7 +1849,7 @@ public class OomAdjuster { mHasVisibleActivities = false; } - void onOtherActivity() { + void onOtherActivity(long perceptibleTaskStoppedTimeMillis) { if (procState > PROCESS_STATE_CACHED_ACTIVITY) { procState = PROCESS_STATE_CACHED_ACTIVITY; mAdjType = "cch-act"; @@ -1856,6 +1858,28 @@ public class OomAdjuster { "Raise procstate to cached activity: " + app); } } + if (Flags.perceptibleTasks() && adj > PERCEPTIBLE_MEDIUM_APP_ADJ) { + if (perceptibleTaskStoppedTimeMillis >= 0) { + final long now = mInjector.getUptimeMillis(); + if (now - perceptibleTaskStoppedTimeMillis < PERCEPTIBLE_TASK_TIMEOUT_MILLIS) { + adj = PERCEPTIBLE_MEDIUM_APP_ADJ; + mAdjType = "perceptible-act"; + if (procState > PROCESS_STATE_IMPORTANT_BACKGROUND) { + procState = PROCESS_STATE_IMPORTANT_BACKGROUND; + } + + maybeSetProcessFollowUpUpdateLocked(app, + perceptibleTaskStoppedTimeMillis + PERCEPTIBLE_TASK_TIMEOUT_MILLIS, + now); + } else if (adj > PREVIOUS_APP_ADJ) { + adj = PREVIOUS_APP_ADJ; + mAdjType = "stale-perceptible-act"; + if (procState > PROCESS_STATE_LAST_ACTIVITY) { + procState = PROCESS_STATE_LAST_ACTIVITY; + } + } + } + } mHasVisibleActivities = false; } } diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index b0f808b39053..25175e6bee5f 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -1120,7 +1120,8 @@ final class ProcessStateRecord { } else if ((flags & ACTIVITY_STATE_FLAG_IS_STOPPING) != 0) { callback.onStoppingActivity((flags & ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) != 0); } else { - callback.onOtherActivity(); + final long ts = mApp.getWindowProcessController().getPerceptibleTaskStoppedTimeMillis(); + callback.onOtherActivity(ts); } mCachedAdj = callback.adj; diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 27c384a22fb6..c8fedf3d1765 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -293,6 +293,13 @@ flag { } flag { + name: "perceptible_tasks" + namespace: "system_performance" + description: "Boost the oom_score_adj of activities in perceptible tasks" + bug: "370890207" +} + +flag { name: "expedite_activity_launch_on_cold_start" namespace: "system_performance" description: "Notify ActivityTaskManager of cold starts early to fix app launch behavior." diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 0fc182f3f1bb..fff812c038e7 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -42,6 +42,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; +import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE; import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE; import android.annotation.FlaggedApi; @@ -286,7 +287,7 @@ public class PreferencesHelper implements RankingConfig { if (!TAG_RANKING.equals(tag)) return; final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1); - boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE; + boolean upgradeForBubbles = xmlVersion >= XML_VERSION_BUBBLES_UPGRADE; boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION); if (mShowReviewPermissionsNotification && (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) { @@ -337,15 +338,19 @@ public class PreferencesHelper implements RankingConfig { } boolean skipWarningLogged = false; boolean skipGroupWarningLogged = false; - boolean hasSAWPermission = false; - if (upgradeForBubbles && uid != UNKNOWN_UID) { - hasSAWPermission = mAppOps.noteOpNoThrow( - OP_SYSTEM_ALERT_WINDOW, uid, name, null, - "check-notif-bubble") == AppOpsManager.MODE_ALLOWED; - } - int bubblePref = hasSAWPermission - ? BUBBLE_PREFERENCE_ALL - : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE); + int bubblePref = parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, + DEFAULT_BUBBLE_PREFERENCE); + boolean bubbleLocked = (parser.getAttributeInt(null, + ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS) & USER_LOCKED_BUBBLE) + != 0; + if (!bubbleLocked + && upgradeForBubbles + && uid != UNKNOWN_UID + && mAppOps.noteOpNoThrow(OP_SYSTEM_ALERT_WINDOW, uid, name, null, + "check-notif-bubble") == AppOpsManager.MODE_ALLOWED) { + // User hasn't changed bubble pref & the app has SAW, so allow all bubbles. + bubblePref = BUBBLE_PREFERENCE_ALL; + } int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); // when data is loaded from disk it's loaded as USER_ALL, but restored data that diff --git a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java index 6ce2cf738192..ab2489c81449 100644 --- a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java @@ -119,6 +119,8 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength]; mWakelockDescriptor = null; + + initEnergyConsumerToPowerBracketMaps(); } /** @@ -157,9 +159,6 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { if (mPlan == null) { mPlan = new PowerEstimationPlan(stats.getConfig()); - if (mStatsLayout.getEnergyConsumerCount() != 0) { - initEnergyConsumerToPowerBracketMaps(); - } } Intermediates intermediates = new Intermediates(); @@ -255,6 +254,10 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { */ private void initEnergyConsumerToPowerBracketMaps() { int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); + if (energyConsumerCount == 0) { + return; + } + int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount]; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9692b69b1256..aa3b500c05f6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -493,6 +493,7 @@ final class ActivityRecord extends WindowToken { private long createTime = System.currentTimeMillis(); long lastVisibleTime; // last time this activity became visible long pauseTime; // last time we started pausing the activity + long mStoppedTime; // last time we completely stopped the activity long launchTickTime; // base time for launch tick messages long topResumedStateLossTime; // last time we reported top resumed state loss to an activity // Last configuration reported to the activity in the client process. @@ -6447,6 +6448,7 @@ final class ActivityRecord extends WindowToken { Slog.w(TAG, "Exception thrown during pause", e); // Just in case, assume it to be stopped. mAppStopped = true; + mStoppedTime = SystemClock.uptimeMillis(); ProtoLog.v(WM_DEBUG_STATES, "Stop failed; moving to STOPPED: %s", this); setState(STOPPED, "stopIfPossible"); } @@ -6480,6 +6482,7 @@ final class ActivityRecord extends WindowToken { if (isStopping) { ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPED: %s (stop complete)", this); + mStoppedTime = SystemClock.uptimeMillis(); setState(STOPPED, "activityStopped"); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 37783781a901..6f83822ee97a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2130,6 +2130,26 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) { + enforceTaskPermission("setTaskIsPerceptible()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final Task task = mRootWindowContainer.anyTaskForId(taskId, + MATCH_ATTACHED_TASK_ONLY); + if (task == null) { + Slog.w(TAG, "setTaskIsPerceptible: No task to set with id=" + taskId); + return false; + } + task.mIsPerceptible = isPerceptible; + } + return true; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public boolean removeTask(int taskId) { mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()"); synchronized (mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 6b3499a5d68c..988af44f6e55 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -504,6 +504,17 @@ class Task extends TaskFragment { int mOffsetYForInsets; /** + * When set to true, the task will be kept at a PERCEPTIBLE_APP_ADJ, and downgraded + * to PREVIOUS_APP_ADJ if not in foreground for a period of time. + * One example use case is for desktop form factors, where it is important keep tasks in the + * perceptible state (rather than cached where it may be frozen) when a user moves it to the + * foreground. + * On startup, restored Tasks will not be perceptible, until user actually interacts with it + * (i.e. brings it to the foreground) + */ + boolean mIsPerceptible = false; + + /** * Whether the compatibility overrides that change the resizability of the app should be allowed * for the specific app. */ @@ -3854,6 +3865,7 @@ class Task extends TaskFragment { pw.print(ActivityInfo.resizeModeToString(mResizeMode)); pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture); pw.print(" isResizeable="); pw.println(isResizeable()); + pw.print(" isPerceptible="); pw.println(mIsPerceptible); pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime); pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)"); pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 30dde543b9d4..b2d28a369edc 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -37,6 +37,7 @@ import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityRecord.State.STARTED; import static com.android.server.wm.ActivityRecord.State.STOPPING; +import static com.android.server.wm.ActivityRecord.State.STOPPED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE; @@ -344,6 +345,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio */ private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; + /** + * The most recent timestamp of when one of this process's stopped activities in a + * perceptible task became stopped. Written by window manager and read by activity manager. + */ + private volatile long mPerceptibleTaskStoppedTimeMillis = Long.MIN_VALUE; + public WindowProcessController(@NonNull ActivityTaskManagerService atm, @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner, @NonNull WindowProcessListener listener) { @@ -1228,6 +1235,17 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mActivityStateFlags; } + /** + * Returns the most recent timestamp when one of this process's stopped activities in a + * perceptible task became stopped. It should only be called if {@link #hasActivities} + * returns {@code true} and {@link #getActivityStateFlags} does not have any of + * the ACTIVITY_STATE_FLAG_IS_(VISIBLE|PAUSING_OR_PAUSED|STOPPING) bit set. + */ + @HotPath(caller = HotPath.OOM_ADJUSTMENT) + public long getPerceptibleTaskStoppedTimeMillis() { + return mPerceptibleTaskStoppedTimeMillis; + } + void computeProcessActivityState() { // Since there could be more than one activities in a process record, we don't need to // compute the OomAdj with each of them, just need to find out the activity with the @@ -1239,6 +1257,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio int minTaskLayer = Integer.MAX_VALUE; int stateFlags = 0; int nonOccludedRatio = 0; + long perceptibleTaskStoppedTimeMillis = Long.MIN_VALUE; final boolean wasResumed = hasResumedActivity(); final boolean wasAnyVisible = (mActivityStateFlags & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; @@ -1287,6 +1306,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio bestInvisibleState = STOPPING; // Not "finishing" if any of activity isn't finishing. allStoppingFinishing &= r.finishing; + } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) { + if (task.mIsPerceptible) { + perceptibleTaskStoppedTimeMillis = + Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis); + } } } } @@ -1324,6 +1348,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } mActivityStateFlags = stateFlags; + mPerceptibleTaskStoppedTimeMillis = perceptibleTaskStoppedTimeMillis; final boolean anyVisible = (stateFlags & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 191c21e661d0..aee32a0473a3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -423,6 +423,7 @@ import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Bitmap; import android.hardware.usb.UsbManager; +import android.health.connect.HealthConnectManager; import android.location.Location; import android.location.LocationManager; import android.media.AudioManager; @@ -2149,6 +2150,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); mBackgroundHandler = BackgroundThread.getHandler(); + // Add the health permission to the list of restricted permissions. + if (android.permission.flags.Flags.replaceBodySensorPermissionEnabled()) { + Set<String> healthPermissions = HealthConnectManager.getHealthPermissions(mContext); + for (String permission : healthPermissions) { + SENSOR_PERMISSIONS.add(permission); + } + } + // Needed when mHasFeature == false, because it controls the certificate warning text. mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 1ef758cf192e..340115a7d465 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -3337,6 +3337,108 @@ public class MockingOomAdjusterTests { followUpTimeCaptor.capture()); } + /** + * For Perceptible Tasks adjustment, this solely unit-tests OomAdjuster -> onOtherActivity() + */ + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS) + public void testPerceptibleAdjustment() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + + long now = mInjector.getUptimeMillis(); + + // GIVEN: perceptible adjustment is NOT enabled (perceptible stop time is not set) + // EXPECT: zero adjustment + // TLDR: App is not set as a perceptible task and hence no oom_adj boosting. + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ, + false, false, PROCESS_STATE_CACHED_ACTIVITY, + SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND); + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(-1); + assertEquals(CACHED_APP_MIN_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj); + + // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and + // elapsed time < PERCEPTIBLE_TASK_TIMEOUT + // EXPECT: adjustment to PERCEPTIBLE_MEDIUM_APP_ADJ + // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting. + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ, + false, false, PROCESS_STATE_CACHED_ACTIVITY, + SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND); + mInjector.reset(); + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(now); + assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ, + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj); + + // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and + // elapsed time > PERCEPTIBLE_TASK_TIMEOUT + // EXPECT: adjustment to PREVIOUS_APP_ADJ + // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting, but + // time has elapsed and has dropped to a lower boosting of PREVIOUS_APP_ADJ + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ, + false, false, PROCESS_STATE_CACHED_ACTIVITY, + SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND); + mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 1000); + mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(0); + assertEquals(PREVIOUS_APP_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj); + } + + /** + * For Perceptible Tasks adjustment, this tests overall adjustment flow. + */ + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS) + public void testUpdateOomAdjPerceptible() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + WindowProcessController wpc = app.getWindowProcessController(); + + // Set uptime to be at least the timeout time + buffer, so that we don't end up with + // negative stopTime in our test input + mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 60L * 1000L); + long now = mInjector.getUptimeMillis(); + doReturn(true).when(wpc).hasActivities(); + + // GIVEN: perceptible adjustment is is enabled + // EXPECT: perceptible-act adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) + .when(wpc).getActivityStateFlags(); + doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_IMPORTANT_BACKGROUND, PERCEPTIBLE_MEDIUM_APP_ADJ, + SCHED_GROUP_BACKGROUND, "perceptible-act"); + + // GIVEN: perceptible adjustment is is enabled and timeout has been reached + // EXPECT: stale-perceptible-act adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) + .when(wpc).getActivityStateFlags(); + + doReturn(now - OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS).when( + wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ, + SCHED_GROUP_BACKGROUND, "stale-perceptible-act"); + + // GIVEN: perceptible adjustment is is disabled + // EXPECT: no perceptible adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) + .when(wpc).getActivityStateFlags(); + doReturn(Long.MIN_VALUE).when(wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_CACHED_ACTIVITY, CACHED_APP_MIN_ADJ, + SCHED_GROUP_BACKGROUND, "cch-act"); + + // GIVEN: perceptible app is in foreground + // EXPECT: no perceptible adjustment + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE) + .when(wpc).getActivityStateFlags(); + doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, + SCHED_GROUP_DEFAULT, "vis-activity"); + } + @SuppressWarnings("GuardedBy") @Test public void testUpdateOomAdj_DoAll_Multiple_Provider_Retention() { diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index d702cae248a9..009ce88cfd6c 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -33,6 +33,9 @@ android_test { "test-apps/DisplayManagerTestApp/src/**/*.java", ], + kotlincflags: [ + "-Werror", + ], static_libs: [ "a11ychecker", "aatf", diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java index 7f60caaa569b..0745c68fd337 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.platform.test.annotations.DisableFlags; @@ -400,6 +401,46 @@ public class AutoclickControllerTest { .isNotEqualTo(initialScheduledTime); } + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void pauseButton_flagOn_clickNotTriggeredWhenPaused() { + injectFakeMouseActionHoverMoveEvent(); + + // Pause autoclick. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isPaused()).thenReturn(true); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + + // Verify there is not a pending click. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + + // Resume autoclick. + when(mockAutoclickTypePanel.isPaused()).thenReturn(false); + + // Send initial move event again. Because this is the first recorded move, a click won't be + // scheduled. + injectFakeMouseActionHoverMoveEvent(); + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + + // Send move again to trigger click and verify there is now a pending click. + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1); + } + private void injectFakeMouseActionHoverMoveEvent() { MotionEvent event = getFakeMotionHoverMoveEvent(); event.setSource(InputDevice.SOURCE_MOUSE); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java index f3016f4b17f9..ba672dcd299b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java @@ -68,6 +68,7 @@ public class AutoclickTypePanelTest { private LinearLayout mPositionButton; private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK; + private boolean mPaused; private final ClickPanelControllerInterface clickPanelController = new ClickPanelControllerInterface() { @@ -77,7 +78,9 @@ public class AutoclickTypePanelTest { } @Override - public void toggleAutoclickPause() {} + public void toggleAutoclickPause(boolean paused) { + mPaused = paused; + } }; @Before @@ -199,6 +202,17 @@ public class AutoclickTypePanelTest { } } + @Test + public void pauseButton_onClick() { + mPauseButton.callOnClick(); + assertThat(mPaused).isTrue(); + assertThat(mAutoclickTypePanel.isPaused()).isTrue(); + + mPauseButton.callOnClick(); + assertThat(mPaused).isFalse(); + assertThat(mAutoclickTypePanel.isPaused()).isFalse(); + } + private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) { GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground(); assertThat(gradientDrawable.getColor().getDefaultColor()) diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index e5fac7ac5e0c..00b0c558b4e3 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -263,6 +263,11 @@ public class DpmMockContext extends MockContext { } @Override + public Context getApplicationContext() { + return this; + } + + @Override public PackageManager getPackageManager() { return mMockSystemServices.packageManager; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 5dea44d6ebf4..67e85ff2445d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -4495,6 +4495,27 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testBubblePreference_sameVersionWithSAWPermission() throws Exception { + when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(), + anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED); + + final String xml = "<ranking version=\"4\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n" + + "<channel id=\"someId\" name=\"hi\"" + + " importance=\"3\"/>" + + "</package>" + + "</ranking>"; + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O)); + assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); + } + + @Test public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception { when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(), anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED); diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index 1f0bd61b5c3f..168141bf6e7d 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -19,6 +19,9 @@ android_test { "src/**/*.kt", ], asset_dirs: ["assets"], + kotlincflags: [ + "-Werror", + ], platform_apis: true, certificate: "platform", static_libs: [ diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 7a19add1d1b9..8cd89ce89e83 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -34,6 +34,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.Map; import java.util.Objects; @@ -73,15 +74,21 @@ public class TestableLooper { /** * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection. */ - private static boolean newTestabilityApisSupported() { - return android.os.Flags.messageQueueTestability(); + private static boolean isAtLeastBaklava() { + Method[] methods = TestLooperManager.class.getMethods(); + for (Method method : methods) { + if (method.getName().equals("peekWhen")) { + return true; + } + } + return false; // TODO(shayba): delete the above, uncomment the below. // SDK_INT has not yet ramped to Baklava in all 25Q2 builds. // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA; } static { - if (newTestabilityApisSupported()) { + if (isAtLeastBaklava()) { MESSAGE_QUEUE_MESSAGES_FIELD = null; MESSAGE_NEXT_FIELD = null; MESSAGE_WHEN_FIELD = null; @@ -241,14 +248,14 @@ public class TestableLooper { } public void moveTimeForward(long milliSeconds) { - if (newTestabilityApisSupported()) { - moveTimeForwardModern(milliSeconds); + if (isAtLeastBaklava()) { + moveTimeForwardBaklava(milliSeconds); } else { moveTimeForwardLegacy(milliSeconds); } } - private void moveTimeForwardModern(long milliSeconds) { + private void moveTimeForwardBaklava(long milliSeconds) { // Drain all Messages from the queue. Queue<Message> messages = new ArrayDeque<>(); while (true) { diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java index c7a36dd3f9d8..4d379e45a81a 100644 --- a/tests/utils/testutils/java/android/os/test/TestLooper.java +++ b/tests/utils/testutils/java/android/os/test/TestLooper.java @@ -65,13 +65,19 @@ public class TestLooper { private AutoDispatchThread mAutoDispatchThread; /** - * Modern introduces new {@link TestLooperManager} APIs that we can use instead of reflection. + * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection. */ - private static boolean newTestabilityApisSupported() { - return android.os.Flags.messageQueueTestability(); + private static boolean isAtLeastBaklava() { + Method[] methods = TestLooperManager.class.getMethods(); + for (Method method : methods) { + if (method.getName().equals("peekWhen")) { + return true; + } + } + return false; // TODO(shayba): delete the above, uncomment the below. - // SDK_INT has not yet ramped to Modern in all 25Q2 builds. - // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Modern; + // SDK_INT has not yet ramped to Baklava in all 25Q2 builds. + // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA; } static { @@ -81,7 +87,7 @@ public class TestLooper { THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); - if (newTestabilityApisSupported()) { + if (isAtLeastBaklava()) { MESSAGE_QUEUE_MESSAGES_FIELD = null; MESSAGE_NEXT_FIELD = null; MESSAGE_WHEN_FIELD = null; @@ -130,7 +136,7 @@ public class TestLooper { throw new RuntimeException("Reflection error constructing or accessing looper", e); } - if (newTestabilityApisSupported()) { + if (isAtLeastBaklava()) { mTestLooperManager = InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper); } else { @@ -159,14 +165,14 @@ public class TestLooper { } public void moveTimeForward(long milliSeconds) { - if (newTestabilityApisSupported()) { - moveTimeForwardModern(milliSeconds); + if (isAtLeastBaklava()) { + moveTimeForwardBaklava(milliSeconds); } else { moveTimeForwardLegacy(milliSeconds); } } - private void moveTimeForwardModern(long milliSeconds) { + private void moveTimeForwardBaklava(long milliSeconds) { // Drain all Messages from the queue. Queue<Message> messages = new ArrayDeque<>(); while (true) { @@ -259,14 +265,14 @@ public class TestLooper { * @return true if there are pending messages in the message queue */ public boolean isIdle() { - if (newTestabilityApisSupported()) { - return isIdleModern(); + if (isAtLeastBaklava()) { + return isIdleBaklava(); } else { return isIdleLegacy(); } } - private boolean isIdleModern() { + private boolean isIdleBaklava() { Long when = mTestLooperManager.peekWhen(); return when != null && currentTime() >= when; } @@ -280,14 +286,14 @@ public class TestLooper { * @return the next message in the Looper's message queue or null if there is none */ public Message nextMessage() { - if (newTestabilityApisSupported()) { - return nextMessageModern(); + if (isAtLeastBaklava()) { + return nextMessageBaklava(); } else { return nextMessageLegacy(); } } - private Message nextMessageModern() { + private Message nextMessageBaklava() { if (isIdle()) { return mTestLooperManager.poll(); } else { @@ -308,14 +314,14 @@ public class TestLooper { * Asserts that there is a message in the queue */ public void dispatchNext() { - if (newTestabilityApisSupported()) { - dispatchNextModern(); + if (isAtLeastBaklava()) { + dispatchNextBaklava(); } else { dispatchNextLegacy(); } } - private void dispatchNextModern() { + private void dispatchNextBaklava() { assertTrue(isIdle()); Message msg = mTestLooperManager.poll(); if (msg == null) { |