Merge "Fix NPE caused by null replacedPkgSetting" into main
diff --git a/Android.bp b/Android.bp
index 127556f..e7c2041 100644
--- a/Android.bp
+++ b/Android.bp
@@ -427,6 +427,7 @@
"framework-permission-aidl-java",
"spatializer-aidl-java",
"audiopolicy-aidl-java",
+ "volumegroupcallback-aidl-java",
"sounddose-aidl-java",
"modules-utils-expresslog",
"perfetto_trace_javastream_protos_jarjar",
diff --git a/boot/boot-image-profile.txt b/boot/boot-image-profile.txt
index d7c409f..4c3e5dc 100644
--- a/boot/boot-image-profile.txt
+++ b/boot/boot-image-profile.txt
@@ -28829,7 +28829,6 @@
Landroid/media/audiopolicy/AudioProductStrategy;
Landroid/media/audiopolicy/AudioVolumeGroup$1;
Landroid/media/audiopolicy/AudioVolumeGroup;
-Landroid/media/audiopolicy/AudioVolumeGroupChangeHandler;
Landroid/media/audiopolicy/IAudioPolicyCallback$Stub$Proxy;
Landroid/media/audiopolicy/IAudioPolicyCallback$Stub;
Landroid/media/audiopolicy/IAudioPolicyCallback;
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 7f4b324..0486877 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -5509,7 +5509,6 @@
android.media.audiopolicy.AudioProductStrategy
android.media.audiopolicy.AudioVolumeGroup$1
android.media.audiopolicy.AudioVolumeGroup
-android.media.audiopolicy.AudioVolumeGroupChangeHandler
android.media.audiopolicy.IAudioPolicyCallback$Stub$Proxy
android.media.audiopolicy.IAudioPolicyCallback$Stub
android.media.audiopolicy.IAudioPolicyCallback
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 707acb0..3f0e00b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -5514,7 +5514,6 @@
android.media.audiopolicy.AudioProductStrategy
android.media.audiopolicy.AudioVolumeGroup$1
android.media.audiopolicy.AudioVolumeGroup
-android.media.audiopolicy.AudioVolumeGroupChangeHandler
android.media.audiopolicy.IAudioPolicyCallback$Stub$Proxy
android.media.audiopolicy.IAudioPolicyCallback$Stub
android.media.audiopolicy.IAudioPolicyCallback
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2ce3609..95b9b49 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2940,6 +2940,13 @@
package android.app.supervision {
+ @FlaggedApi("android.app.supervision.flags.enable_supervision_app_service") public class SupervisionAppService extends android.app.Service {
+ ctor public SupervisionAppService();
+ method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @FlaggedApi("android.app.supervision.flags.enable_supervision_app_service") public void onDisabled();
+ method @FlaggedApi("android.app.supervision.flags.enable_supervision_app_service") public void onEnabled();
+ }
+
@FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager {
method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public android.content.Intent createConfirmSupervisionCredentialsIntent();
method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSupervisionEnabled();
@@ -7435,7 +7442,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
- method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
+ method @FlaggedApi("android.media.audio.register_volume_callback_api_hardening") @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeAssistantServicesUids(@NonNull int[]);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removeDeviceAsNonDefaultForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public void removeOnDevicesForAttributesChangedListener(@NonNull android.media.AudioManager.OnDevicesForAttributesChangedListener);
@@ -7466,7 +7473,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
- method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
+ method @FlaggedApi("android.media.audio.register_volume_callback_api_hardening") @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
field public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION";
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
diff --git a/core/java/android/app/supervision/SupervisionAppService.java b/core/java/android/app/supervision/SupervisionAppService.java
index 4530be5..93eb962 100644
--- a/core/java/android/app/supervision/SupervisionAppService.java
+++ b/core/java/android/app/supervision/SupervisionAppService.java
@@ -16,7 +16,11 @@
package android.app.supervision;
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.Service;
+import android.app.supervision.flags.Flags;
import android.content.Intent;
import android.os.IBinder;
@@ -26,31 +30,43 @@
*
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE)
public class SupervisionAppService extends Service {
- private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() {
- @Override
- public void onEnabled() {
- SupervisionAppService.this.onEnabled();
- }
+ private final ISupervisionAppService mBinder =
+ new ISupervisionAppService.Stub() {
+ @Override
+ public void onEnabled() {
+ SupervisionAppService.this.onEnabled();
+ }
- @Override
- public void onDisabled() {
- SupervisionAppService.this.onDisabled();
- }
- };
+ @Override
+ public void onDisabled() {
+ SupervisionAppService.this.onDisabled();
+ }
+ };
+ @Nullable
@Override
- public final IBinder onBind(Intent intent) {
+ public final IBinder onBind(@Nullable Intent intent) {
return mBinder.asBinder();
}
/**
* Called when supervision is enabled.
+ *
+ * @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE)
public void onEnabled() {}
/**
* Called when supervision is disabled.
+ *
+ * @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE)
public void onDisabled() {}
}
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index 7ee0ff1..c599079 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -129,7 +129,7 @@
return ContainerHelpers.binarySearch(hashes, N, hash);
} catch (ArrayIndexOutOfBoundsException e) {
if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
- throw new ConcurrentModificationException();
+ throw new ConcurrentModificationException(e);
} else {
throw e; // the cache is poisoned at this point, there's not much we can do
}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 4bd7d16..4bd0d97 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -103,7 +103,7 @@
ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity,
true),
- ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD(Flags::enableDragResizeSetUpInBgThread, false),
+ ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD(Flags::enableDragResizeSetUpInBgThread, true),
ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX(
Flags::enableDragToDesktopIncomingTransitionsBugfix, false),
ENABLE_FULLY_IMMERSIVE_IN_DESKTOP(Flags::enableFullyImmersiveInDesktop, true),
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index bfa0aa9..7ed73d7 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -210,7 +210,6 @@
"android_media_AudioAttributes.cpp",
"android_media_AudioProductStrategies.cpp",
"android_media_AudioVolumeGroups.cpp",
- "android_media_AudioVolumeGroupCallback.cpp",
"android_media_DeviceCallback.cpp",
"android_media_MediaMetricsJNI.cpp",
"android_media_MicrophoneInfo.cpp",
@@ -311,6 +310,7 @@
"audioflinger-aidl-cpp",
"audiopolicy-types-aidl-cpp",
"spatializer-aidl-cpp",
+ "volumegroupcallback-aidl-cpp",
"av-types-aidl-cpp",
"android.hardware.camera.device@3.2",
"camera_platform_flags_c_lib",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b2b8263..1ff0774 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -101,7 +101,6 @@
extern int register_android_media_AudioAttributes(JNIEnv *env);
extern int register_android_media_AudioProductStrategies(JNIEnv *env);
extern int register_android_media_AudioVolumeGroups(JNIEnv *env);
-extern int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env);
extern int register_android_media_ImageReader(JNIEnv *env);
extern int register_android_media_ImageWriter(JNIEnv *env);
extern int register_android_media_MicrophoneInfo(JNIEnv *env);
@@ -1660,7 +1659,6 @@
REG_JNI(register_android_media_AudioAttributes),
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
- REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
REG_JNI(register_android_media_ImageReader),
REG_JNI(register_android_media_ImageWriter),
REG_JNI(register_android_media_MediaMetrics),
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index b679688..1bbf811 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -20,16 +20,17 @@
#include <atomic>
#define LOG_TAG "AudioSystem-JNI"
+#include <android-base/properties.h>
#include <android/binder_ibinder_jni.h>
#include <android/binder_libbinder.h>
#include <android/media/AudioVibratorInfo.h>
+#include <android/media/INativeAudioVolumeGroupCallback.h>
#include <android/media/INativeSpatializerCallback.h>
#include <android/media/ISpatializer.h>
#include <android/media/audio/common/AudioConfigBase.h>
#include <android_media_audiopolicy.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
-#include <android-base/properties.h>
#include <binder/IBinder.h>
#include <jni.h>
#include <media/AidlConversion.h>
@@ -41,14 +42,14 @@
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/jni_macros.h>
+#include <sys/system_properties.h>
#include <system/audio.h>
#include <system/audio_policy.h>
-#include <sys/system_properties.h>
#include <utils/Log.h>
+#include <memory>
#include <optional>
#include <sstream>
-#include <memory>
#include <vector>
#include "android_media_AudioAttributes.h"
@@ -59,8 +60,8 @@
#include "android_media_AudioFormat.h"
#include "android_media_AudioMixerAttributes.h"
#include "android_media_AudioProfile.h"
-#include "android_media_MicrophoneInfo.h"
#include "android_media_JNIUtils.h"
+#include "android_media_MicrophoneInfo.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
@@ -3442,6 +3443,21 @@
}
}
+static int android_media_AudioSystem_registerAudioVolumeGroupCallback(
+ JNIEnv *env, jobject thiz, jobject jIAudioVolumeGroupCallback) {
+ sp<media::INativeAudioVolumeGroupCallback> nIAudioVolumeGroupCallback =
+ interface_cast<media::INativeAudioVolumeGroupCallback>(
+ ibinderForJavaObject(env, jIAudioVolumeGroupCallback));
+ return AudioSystem::addAudioVolumeGroupCallback(nIAudioVolumeGroupCallback);
+}
+
+static int android_media_AudioSystem_unregisterAudioVolumeGroupCallback(
+ JNIEnv *env, jobject thiz, jobject jIAudioVolumeGroupCallback) {
+ sp<media::INativeAudioVolumeGroupCallback> nIAudioVolumeGroupCallback =
+ interface_cast<media::INativeAudioVolumeGroupCallback>(
+ ibinderForJavaObject(env, jIAudioVolumeGroupCallback));
+ return AudioSystem::removeAudioVolumeGroupCallback(nIAudioVolumeGroupCallback);
+}
// ----------------------------------------------------------------------------
@@ -3612,6 +3628,12 @@
MAKE_JNI_NATIVE_METHOD("clearPreferredMixerAttributes",
"(Landroid/media/AudioAttributes;II)I",
android_media_AudioSystem_clearPreferredMixerAttributes),
+ MAKE_JNI_NATIVE_METHOD("registerAudioVolumeGroupCallback",
+ "(Landroid/media/INativeAudioVolumeGroupCallback;)I",
+ android_media_AudioSystem_registerAudioVolumeGroupCallback),
+ MAKE_JNI_NATIVE_METHOD("unregisterAudioVolumeGroupCallback",
+ "(Landroid/media/INativeAudioVolumeGroupCallback;)I",
+ android_media_AudioSystem_unregisterAudioVolumeGroupCallback),
MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
diff --git a/core/jni/android_media_AudioVolumeGroupCallback.cpp b/core/jni/android_media_AudioVolumeGroupCallback.cpp
deleted file mode 100644
index d130a4b..0000000
--- a/core/jni/android_media_AudioVolumeGroupCallback.cpp
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#undef ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION // TODO:remove this and fix code
-
-//#define LOG_NDEBUG 0
-
-#define LOG_TAG "AudioVolumeGroupCallback-JNI"
-
-#include <utils/Log.h>
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-
-#include "android_media_AudioVolumeGroupCallback.h"
-
-
-// ----------------------------------------------------------------------------
-using namespace android;
-
-static const char* const kAudioVolumeGroupChangeHandlerClassPathName =
- "android/media/audiopolicy/AudioVolumeGroupChangeHandler";
-
-static struct {
- jfieldID mJniCallback;
-} gAudioVolumeGroupChangeHandlerFields;
-
-static struct {
- jmethodID postEventFromNative;
-} gAudioVolumeGroupChangeHandlerMethods;
-
-static Mutex gLock;
-
-JNIAudioVolumeGroupCallback::JNIAudioVolumeGroupCallback(JNIEnv* env,
- jobject thiz,
- jobject weak_thiz)
-{
- jclass clazz = env->GetObjectClass(thiz);
- if (clazz == NULL) {
- ALOGE("Can't find class %s", kAudioVolumeGroupChangeHandlerClassPathName);
- return;
- }
- mClass = (jclass)env->NewGlobalRef(clazz);
-
- // We use a weak reference so the AudioVolumeGroupChangeHandler object can be garbage collected.
- // The reference is only used as a proxy for callbacks.
- mObject = env->NewGlobalRef(weak_thiz);
-}
-
-JNIAudioVolumeGroupCallback::~JNIAudioVolumeGroupCallback()
-{
- // remove global references
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (env == NULL) {
- return;
- }
- env->DeleteGlobalRef(mObject);
- env->DeleteGlobalRef(mClass);
-}
-
-void JNIAudioVolumeGroupCallback::onAudioVolumeGroupChanged(volume_group_t group, int flags)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (env == NULL) {
- return;
- }
- ALOGV("%s volume group id %d", __FUNCTION__, group);
- env->CallStaticVoidMethod(mClass,
- gAudioVolumeGroupChangeHandlerMethods.postEventFromNative,
- mObject,
- AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED, group, flags, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNIAudioVolumeGroupCallback::onServiceDied()
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (env == NULL) {
- return;
- }
- env->CallStaticVoidMethod(mClass,
- gAudioVolumeGroupChangeHandlerMethods.postEventFromNative,
- mObject,
- AUDIOVOLUMEGROUP_EVENT_SERVICE_DIED, 0, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-static
-sp<JNIAudioVolumeGroupCallback> setJniCallback(JNIEnv* env,
- jobject thiz,
- const sp<JNIAudioVolumeGroupCallback>& callback)
-{
- Mutex::Autolock l(gLock);
- sp<JNIAudioVolumeGroupCallback> old = (JNIAudioVolumeGroupCallback*)env->GetLongField(
- thiz, gAudioVolumeGroupChangeHandlerFields.mJniCallback);
- if (callback.get()) {
- callback->incStrong((void*)setJniCallback);
- }
- if (old != 0) {
- old->decStrong((void*)setJniCallback);
- }
- env->SetLongField(thiz, gAudioVolumeGroupChangeHandlerFields.mJniCallback,
- (jlong)callback.get());
- return old;
-}
-
-static void
-android_media_AudioVolumeGroupChangeHandler_eventHandlerSetup(JNIEnv *env,
- jobject thiz,
- jobject weak_this)
-{
- ALOGV("%s", __FUNCTION__);
- sp<JNIAudioVolumeGroupCallback> callback =
- new JNIAudioVolumeGroupCallback(env, thiz, weak_this);
-
- if (AudioSystem::addAudioVolumeGroupCallback(callback) == NO_ERROR) {
- setJniCallback(env, thiz, callback);
- }
-}
-
-static void
-android_media_AudioVolumeGroupChangeHandler_eventHandlerFinalize(JNIEnv *env, jobject thiz)
-{
- ALOGV("%s", __FUNCTION__);
- sp<JNIAudioVolumeGroupCallback> callback = setJniCallback(env, thiz, 0);
- if (callback != 0) {
- AudioSystem::removeAudioVolumeGroupCallback(callback);
- }
-}
-
-/*
- * JNI registration.
- */
-static const JNINativeMethod gMethods[] = {
- {"native_setup", "(Ljava/lang/Object;)V",
- (void *)android_media_AudioVolumeGroupChangeHandler_eventHandlerSetup},
- {"native_finalize", "()V",
- (void *)android_media_AudioVolumeGroupChangeHandler_eventHandlerFinalize},
-};
-
-int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env)
-{
- jclass audioVolumeGroupChangeHandlerClass =
- FindClassOrDie(env, kAudioVolumeGroupChangeHandlerClassPathName);
- gAudioVolumeGroupChangeHandlerMethods.postEventFromNative =
- GetStaticMethodIDOrDie(env, audioVolumeGroupChangeHandlerClass, "postEventFromNative",
- "(Ljava/lang/Object;IIILjava/lang/Object;)V");
-
- gAudioVolumeGroupChangeHandlerFields.mJniCallback =
- GetFieldIDOrDie(env, audioVolumeGroupChangeHandlerClass, "mJniCallback", "J");
-
- env->DeleteLocalRef(audioVolumeGroupChangeHandlerClass);
-
- return RegisterMethodsOrDie(env,
- kAudioVolumeGroupChangeHandlerClassPathName,
- gMethods,
- NELEM(gMethods));
-}
-
diff --git a/core/jni/android_media_AudioVolumeGroupCallback.h b/core/jni/android_media_AudioVolumeGroupCallback.h
deleted file mode 100644
index de06549..0000000
--- a/core/jni/android_media_AudioVolumeGroupCallback.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <system/audio.h>
-#include <media/AudioSystem.h>
-
-namespace android {
-
-// keep in sync with AudioManager.AudioVolumeGroupChangeHandler.java
-#define AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED 1000
-#define AUDIOVOLUMEGROUP_EVENT_SERVICE_DIED 1001
-
-class JNIAudioVolumeGroupCallback: public AudioSystem::AudioVolumeGroupCallback
-{
-public:
- JNIAudioVolumeGroupCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
- ~JNIAudioVolumeGroupCallback();
-
- void onAudioVolumeGroupChanged(volume_group_t group, int flags) override;
- void onServiceDied() override;
-
-private:
- void sendEvent(int event);
-
- jclass mClass; /**< Reference to AudioVolumeGroupChangeHandler class. */
- jobject mObject; /**< Weak ref to AudioVolumeGroupChangeHandler object to call on. */
-};
-
-} // namespace android
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e47adc9..1a74fe6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1219,6 +1219,8 @@
6 - Lock if keyguard enabled or go to sleep (doze)
7 - Dream if possible or go to sleep (doze)
8 - Go to glanceable hub or dream if possible, or sleep if neither is available (doze)
+ 9 - Go to dream if device is not dreaming, stop dream if device is dreaming, or sleep if
+ neither is available (doze)
-->
<integer name="config_shortPressOnPowerBehavior">1</integer>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index ef6b918..849ca28 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -86,7 +86,7 @@
CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY.
If 0, the device always switch to the higher score SIM.
If < 0, the network type and signal strength based auto switch is disabled. -->
- <integer name="auto_data_switch_score_tolerance">4000</integer>
+ <integer name="auto_data_switch_score_tolerance">7000</integer>
<java-symbol type="integer" name="auto_data_switch_score_tolerance" />
<!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
@@ -261,7 +261,9 @@
to identify providers that should be ignored if the carrier config
carrier_supported_satellite_services_per_provider_bundle does not support them.
-->
- <string-array name="config_satellite_providers" translatable="false"></string-array>
+ <string-array name="config_satellite_providers" translatable="false">
+ <item>"310830"</item>
+ </string-array>
<java-symbol type="array" name="config_satellite_providers" />
<!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
diff --git a/core/tests/coretests/src/com/android/internal/app/MediaRouteDialogPresenterTest.kt b/core/tests/coretests/src/com/android/internal/app/MediaRouteDialogPresenterTest.kt
new file mode 100644
index 0000000..e80d3a6
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/MediaRouteDialogPresenterTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app
+
+import android.content.Context
+import android.media.MediaRouter
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class MediaRouteDialogPresenterTest {
+ private var selectedRoute: MediaRouter.RouteInfo = mock()
+ private var mediaRouter: MediaRouter = mock<MediaRouter> {
+ on { selectedRoute } doReturn selectedRoute
+ }
+ private var context: Context = mock<Context> {
+ on { getSystemServiceName(MediaRouter::class.java) } doReturn Context.MEDIA_ROUTER_SERVICE
+ on { getSystemService(MediaRouter::class.java) } doReturn mediaRouter
+ }
+
+ @Test
+ fun shouldShowChooserDialog_routeNotDefault_returnsFalse() {
+ selectedRoute.stub {
+ on { isDefault } doReturn false
+ on { matchesTypes(anyInt()) } doReturn true
+ }
+
+ assertThat(MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY))
+ .isEqualTo(false)
+ }
+
+ @Test
+ fun shouldShowChooserDialog_routeDefault_returnsTrue() {
+ selectedRoute.stub {
+ on { isDefault } doReturn true
+ on { matchesTypes(anyInt()) } doReturn true
+ }
+
+ assertThat(MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY))
+ .isEqualTo(true)
+ }
+
+ @Test
+ fun shouldShowChooserDialog_routeNotMatch_returnsTrue() {
+ selectedRoute.stub {
+ on { isDefault } doReturn false
+ on { matchesTypes(anyInt()) } doReturn false
+ }
+
+ assertThat(MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY))
+ .isEqualTo(true)
+ }
+
+ @Test
+ fun shouldShowChooserDialog_routeDefaultAndNotMatch_returnsTrue() {
+ selectedRoute.stub {
+ on { isDefault } doReturn true
+ on { matchesTypes(anyInt()) } doReturn false
+ }
+
+ assertThat(MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY))
+ .isEqualTo(true)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index ed5e0c6..e5a4cd0 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -278,6 +278,9 @@
if (!canEnterDesktopMode(context)) {
return false;
}
+ if (!enforceDeviceRestrictions()) {
+ return true;
+ }
if (display.getType() == Display.TYPE_INTERNAL) {
return canInternalDisplayHostDesktops(context);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java
index 9fa1621..d9a66e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java
@@ -57,6 +57,11 @@
* @return 0f = no dim applied. 1f = full black.
*/
public float getDimValue(int position, DividerSnapAlgorithm snapAlgorithm) {
+ // On tablets, apps don't go offscreen, so only dim for dismissal.
+ if (!snapAlgorithm.areOffscreenRatiosSupported()) {
+ return ParallaxSpec.super.getDimValue(position, snapAlgorithm);
+ }
+
int startDismissPos = snapAlgorithm.getDismissStartTarget().getPosition();
int firstTargetPos = snapAlgorithm.getFirstSplitTarget().getPosition();
int middleTargetPos = snapAlgorithm.getMiddleTarget().getPosition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 2d44395..5f13ba9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -762,6 +762,7 @@
floatingBtn.isEnabled = !taskInfo.isPinned
floatingBtn.imageTintList = style.windowingButtonColor
desktopBtn.isGone = !shouldShowDesktopModeButton
+ desktopBtnSpace.isGone = !shouldShowDesktopModeButton
desktopBtn.isSelected = taskInfo.isFreeform
desktopBtn.isEnabled = !taskInfo.isFreeform
desktopBtn.imageTintList = style.windowingButtonColor
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java
index 22a85fc..9f2534e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java
@@ -71,6 +71,7 @@
when(mockSnapAlgorithm.getMiddleTarget()).thenReturn(mockMiddleTarget);
when(mockSnapAlgorithm.getLastSplitTarget()).thenReturn(mockLastTarget);
when(mockSnapAlgorithm.getDismissEndTarget()).thenReturn(mockEndEdge);
+ when(mockSnapAlgorithm.areOffscreenRatiosSupported()).thenReturn(true);
when(mockStartEdge.getPosition()).thenReturn(0);
when(mockFirstTarget.getPosition()).thenReturn(250);
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index 01e8010..64d38b9 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -149,25 +149,9 @@
return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get());
}
- int width = -1;
- int result = mWindow->query(mWindow, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
- if (result != OK || width < 0) {
- ALOGW("Failed to query window default width: %s (%d) value=%d", strerror(-result), result,
- width);
- width = 1;
- }
-
- int height = -1;
- result = mWindow->query(mWindow, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
- if (result != OK || height < 0) {
- ALOGW("Failed to query window default height: %s (%d) value=%d", strerror(-result), result,
- height);
- height = 1;
- }
-
AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{
- .width = static_cast<uint32_t>(width),
- .height = static_cast<uint32_t>(height),
+ .width = 1,
+ .height = 1,
.layers = 1,
.format = mFormat,
.usage = mUsage,
@@ -176,9 +160,9 @@
};
AHardwareBuffer* newBuffer;
- result = AHardwareBuffer_allocate(&desc, &newBuffer);
+ int result = AHardwareBuffer_allocate(&desc, &newBuffer);
- if (result != OK) {
+ if (result != NO_ERROR) {
// Allocate failed, that sucks
ALOGW("Failed to allocate scratch buffer, error=%d", result);
return nullptr;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index a67aea4..0cd9c53 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -238,6 +238,7 @@
for (uint32_t i = 0; i < queueCount; i++) {
queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
queuePriorityProps[i].pNext = nullptr;
+ queueProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
queueProps[i].pNext = &queuePriorityProps[i];
}
mGetPhysicalDeviceQueueFamilyProperties2(mPhysicalDevice, &queueCount, queueProps.get());
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4aba491..0a1bfd55 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -19,14 +19,15 @@
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO;
+import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
+import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
+import static android.media.audio.Flags.FLAG_REGISTER_VOLUME_CALLBACK_API_HARDENING;
+import static android.media.audio.Flags.FLAG_SUPPORTED_DEVICE_TYPES_API;
import static android.media.audio.Flags.FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.cacheGetStreamMinMaxVolume;
import static android.media.audio.Flags.cacheGetStreamVolume;
-import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO;
-import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
-import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
-import static android.media.audio.Flags.FLAG_SUPPORTED_DEVICE_TYPES_API;
import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.Manifest;
@@ -65,7 +66,7 @@
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
-import android.media.audiopolicy.AudioVolumeGroupChangeHandler;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
import android.media.projection.MediaProjection;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -128,8 +129,6 @@
private static final String TAG = "AudioManager";
private static final boolean DEBUG = false;
private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
- private static final AudioVolumeGroupChangeHandler sAudioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
private static WeakReference<Context> sContext;
@@ -8761,9 +8760,13 @@
}
}
+ //====================================================================
+ // Notification of volume group changes
/**
+ * Callback to receive updates on volume group changes, register using
+ * {@link AudioManager#registerVolumeGroupCallback(Executor, AudioVolumeCallback)}.
+ *
* @hide
- * Callback registered by client to be notified upon volume group change.
*/
@SystemApi
public abstract static class VolumeGroupCallback {
@@ -8774,35 +8777,70 @@
public void onAudioVolumeGroupChanged(int group, int flags) {}
}
- /**
- * @hide
- * Register an audio volume group change listener.
- * @param callback the {@link VolumeGroupCallback} to register
- */
+ /**
+ * Register an audio volume group change listener.
+ *
+ * @param executor {@link Executor} to handle the callbacks
+ * @param callback the callback to receive the audio volume group changes
+ * @throws SecurityException if the caller doesn't have the required permission.
+ *
+ * @hide
+ */
@SystemApi
- public void registerVolumeGroupCallback(
- @NonNull Executor executor,
+ @FlaggedApi(FLAG_REGISTER_VOLUME_CALLBACK_API_HARDENING)
+ @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ public void registerVolumeGroupCallback(@NonNull Executor executor,
@NonNull VolumeGroupCallback callback) {
- Preconditions.checkNotNull(executor, "executor must not be null");
- Preconditions.checkNotNull(callback, "volume group change cb must not be null");
- sAudioAudioVolumeGroupChangedHandler.init();
- // TODO: make use of executor
- sAudioAudioVolumeGroupChangedHandler.registerListener(callback);
+ mVolumeChangedListenerMgr.addListener(executor, callback, "registerVolumeGroupCallback",
+ () -> new AudioVolumeChangeDispatcherStub());
}
- /**
- * @hide
- * Unregister an audio volume group change listener.
- * @param callback the {@link VolumeGroupCallback} to unregister
- */
+ /**
+ * Unregister an audio volume group change listener.
+ *
+ * @param callback the {@link VolumeGroupCallback} to unregister
+ *
+ * @hide
+ */
@SystemApi
- public void unregisterVolumeGroupCallback(
- @NonNull VolumeGroupCallback callback) {
- Preconditions.checkNotNull(callback, "volume group change cb must not be null");
- sAudioAudioVolumeGroupChangedHandler.unregisterListener(callback);
+ @FlaggedApi(FLAG_REGISTER_VOLUME_CALLBACK_API_HARDENING)
+ @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ public void unregisterVolumeGroupCallback(@NonNull VolumeGroupCallback callback) {
+ mVolumeChangedListenerMgr.removeListener(callback, "unregisterVolumeGroupCallback");
}
/**
+ * Manages the VolumeGroupCallback listeners and the AudioVolumeChangeDispatcherStub
+ */
+ private final CallbackUtil.LazyListenerManager<VolumeGroupCallback> mVolumeChangedListenerMgr =
+ new CallbackUtil.LazyListenerManager();
+
+ final class AudioVolumeChangeDispatcherStub extends IAudioVolumeChangeDispatcher.Stub
+ implements CallbackUtil.DispatcherStub {
+
+ @Override
+ public void register(boolean register) {
+ try {
+ if (register) {
+ getService().registerAudioVolumeCallback(this);
+ } else {
+ getService().unregisterAudioVolumeCallback(this);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onAudioVolumeGroupChanged(int group, int flags) {
+ mVolumeChangedListenerMgr.callListeners((listener) ->
+ listener.onAudioVolumeGroupChanged(group, flags));
+ }
+ }
+
+ //====================================================================
+
+ /**
* Return if an asset contains haptic channels or not.
*
* @param context the {@link Context} to resolve the uri.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index ad6f2e52..4906cd3 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2732,4 +2732,25 @@
* @hide
*/
public static native void triggerSystemPropertyUpdate(long handle);
+
+ /**
+ * Registers the given {@link INativeAudioVolumeGroupCallback} to native audioserver.
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public static native int registerAudioVolumeGroupCallback(
+ INativeAudioVolumeGroupCallback callback);
+
+ /**
+ * Unegisters the given {@link INativeAudioVolumeGroupCallback} from native audioserver
+ * previously registered via {@link #registerAudioVolumeGroupCallback}.
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public static native int unregisterAudioVolumeGroupCallback(
+ INativeAudioVolumeGroupCallback callback);
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8aadb41..c505bce 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -65,6 +65,7 @@
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.media.audiopolicy.IAudioPolicyCallback;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
import android.media.projection.IMediaProjection;
import android.net.Uri;
import android.os.PersistableBundle;
@@ -446,6 +447,12 @@
boolean isAudioServerRunning();
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ void registerAudioVolumeCallback(IAudioVolumeChangeDispatcher avc);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ oneway void unregisterAudioVolumeCallback(IAudioVolumeChangeDispatcher avc);
+
int setUidDeviceAffinity(in IAudioPolicyCallback pcb, in int uid, in int[] deviceTypes,
in String[] deviceAddresses);
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
deleted file mode 100644
index 022cfee..0000000
--- a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.audiopolicy;
-
-import android.annotation.NonNull;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-
-import com.android.internal.util.Preconditions;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-/**
- * The AudioVolumeGroupChangeHandler handles AudioManager.OnAudioVolumeGroupChangedListener
- * callbacks posted from JNI
- *
- * TODO: Make use of Executor of callbacks.
- * @hide
- */
-public class AudioVolumeGroupChangeHandler {
- private Handler mHandler;
- private HandlerThread mHandlerThread;
- private final ArrayList<AudioManager.VolumeGroupCallback> mListeners =
- new ArrayList<AudioManager.VolumeGroupCallback>();
-
- private static final String TAG = "AudioVolumeGroupChangeHandler";
-
- private static final int AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED = 1000;
- private static final int AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER = 4;
-
- /**
- * Accessed by native methods: JNI Callback context.
- */
- @SuppressWarnings("unused")
- private long mJniCallback;
-
- /**
- * Initialization
- */
- public void init() {
- synchronized (this) {
- if (mHandler != null) {
- return;
- }
- // create a new thread for our new event handler
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
-
- if (mHandlerThread.getLooper() == null) {
- mHandler = null;
- return;
- }
- mHandler = new Handler(mHandlerThread.getLooper()) {
- @Override
- public void handleMessage(Message msg) {
- ArrayList<AudioManager.VolumeGroupCallback> listeners;
- synchronized (this) {
- if (msg.what == AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER) {
- listeners =
- new ArrayList<AudioManager.VolumeGroupCallback>();
- if (mListeners.contains(msg.obj)) {
- listeners.add(
- (AudioManager.VolumeGroupCallback) msg.obj);
- }
- } else {
- listeners = (ArrayList<AudioManager.VolumeGroupCallback>)
- mListeners.clone();
- }
- }
- if (listeners.isEmpty()) {
- return;
- }
-
- switch (msg.what) {
- case AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED:
- for (int i = 0; i < listeners.size(); i++) {
- listeners.get(i).onAudioVolumeGroupChanged((int) msg.arg1,
- (int) msg.arg2);
- }
- break;
-
- default:
- break;
- }
- }
- };
- native_setup(new WeakReference<AudioVolumeGroupChangeHandler>(this));
- }
- }
-
- private native void native_setup(Object moduleThis);
-
- @Override
- protected void finalize() {
- native_finalize();
- if (mHandlerThread.isAlive()) {
- mHandlerThread.quit();
- }
- }
- private native void native_finalize();
-
- /**
- * @param cb the {@link AudioManager.VolumeGroupCallback} to register
- */
- public void registerListener(@NonNull AudioManager.VolumeGroupCallback cb) {
- Preconditions.checkNotNull(cb, "volume group callback shall not be null");
- synchronized (this) {
- mListeners.add(cb);
- }
- if (mHandler != null) {
- Message m = mHandler.obtainMessage(
- AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER, 0, 0, cb);
- mHandler.sendMessage(m);
- }
- }
-
- /**
- * @param cb the {@link AudioManager.VolumeGroupCallback} to unregister
- */
- public void unregisterListener(@NonNull AudioManager.VolumeGroupCallback cb) {
- Preconditions.checkNotNull(cb, "volume group callback shall not be null");
- synchronized (this) {
- mListeners.remove(cb);
- }
- }
-
- Handler handler() {
- return mHandler;
- }
-
- @SuppressWarnings("unused")
- private static void postEventFromNative(Object moduleRef,
- int what, int arg1, int arg2, Object obj) {
- AudioVolumeGroupChangeHandler eventHandler =
- (AudioVolumeGroupChangeHandler) ((WeakReference) moduleRef).get();
- if (eventHandler == null) {
- return;
- }
-
- if (eventHandler != null) {
- Handler handler = eventHandler.handler();
- if (handler != null) {
- Message m = handler.obtainMessage(what, arg1, arg2, obj);
- // Do not remove previous messages, as we would lose notification of group changes
- handler.sendMessage(m);
- }
- }
- }
-}
diff --git a/media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl b/media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl
new file mode 100644
index 0000000..e6f9024
--- /dev/null
+++ b/media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl
@@ -0,0 +1,31 @@
+/* Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audiopolicy;
+
+/**
+ * AIDL for the AudioService to signal audio volume groups changes
+ *
+ * {@hide}
+ */
+oneway interface IAudioVolumeChangeDispatcher {
+
+ /**
+ * Called when a volume group has been changed
+ * @param group id of the volume group that has changed.
+ * @param flags one or more flags to describe the volume change.
+ */
+ void onAudioVolumeGroupChanged(int group, int flags);
+}
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index fccdba8..ece87a6 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -72,6 +72,43 @@
*/
public static final String LEVEL_OFF = "level_off";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "COLOR_TEMP", value = {
+ COLOR_TEMP_USER,
+ COLOR_TEMP_COOL,
+ COLOR_TEMP_STANDARD,
+ COLOR_TEMP_WARM,
+ COLOR_TEMP_USER_HDR10PLUS,
+ COLOR_TEMP_COOL_HDR10PLUS,
+ COLOR_TEMP_STANDARD_HDR10PLUS,
+ COLOR_TEMP_WARM_HDR10PLUS,
+ COLOR_TEMP_FMMSDR,
+ COLOR_TEMP_FMMHDR,
+ })
+ public @interface ColorTempValue {}
+
+ /** @hide */
+ public static final String COLOR_TEMP_USER = "color_temp_user";
+ /** @hide */
+ public static final String COLOR_TEMP_COOL = "color_temp_cool";
+ /** @hide */
+ public static final String COLOR_TEMP_STANDARD = "color_temp_standard";
+ /** @hide */
+ public static final String COLOR_TEMP_WARM = "color_temp_warm";
+ /** @hide */
+ public static final String COLOR_TEMP_USER_HDR10PLUS = "color_temp_user_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_COOL_HDR10PLUS = "color_temp_cool_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_STANDARD_HDR10PLUS = "color_temp_standard_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_WARM_HDR10PLUS = "color_temp_warm_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_FMMSDR = "color_temp_fmmsdr";
+ /** @hide */
+ public static final String COLOR_TEMP_FMMHDR = "color_temp_fmmhdr";
+
/**
* @hide
@@ -82,7 +119,6 @@
String PARAMETER_NAME = "_name";
String PARAMETER_PACKAGE = "_package";
String PARAMETER_INPUT_ID = "_input_id";
- String VENDOR_PARAMETERS = "_vendor_parameters";
}
/**
diff --git a/media/tests/AudioPolicyTest/AndroidManifest.xml b/media/tests/AudioPolicyTest/AndroidManifest.xml
index 5c911b1..466da7e 100644
--- a/media/tests/AudioPolicyTest/AndroidManifest.xml
+++ b/media/tests/AudioPolicyTest/AndroidManifest.xml
@@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
deleted file mode 100644
index 82394a2..0000000
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.audiopolicytest;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
-import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES;
-import static com.android.audiopolicytest.AudioVolumeTestUtil.incrementVolumeIndex;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.audiopolicy.AudioVolumeGroup;
-import android.media.audiopolicy.AudioVolumeGroupChangeHandler;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class AudioVolumeGroupChangeHandlerTest {
- private static final String TAG = "AudioVolumeGroupChangeHandlerTest";
-
- @Rule
- public final AudioVolumesTestRule rule = new AudioVolumesTestRule();
-
- private AudioManager mAudioManager;
-
- @Before
- public void setUp() {
- mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
- }
-
- @Test
- public void testRegisterInvalidCallback() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- assertThrows(NullPointerException.class, () -> {
- AudioManager.VolumeGroupCallback nullCb = null;
- audioAudioVolumeGroupChangedHandler.registerListener(nullCb);
- });
- }
-
- @Test
- public void testUnregisterInvalidCallback() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- final AudioVolumeGroupCallbackHelper cb = new AudioVolumeGroupCallbackHelper();
- audioAudioVolumeGroupChangedHandler.registerListener(cb);
-
- assertThrows(NullPointerException.class, () -> {
- AudioManager.VolumeGroupCallback nullCb = null;
- audioAudioVolumeGroupChangedHandler.unregisterListener(nullCb);
- });
- audioAudioVolumeGroupChangedHandler.unregisterListener(cb);
- }
-
- @Test
- public void testRegisterUnregisterCallback() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
- final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper();
-
- // Should not assert, otherwise test will fail
- audioAudioVolumeGroupChangedHandler.registerListener(validCb);
-
- // Should not assert, otherwise test will fail
- audioAudioVolumeGroupChangedHandler.unregisterListener(validCb);
- }
-
- @Test
- public void testCallbackReceived() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper();
- audioAudioVolumeGroupChangedHandler.registerListener(validCb);
-
- List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
- assertTrue(audioVolumeGroups.size() > 0);
-
- try {
- for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
- int volumeGroupId = audioVolumeGroup.getId();
-
- List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
- // Set the volume per attributes (if valid) and wait the callback
- if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(DEFAULT_ATTRIBUTES)) {
- // Some volume groups may not have valid attributes, used for internal
- // volume management like patch/rerouting
- // so bailing out strategy retrieval from attributes
- continue;
- }
- final AudioAttributes aa = avgAttributes.get(0);
-
- int index = mAudioManager.getVolumeIndexForAttributes(aa);
- int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa);
- int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa);
-
- final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax);
-
- // Set the receiver to filter only the current group callback
- validCb.setExpectedVolumeGroup(volumeGroupId);
- mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/);
- assertTrue(validCb.waitForExpectedVolumeGroupChanged(
- AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
-
- final int readIndex = mAudioManager.getVolumeIndexForAttributes(aa);
- assertEquals(readIndex, indexForAa);
- }
- } finally {
- audioAudioVolumeGroupChangedHandler.unregisterListener(validCb);
- }
- }
-
- @Test
- public void testMultipleCallbackReceived() {
-
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- final int callbackCount = 10;
- final List<AudioVolumeGroupCallbackHelper> validCbs =
- new ArrayList<AudioVolumeGroupCallbackHelper>();
- for (int i = 0; i < callbackCount; i++) {
- validCbs.add(new AudioVolumeGroupCallbackHelper());
- }
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- audioAudioVolumeGroupChangedHandler.registerListener(cb);
- }
-
- List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
- assertTrue(audioVolumeGroups.size() > 0);
-
- try {
- for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
- int volumeGroupId = audioVolumeGroup.getId();
-
- List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
- // Set the volume per attributes (if valid) and wait the callback
- if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(DEFAULT_ATTRIBUTES)) {
- // Some volume groups may not have valid attributes, used for internal
- // volume management like patch/rerouting
- // so bailing out strategy retrieval from attributes
- continue;
- }
- AudioAttributes aa = avgAttributes.get(0);
-
- int index = mAudioManager.getVolumeIndexForAttributes(aa);
- int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa);
- int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa);
-
- final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax);
-
- // Set the receiver to filter only the current group callback
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- cb.setExpectedVolumeGroup(volumeGroupId);
- }
- mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/);
-
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- assertTrue(cb.waitForExpectedVolumeGroupChanged(
- AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
- }
- int readIndex = mAudioManager.getVolumeIndexForAttributes(aa);
- assertEquals(readIndex, indexForAa);
- }
- } finally {
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- audioAudioVolumeGroupChangedHandler.unregisterListener(cb);
- }
- }
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index c4e7245..21d518a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -27,7 +27,6 @@
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
-import com.android.settingslib.flags.Flags
import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModesBackend
import java.time.Duration
@@ -35,6 +34,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
@@ -72,7 +72,7 @@
private val notificationManager: NotificationManager,
private val backend: ZenModesBackend,
private val contentResolver: ContentResolver,
- val scope: CoroutineScope,
+ val applicationScope: CoroutineScope,
val backgroundCoroutineContext: CoroutineContext,
// This is nullable just to simplify testing, since SettingsLib doesn't have a good way
// to create a fake handler.
@@ -104,7 +104,7 @@
awaitClose { context.unregisterReceiver(receiver) }
}
.flowOn(backgroundCoroutineContext)
- .shareIn(started = SharingStarted.WhileSubscribed(), scope = scope)
+ .shareIn(started = SharingStarted.WhileSubscribed(), scope = applicationScope)
}
override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
@@ -129,14 +129,11 @@
.map { mapper(it) }
.onStart { emit(mapper(null)) }
.flowOn(backgroundCoroutineContext)
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), null)
private val zenConfigChanged by lazy {
if (android.app.Flags.modesUi()) {
callbackFlow {
- // emit an initial value
- trySend(Unit)
-
val observer =
object : ContentObserver(backgroundHandler) {
override fun onChange(selfChange: Boolean) {
@@ -163,16 +160,18 @@
}
}
- override val modes: Flow<List<ZenMode>> by lazy {
- if (android.app.Flags.modesUi()) {
+ override val modes: StateFlow<List<ZenMode>> =
+ if (android.app.Flags.modesUi())
zenConfigChanged
.map { backend.modes }
.distinctUntilChanged()
.flowOn(backgroundCoroutineContext)
- } else {
- flowOf(emptyList())
- }
- }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = backend.modes,
+ )
+ else MutableStateFlow<List<ZenMode>>(emptyList())
/**
* Gets the current list of [ZenMode] instances according to the backend.
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index b364368..ec7baf6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -75,10 +75,14 @@
private val testScope: TestScope = TestScope()
+ private val initialModes = listOf(TestModeBuilder().setId("Built-in").build())
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ `when`(zenModesBackend.modes).thenReturn(initialModes)
+
underTest =
ZenModeRepositoryImpl(
context,
@@ -151,8 +155,8 @@
fun modesListEmitsOnSettingsChange() {
testScope.runTest {
val values = mutableListOf<List<ZenMode>>()
- val modes1 = listOf(TestModeBuilder().setId("One").build())
- `when`(zenModesBackend.modes).thenReturn(modes1)
+
+ // an initial list of modes is read when the stateflow is created
underTest.modes.onEach { values.add(it) }.launchIn(backgroundScope)
runCurrent()
@@ -172,7 +176,7 @@
triggerZenModeSettingUpdate()
runCurrent()
- assertThat(values).containsExactly(modes1, modes2, modes3).inOrder()
+ assertThat(values).containsExactly(initialModes, modes2, modes3).inOrder()
}
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a4852d2..685c689 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -289,6 +289,15 @@
}
}
+flag {
+ name: "notification_skip_silent_updates"
+ namespace: "systemui"
+ description: "Do not notify HeadsUpManager for silent updates."
+ bug: "401068530"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
flag {
name: "scene_container"
@@ -1856,10 +1865,11 @@
}
flag {
- name: "shade_header_fonts"
+ name: "shade_header_font_update"
namespace: "systemui"
description: "Updates the fonts of the shade header"
- bug: "393609724"
+ bug: "393609960"
+ is_fixed_read_only: true
}
flag {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/CastDetailsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/CastDetailsViewModelTest.kt
new file mode 100644
index 0000000..468c3dc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/CastDetailsViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog
+
+import android.content.Context
+import android.media.MediaRouter
+import android.provider.Settings
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.app.MediaRouteDialogPresenter
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.domain.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.domain.actions.intentInputs
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class CastDetailsViewModelTest : SysuiTestCase() {
+ var inputHandler: FakeQSTileIntentUserInputHandler = FakeQSTileIntentUserInputHandler()
+ private var context: Context = mock()
+ private var mediaRouter: MediaRouter = mock()
+ private var selectedRoute: MediaRouter.RouteInfo = mock()
+
+ @Test
+ fun testClickOnSettingsButton() {
+ var viewModel = CastDetailsViewModel(inputHandler, context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)
+
+ viewModel.clickOnSettingsButton()
+
+ assertThat(inputHandler.handledInputs).hasSize(1)
+ val intentInput = inputHandler.intentInputs.last()
+ assertThat(intentInput.expandable).isNull()
+ assertThat(intentInput.intent.action).isEqualTo(Settings.ACTION_CAST_SETTINGS)
+ }
+
+ @Test
+ fun testShouldShowChooserDialog() {
+ context.stub {
+ on { getSystemService(MediaRouter::class.java) } doReturn mediaRouter
+ }
+ mediaRouter.stub {
+ on { selectedRoute } doReturn selectedRoute
+ }
+
+ var viewModel =
+ CastDetailsViewModel(inputHandler, context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)
+
+ assertThat(viewModel.shouldShowChooserDialog())
+ .isEqualTo(
+ MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context,
+ MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 3ecf302..67af7a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
@@ -45,6 +46,8 @@
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -65,8 +68,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-import java.util.Optional
-import java.util.function.Consumer
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -75,6 +76,7 @@
private val kosmos = testKosmos()
private val applicationScope = kosmos.testScope.backgroundScope
+ private val shadeDisplayRepository = kosmos.fakeShadeDisplaysRepository
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var blurUtils: BlurUtils
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@@ -135,7 +137,8 @@
windowRootViewBlurInteractor,
appZoomOutOptional,
applicationScope,
- dumpManager
+ dumpManager,
+ { shadeDisplayRepository },
)
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
@@ -355,6 +358,36 @@
}
@Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun updateBlurCallback_shadeInExternalDisplay_doesSetZeroZoom() {
+ notificationShadeDepthController.onPanelExpansionChanged(
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
+ notificationShadeDepthController.addListener(listener)
+ shadeDisplayRepository.setDisplayId(1) // not default display.
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+ verify(wallpaperController).setNotificationShadeZoom(eq(0f))
+ verify(listener).onWallpaperZoomOutChanged(eq(0f))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun updateBlurCallback_shadeInDefaultDisplay_doesNotSetZeroZoom() {
+ notificationShadeDepthController.onPanelExpansionChanged(
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
+ notificationShadeDepthController.addListener(listener)
+ shadeDisplayRepository.setDisplayId(0) // shade is in default display
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+ verify(wallpaperController).setNotificationShadeZoom(floatThat { it != 0f })
+ verify(listener).onWallpaperZoomOutChanged(floatThat { it != 0f })
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_NOTIFICATION_SHADE_BLUR)
fun updateBlurCallback_setsOpaque_whenScrim() {
scrimVisibilityCaptor.value.accept(ScrimController.OPAQUE)
@@ -488,10 +521,10 @@
}
private fun enableSplitShade() {
- `when` (shadeModeInteractor.isSplitShade).thenReturn(true)
+ `when`(shadeModeInteractor.isSplitShade).thenReturn(true)
}
private fun disableSplitShade() {
- `when` (shadeModeInteractor.isSplitShade).thenReturn(false)
+ `when`(shadeModeInteractor.isSplitShade).thenReturn(false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 6c498c8..5ec9b60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
@@ -799,8 +800,8 @@
}
private val PROMOTED_CONTENT_WITH_COLOR =
- PromotedNotificationContentModel.Builder("notif")
- .apply {
+ PromotedNotificationContentBuilder("notif")
+ .applyToShared {
this.colors =
PromotedNotificationContentModel.Colors(
backgroundColor = PROMOTED_BACKGROUND_COLOR,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index 7f8f5f4..9ce43a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -30,7 +30,7 @@
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -420,7 +420,7 @@
// WHEN the notif gets a new UID that starts as visible
activityManagerRepository.fake.startingIsAppVisibleValue = true
val newPromotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.shortCriticalText = "Arrived"
}
val newPromotedContent = newPromotedContentBuilder.build()
@@ -452,6 +452,6 @@
companion object {
private const val UID = 885
- private val PROMOTED_CONTENT = PromotedNotificationContentModel.Builder("notif1").build()
+ private val PROMOTED_CONTENT = PromotedNotificationContentBuilder("notif1").build()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
index 0b9b297..202d5cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
@@ -36,7 +36,7 @@
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.addNotif
import com.android.systemui.statusbar.notification.data.repository.removeNotif
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.testKosmos
@@ -65,7 +65,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock<StatusBarIconView>(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -96,7 +96,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -115,7 +115,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -135,7 +135,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -158,12 +158,12 @@
activeNotificationModel(
key = "notif1",
statusBarChipIcon = firstIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
),
activeNotificationModel(
key = "notif2",
statusBarChipIcon = secondIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
),
activeNotificationModel(
key = "notif3",
@@ -195,7 +195,7 @@
key = "notif",
uid = uid,
statusBarChipIcon = mock<StatusBarIconView>(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
)
@@ -223,14 +223,14 @@
key = "promotedNormal",
statusBarChipIcon = mock(),
promotedContent =
- PromotedNotificationContentModel.Builder("promotedNormal").build(),
+ PromotedNotificationContentBuilder("promotedNormal").build(),
callType = CallType.None,
),
activeNotificationModel(
key = "promotedCall",
statusBarChipIcon = mock(),
promotedContent =
- PromotedNotificationContentModel.Builder("promotedCall").build(),
+ PromotedNotificationContentBuilder("promotedCall").build(),
callType = CallType.Ongoing,
),
)
@@ -256,7 +256,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = firstIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -269,7 +269,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = secondIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -282,7 +282,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = thirdIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -302,7 +302,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -325,7 +325,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -348,7 +348,7 @@
activeNotificationModel(
key = "notif1",
statusBarChipIcon = firstIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
setNotifs(listOf(notif1))
assertThat(latest!!.map { it.key }).containsExactly("notif1").inOrder()
@@ -359,7 +359,7 @@
activeNotificationModel(
key = "notif2",
statusBarChipIcon = secondIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
setNotifs(listOf(notif1, notif2))
@@ -380,7 +380,7 @@
// WHEN notif1 gets an update
val notif1NewPromotedContent =
- PromotedNotificationContentModel.Builder("notif1").apply {
+ PromotedNotificationContentBuilder("notif1").applyToShared {
this.shortCriticalText = "Arrived"
}
setNotifs(
@@ -426,8 +426,7 @@
key = notif1Info.key,
uid = notif1Info.uid,
statusBarChipIcon = notif1Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif1Info.key).build(),
)
)
activityManagerRepository.fake.setIsAppVisible(notif1Info.uid, isAppVisible = false)
@@ -443,8 +442,7 @@
key = notif2Info.key,
uid = notif2Info.uid,
statusBarChipIcon = notif2Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif2Info.key).build(),
)
)
activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = false)
@@ -482,16 +480,14 @@
key = notif1Info.key,
uid = notif1Info.uid,
statusBarChipIcon = notif1Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif1Info.key).build(),
)
val notif2 =
activeNotificationModel(
key = notif2Info.key,
uid = notif2Info.uid,
statusBarChipIcon = notif2Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif2Info.key).build(),
)
setNotifs(listOf(notif1, notif2))
assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
@@ -537,16 +533,14 @@
key = notif1Info.key,
uid = notif1Info.uid,
statusBarChipIcon = notif1Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif1Info.key).build(),
)
val notif2 =
activeNotificationModel(
key = notif2Info.key,
uid = notif2Info.uid,
statusBarChipIcon = notif2Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif2Info.key).build(),
)
setNotifs(listOf(notif1, notif2))
assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
@@ -567,8 +561,7 @@
key = notif3Info.key,
uid = notif3Info.uid,
statusBarChipIcon = notif3Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif3Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif3Info.key).build(),
)
setNotifs(listOf(notif1, notif2, notif3))
@@ -597,8 +590,7 @@
key = notif1Info.key,
uid = notif1Info.uid,
statusBarChipIcon = notif1Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif1Info.key).build(),
)
setNotifs(listOf(notif1))
@@ -609,8 +601,7 @@
key = notif2Info.key,
uid = notif2Info.uid,
statusBarChipIcon = notif2Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif2Info.key).build(),
)
setNotifs(listOf(notif1, notif2))
@@ -637,7 +628,7 @@
// WHEN notif2 gets an update
val notif2NewPromotedContent =
- PromotedNotificationContentModel.Builder("notif2").apply {
+ PromotedNotificationContentBuilder("notif2").applyToShared {
this.shortCriticalText = "Arrived"
}
setNotifs(
@@ -662,8 +653,7 @@
key = notif3Info.key,
uid = notif3Info.uid,
statusBarChipIcon = notif3Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif3Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif3Info.key).build(),
)
setNotifs(listOf(notif1, notif2, notif3))
@@ -710,8 +700,7 @@
activeNotificationModel(
key = "notif|uid1",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("notif|uid1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif|uid1").build(),
)
)
)
@@ -725,8 +714,7 @@
activeNotificationModel(
key = "notif|uid2",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("notif|uid2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif|uid2").build(),
)
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 8368fa6..eecdbbf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -43,6 +43,7 @@
import com.android.systemui.statusbar.notification.data.repository.UnconfinedFakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -101,7 +102,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -121,7 +122,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -142,7 +143,7 @@
key = "notif",
appName = "Fake App Name",
statusBarChipIcon = icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -172,7 +173,7 @@
key = notifKey,
appName = "Fake App Name",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder(notifKey).build(),
+ promotedContent = PromotedNotificationContentBuilder(notifKey).build(),
)
)
)
@@ -195,7 +196,7 @@
val latest by collectLastValue(underTest.chips)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.colors =
PromotedNotificationContentModel.Colors(
backgroundColor = 56,
@@ -229,12 +230,12 @@
activeNotificationModel(
key = "notif1",
statusBarChipIcon = firstIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
),
activeNotificationModel(
key = "notif2",
statusBarChipIcon = secondIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
),
activeNotificationModel(
key = "notif3",
@@ -264,13 +265,12 @@
activeNotificationModel(
key = firstKey,
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder(firstKey).build(),
+ promotedContent = PromotedNotificationContentBuilder(firstKey).build(),
),
activeNotificationModel(
key = secondKey,
statusBarChipIcon = null,
- promotedContent =
- PromotedNotificationContentModel.Builder(secondKey).build(),
+ promotedContent = PromotedNotificationContentBuilder(secondKey).build(),
),
activeNotificationModel(
key = thirdKey,
@@ -294,7 +294,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.shortCriticalText = "Arrived"
this.time = When.Time(currentTime + 30.minutes.inWholeMilliseconds)
}
@@ -321,7 +321,7 @@
val latest by collectLastValue(underTest.chips)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply { this.time = null }
+ PromotedNotificationContentBuilder("notif").applyToShared { this.time = null }
setNotifs(
listOf(
activeNotificationModel(
@@ -346,7 +346,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.wasPromotedAutomatically = true
this.time = When.Time(currentTime + 30.minutes.inWholeMilliseconds)
}
@@ -374,7 +374,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.wasPromotedAutomatically = false
this.time = When.Time(currentTime + 30.minutes.inWholeMilliseconds)
}
@@ -402,7 +402,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 13.minutes.inWholeMilliseconds)
}
@@ -430,7 +430,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 500)
}
@@ -458,7 +458,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime)
}
@@ -486,7 +486,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime - 2.minutes.inWholeMilliseconds)
}
@@ -515,7 +515,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 3.minutes.inWholeMilliseconds)
}
@@ -555,7 +555,7 @@
val whenElapsed = currentElapsed - 1.minutes.inWholeMilliseconds
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time =
When.Chronometer(elapsedRealtimeMillis = whenElapsed, isCountDown = false)
}
@@ -592,7 +592,7 @@
val whenElapsed = currentElapsed + 10.minutes.inWholeMilliseconds
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time =
When.Chronometer(elapsedRealtimeMillis = whenElapsed, isCountDown = true)
}
@@ -623,7 +623,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
setNotifs(
@@ -653,7 +653,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
setNotifs(
@@ -690,11 +690,11 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
val otherPromotedContentBuilder =
- PromotedNotificationContentModel.Builder("other notif").apply {
+ PromotedNotificationContentBuilder("other notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
val icon = createStatusBarIconViewOrNull()
@@ -738,7 +738,7 @@
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
setNotifs(
@@ -781,7 +781,7 @@
activeNotificationModel(
key,
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder(key).build(),
+ promotedContent = PromotedNotificationContentBuilder(key).build(),
)
)
)
@@ -809,7 +809,7 @@
activeNotificationModel(
key,
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder(key).build(),
+ promotedContent = PromotedNotificationContentBuilder(key).build(),
)
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 608a84b..83b3c9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -63,7 +63,7 @@
import com.android.systemui.statusbar.notification.data.repository.addNotif
import com.android.systemui.statusbar.notification.data.repository.addNotifs
import com.android.systemui.statusbar.notification.data.repository.removeNotif
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
@@ -358,7 +358,7 @@
addOngoingCallState(key = "call")
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.shortCriticalText = "Some text here"
}
activeNotificationListRepository.addNotif(
@@ -741,7 +741,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -765,7 +765,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -791,14 +791,12 @@
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
)
)
@@ -822,14 +820,12 @@
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
)
)
@@ -857,20 +853,17 @@
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = thirdIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
)
)
@@ -896,26 +889,22 @@
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = thirdIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
activeNotificationModel(
key = "fourthNotif",
statusBarChipIcon = fourthIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("fourthNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("fourthNotif").build(),
),
)
)
@@ -941,20 +930,17 @@
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
)
)
@@ -973,26 +959,22 @@
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
activeNotificationModel(
key = "fourthNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("fourthNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("fourthNotif").build(),
),
)
)
@@ -1016,14 +998,12 @@
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
)
)
@@ -1050,20 +1030,17 @@
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = thirdIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
)
)
@@ -1092,7 +1069,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
@@ -1114,14 +1091,14 @@
activeNotificationModel(
key = "notif1",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
activeNotificationListRepository.addNotif(
activeNotificationModel(
key = "notif2",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -1143,14 +1120,14 @@
activeNotificationModel(
key = "notif1",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
activeNotificationListRepository.addNotif(
activeNotificationModel(
key = "notif2",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -1178,7 +1155,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
addOngoingCallState(key = callNotificationKey)
@@ -1189,7 +1166,7 @@
activeNotificationModel(
key = "notif2",
statusBarChipIcon = notifIcon2,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -1214,7 +1191,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -1265,7 +1242,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
@@ -1304,7 +1281,7 @@
activeNotificationModel(
key = "notif",
statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
// And everything else hidden
@@ -1382,7 +1359,7 @@
activeNotificationModel(
key = "notif1",
statusBarChipIcon = notif1Icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
)
@@ -1436,7 +1413,7 @@
activeNotificationModel(
key = "notif2",
statusBarChipIcon = notif2Icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index d3befa9..29bb29f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -29,7 +29,7 @@
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -170,7 +170,7 @@
val promoted1 =
activeNotificationModel(
key = "notif1",
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
val notPromoted2 = activeNotificationModel(key = "notif2", promotedContent = null)
@@ -208,14 +208,14 @@
val promoted1 =
activeNotificationModel(
key = "notif1",
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
val notPromoted2 = activeNotificationModel(key = "notif2", promotedContent = null)
val notPromoted3 = activeNotificationModel(key = "notif3", promotedContent = null)
val promoted4 =
activeNotificationModel(
key = "notif4",
- promotedContent = PromotedNotificationContentModel.Builder("notif4").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif4").build(),
)
activeNotificationListRepository.activeNotifications.value =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 35b19c1..5c749e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -28,7 +28,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.byKey
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
@@ -130,7 +131,7 @@
val promoted2 =
mockNotificationEntry(
"key2",
- promotedContent = PromotedNotificationContentModel.Builder("key2").build(),
+ promotedContent = PromotedNotificationContentBuilder("key2").build(),
)
underTest.setRenderedList(listOf(notPromoted1, promoted2))
@@ -149,7 +150,7 @@
private fun mockNotificationEntry(
key: String,
rank: Int = 0,
- promotedContent: PromotedNotificationContentModel? = null,
+ promotedContent: PromotedNotificationContentModels? = null,
): NotificationEntry {
val nBuilder = Notification.Builder(context, "a")
val notification = nBuilder.build()
@@ -165,7 +166,7 @@
whenever(this.representativeEntry).thenReturn(this)
whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
whenever(this.sbn).thenReturn(mockSbn)
- whenever(this.promotedNotificationContentModel).thenReturn(promotedContent)
+ whenever(this.promotedNotificationContentModels).thenReturn(promotedContent)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index ee698ae..41120a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -33,8 +33,10 @@
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.promoted.domain.interactor.aodPromotedNotificationInteractor
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style.Base
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.byIsAmbient
import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
import com.android.systemui.statusbar.notification.shared.byIsPromoted
@@ -354,6 +356,6 @@
private fun promotedContent(
key: String,
style: PromotedNotificationContentModel.Style,
-): PromotedNotificationContentModel {
- return PromotedNotificationContentModel.Builder(key).apply { this.style = style }.build()
+): PromotedNotificationContentModels {
+ return PromotedNotificationContentBuilder(key).applyToShared { this.style = style }.build()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index 0ac944a..cc016b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -33,18 +33,21 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator.Companion.EXTRA_WAS_AUTOMATICALLY_PROMOTED
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.row.RowImageInflater
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.android.systemui.util.time.systemClock
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertNotNull
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import org.junit.Test
@@ -112,12 +115,43 @@
setContentText(TEST_CONTENT_TEXT)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.subText).isEqualTo(TEST_SUB_TEXT)
- assertThat(content?.title).isEqualTo(TEST_CONTENT_TITLE)
- assertThat(content?.text).isEqualTo(TEST_CONTENT_TEXT)
+ content.privateVersion.apply {
+ assertThat(subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ }
+
+ content.publicVersion.apply {
+ assertThat(subText).isNull()
+ assertThat(title).isNull()
+ assertThat(text).isNull()
+ }
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractsContent_commonFields_noRedaction() {
+ val entry = createEntry {
+ setSubText(TEST_SUB_TEXT)
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ }
+
+ val content = requireContent(entry, redactionType = REDACTION_TYPE_NONE)
+
+ content.privateVersion.apply {
+ assertThat(subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ }
+
+ content.publicVersion.apply {
+ assertThat(subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ }
}
@Test
@@ -125,9 +159,9 @@
fun extractContent_wasPromotedAutomatically_false() {
val entry = createEntry { extras.putBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, false) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content!!.wasPromotedAutomatically).isFalse()
+ assertThat(content.wasPromotedAutomatically).isFalse()
}
@Test
@@ -135,9 +169,9 @@
fun extractContent_wasPromotedAutomatically_true() {
val entry = createEntry { extras.putBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, true) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content!!.wasPromotedAutomatically).isTrue()
+ assertThat(content.wasPromotedAutomatically).isTrue()
}
@Test
@@ -146,10 +180,9 @@
fun extractContent_apiFlagOff_shortCriticalTextNotExtracted() {
val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content).isNotNull()
- assertThat(content?.text).isNull()
+ assertThat(content.text).isNull()
}
@Test
@@ -161,10 +194,9 @@
fun extractContent_apiFlagOn_shortCriticalTextExtracted() {
val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content).isNotNull()
- assertThat(content?.shortCriticalText).isEqualTo(TEST_SHORT_CRITICAL_TEXT)
+ assertThat(content.shortCriticalText).isEqualTo(TEST_SHORT_CRITICAL_TEXT)
}
@Test
@@ -176,10 +208,9 @@
fun extractContent_noShortCriticalTextSet_textIsNull() {
val entry = createEntry { setShortCriticalText(null) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content).isNotNull()
- assertThat(content?.shortCriticalText).isNull()
+ assertThat(content.shortCriticalText).isNull()
}
@Test
@@ -379,17 +410,14 @@
setWhen(providedCurrentTime)
}
- val content = extractContent(entry)
-
- assertThat(content).isNotNull()
+ val content = requireContent(entry).privateVersion
when (expected) {
- ExpectedTime.Null -> assertThat(content?.time).isNull()
+ ExpectedTime.Null -> assertThat(content.time).isNull()
ExpectedTime.Time -> {
- val actual = content?.time as? When.Time
- assertThat(actual).isNotNull()
- assertThat(actual?.currentTimeMillis).isEqualTo(expectedCurrentTime)
+ val actual = assertNotNull(content.time as? When.Time)
+ assertThat(actual.currentTimeMillis).isEqualTo(expectedCurrentTime)
}
ExpectedTime.CountDown,
@@ -398,23 +426,24 @@
expectedCurrentTime + systemClock.elapsedRealtime() -
systemClock.currentTimeMillis()
- val actual = content?.time as? When.Chronometer
- assertThat(actual).isNotNull()
- assertThat(actual?.elapsedRealtimeMillis).isEqualTo(expectedElapsedRealtime)
- assertThat(actual?.isCountDown).isEqualTo(expected == ExpectedTime.CountDown)
+ val actual = assertNotNull(content.time as? When.Chronometer)
+ assertThat(actual.elapsedRealtimeMillis).isEqualTo(expectedElapsedRealtime)
+ assertThat(actual.isCountDown).isEqualTo(expected == ExpectedTime.CountDown)
}
}
}
+ // TODO: Add tests for the style of the publicVersion once we implement that
+
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun extractContent_fromBaseStyle() {
val entry = createEntry { setStyle(null) }
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.Base)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
}
@Test
@@ -422,10 +451,10 @@
fun extractContent_fromBigPictureStyle() {
val entry = createEntry { setStyle(BigPictureStyle()) }
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.BigPicture)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigPicture)
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
}
@Test
@@ -442,12 +471,15 @@
)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.BigText)
- assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
- assertThat(content?.text).isEqualTo(TEST_BIG_TEXT)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content.privateVersion.text).isEqualTo(TEST_BIG_TEXT)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
}
@Test
@@ -464,12 +496,15 @@
)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.BigText)
- assertThat(content?.title).isEqualTo(TEST_CONTENT_TITLE)
- assertThat(content?.text).isEqualTo(TEST_BIG_TEXT)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(content.privateVersion.text).isEqualTo(TEST_BIG_TEXT)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
}
@Test
@@ -486,12 +521,15 @@
)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.BigText)
- assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
- assertThat(content?.text).isEqualTo(TEST_CONTENT_TEXT)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content.privateVersion.text).isEqualTo(TEST_CONTENT_TEXT)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
}
@Test
@@ -506,11 +544,14 @@
)
val entry = createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) }
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.Call)
- assertThat(content?.title).isEqualTo(TEST_PERSON_NAME)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Call)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_PERSON_NAME)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
}
@Test
@@ -524,13 +565,17 @@
setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.Progress)
- assertThat(content?.newProgress).isNotNull()
- assertThat(content?.newProgress?.progress).isEqualTo(75)
- assertThat(content?.newProgress?.progressMax).isEqualTo(100)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Progress)
+ val newProgress = assertNotNull(content.privateVersion.newProgress)
+ assertThat(newProgress.progress).isEqualTo(75)
+ assertThat(newProgress.progressMax).isEqualTo(100)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
+ assertThat(content.publicVersion.newProgress).isNull()
}
@Test
@@ -540,10 +585,11 @@
setStyle(MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON))
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.Ineligible)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Ineligible)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Ineligible)
}
@Test
@@ -553,18 +599,13 @@
setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ false)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
+ val oldProgress = assertNotNull(content.privateVersion.oldProgress)
- val oldProgress = content?.oldProgress
- assertThat(oldProgress).isNotNull()
-
- assertThat(content).isNotNull()
- assertThat(content?.oldProgress).isNotNull()
- assertThat(content?.oldProgress?.progress).isEqualTo(TEST_PROGRESS)
- assertThat(content?.oldProgress?.max).isEqualTo(TEST_PROGRESS_MAX)
- assertThat(content?.oldProgress?.isIndeterminate).isFalse()
+ assertThat(oldProgress.progress).isEqualTo(TEST_PROGRESS)
+ assertThat(oldProgress.max).isEqualTo(TEST_PROGRESS_MAX)
+ assertThat(oldProgress.isIndeterminate).isFalse()
}
@Test
@@ -574,18 +615,25 @@
setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ true)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
+ val oldProgress = assertNotNull(content.privateVersion.oldProgress)
- assertThat(content).isNotNull()
- assertThat(content?.oldProgress).isNotNull()
- assertThat(content?.oldProgress?.progress).isEqualTo(TEST_PROGRESS)
- assertThat(content?.oldProgress?.max).isEqualTo(TEST_PROGRESS_MAX)
- assertThat(content?.oldProgress?.isIndeterminate).isTrue()
+ assertThat(oldProgress.progress).isEqualTo(TEST_PROGRESS)
+ assertThat(oldProgress.max).isEqualTo(TEST_PROGRESS_MAX)
+ assertThat(oldProgress.isIndeterminate).isTrue()
}
- private fun extractContent(entry: NotificationEntry): PromotedNotificationContentModel? {
+ private fun requireContent(
+ entry: NotificationEntry,
+ redactionType: Int = REDACTION_TYPE_PUBLIC,
+ ): PromotedNotificationContentModels = assertNotNull(extractContent(entry, redactionType))
+
+ private fun extractContent(
+ entry: NotificationEntry,
+ redactionType: Int = REDACTION_TYPE_PUBLIC,
+ ): PromotedNotificationContentModels? {
val recoveredBuilder = Notification.Builder(context, entry.sbn.notification)
- return underTest.extractContent(entry, recoveredBuilder, imageModelProvider)
+ return underTest.extractContent(entry, recoveredBuilder, redactionType, imageModelProvider)
}
private fun createEntry(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
index 873ab5b..42c3f66 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
@@ -44,7 +44,7 @@
import com.android.systemui.statusbar.notification.data.repository.addNotif
import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
import com.android.systemui.testKosmos
@@ -562,14 +562,14 @@
activeNotificationModel(
key = "notif1",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
activeNotificationListRepository.addNotif(
activeNotificationModel(
key = "notif2",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -608,14 +608,14 @@
activeNotificationModel(
key = "notif1",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
activeNotificationListRepository.addNotif(
activeNotificationModel(
key = "notif2",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -643,8 +643,7 @@
collectLastValue(underTest.aodPromotedNotification)
// THEN the ron is first because the call has no content
- assertThat(topPromotedNotificationContent?.identity?.key)
- .isEqualTo("0|test_pkg|0|ron|0")
+ assertThat(topPromotedNotificationContent?.key).isEqualTo("0|test_pkg|0|ron|0")
}
@Test
@@ -663,8 +662,7 @@
collectLastValue(underTest.aodPromotedNotification)
// THEN the call is the top notification
- assertThat(topPromotedNotificationContent?.identity?.key)
- .isEqualTo("0|test_pkg|0|call|0")
+ assertThat(topPromotedNotificationContent?.key).isEqualTo("0|test_pkg|0|call|0")
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 99f2596..19b1046 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -67,11 +67,11 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
-import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -389,8 +389,8 @@
@Test
@DisableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
public void testExtractsPromotedContent_notWhenBothFlagsDisabled() throws Exception {
- final PromotedNotificationContentModel content =
- new PromotedNotificationContentModel.Builder("key").build();
+ final PromotedNotificationContentModels content =
+ new PromotedNotificationContentBuilder("key").build();
mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
@@ -401,43 +401,43 @@
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(StatusBarNotifChips.FLAG_NAME)
- public void testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled()
+ public void testExtractsPromotedContent_whePromotedNotificationUiFlagEnabled()
throws Exception {
- final PromotedNotificationContentModel content =
- new PromotedNotificationContentModel.Builder("key").build();
+ final PromotedNotificationContentModels content =
+ new PromotedNotificationContentBuilder("key").build();
mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+ assertEquals(content, mRow.getEntry().getPromotedNotificationContentModels());
}
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
@DisableFlags(PromotedNotificationUi.FLAG_NAME)
public void testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() throws Exception {
- final PromotedNotificationContentModel content =
- new PromotedNotificationContentModel.Builder("key").build();
+ final PromotedNotificationContentModels content =
+ new PromotedNotificationContentBuilder("key").build();
mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+ assertEquals(content, mRow.getEntry().getPromotedNotificationContentModels());
}
@Test
@EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
public void testExtractsPromotedContent_whenBothFlagsEnabled() throws Exception {
- final PromotedNotificationContentModel content =
- new PromotedNotificationContentModel.Builder("key").build();
+ final PromotedNotificationContentModels content =
+ new PromotedNotificationContentBuilder("key").build();
mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+ assertEquals(content, mRow.getEntry().getPromotedNotificationContentModels());
}
@Test
@@ -448,7 +448,7 @@
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertNull(mRow.getEntry().getPromotedNotificationContentModel());
+ assertNull(mRow.getEntry().getPromotedNotificationContentModels());
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 063a04a..dcba3e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -44,7 +44,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -456,7 +456,7 @@
@Test
@DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_notWhenBothFlagsDisabled() {
- val content = PromotedNotificationContentModel.Builder("key").build()
+ val content = PromotedNotificationContentBuilder("key").build()
promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
@@ -468,38 +468,38 @@
@EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled() {
- val content = PromotedNotificationContentModel.Builder("key").build()
+ val content = PromotedNotificationContentBuilder("key").build()
promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+ Assert.assertEquals(content, row.entry.promotedNotificationContentModels)
}
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
@DisableFlags(PromotedNotificationUi.FLAG_NAME)
fun testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() {
- val content = PromotedNotificationContentModel.Builder("key").build()
+ val content = PromotedNotificationContentBuilder("key").build()
promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+ Assert.assertEquals(content, row.entry.promotedNotificationContentModels)
}
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_whenBothFlagsEnabled() {
- val content = PromotedNotificationContentModel.Builder("key").build()
+ val content = PromotedNotificationContentBuilder("key").build()
promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+ Assert.assertEquals(content, row.entry.promotedNotificationContentModels)
}
@Test
@@ -510,7 +510,7 @@
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertNull(row.entry.promotedNotificationContentModel)
+ Assert.assertNull(row.entry.promotedNotificationContentModels)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index c58b4bc..18074d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -40,7 +40,7 @@
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -170,7 +170,7 @@
@Test
fun interactorHasOngoingCallNotif_repoHasPromotedContent() =
testScope.runTest {
- val promotedContent = PromotedNotificationContentModel.Builder("ongoingNotif").build()
+ val promotedContent = PromotedNotificationContentBuilder("ongoingNotif").build()
setNotifOnRepo(
activeNotificationModel(
key = "ongoingNotif",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
index 84f1d5c..c071327a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
@@ -29,7 +29,7 @@
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
@@ -75,7 +75,7 @@
val startTimeMs = 1000L
val testIconView: StatusBarIconView = mock()
val testIntent: PendingIntent = mock()
- val testPromotedContent = PromotedNotificationContentModel.Builder(key).build()
+ val testPromotedContent = PromotedNotificationContentBuilder(key).build()
addOngoingCallState(
key = key,
startTimeMs = startTimeMs,
@@ -106,7 +106,7 @@
val startTimeMs = 1000L
val testIconView: StatusBarIconView = mock()
val testIntent: PendingIntent = mock()
- val testPromotedContent = PromotedNotificationContentModel.Builder(key).build()
+ val testPromotedContent = PromotedNotificationContentBuilder(key).build()
addOngoingCallState(
key = key,
startTimeMs = startTimeMs,
diff --git a/packages/SystemUI/res/layout/activity_rear_display_enabled.xml b/packages/SystemUI/res/layout/activity_rear_display_enabled.xml
index f900626..6b633e0 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_enabled.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_enabled.xml
@@ -56,6 +56,7 @@
android:gravity="center_horizontal" />
<TextView
+ android:id="@+id/seekbar_instructions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rear_display_unfolded_front_screen_on_slide_to_cancel"
@@ -73,4 +74,13 @@
android:background="@null"
android:gravity="center_horizontal" />
+ <Button
+ android:id="@+id/cancel_button"
+ android:text="@string/cancel"
+ android:layout_width="@dimen/rear_display_animation_width_opened"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:visibility="gone"
+ style="@style/Widget.Dialog.Button.BorderButton"/>
+
</LinearLayout>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index ea73216..b8cd5be 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -208,4 +208,19 @@
}
mMatrix.postTranslate(translateX, translateY);
}
+
+ /**
+ * A factory that returns a new instance of the {@link PreviewPositionHelper}.
+ * <p>{@link PreviewPositionHelper} is a stateful helper, and hence when using it in distinct
+ * scenarios, prefer fetching an object using this factory</p>
+ * <p>Additionally, helpful for injecting mocks in tests</p>
+ */
+ public static class PreviewPositionHelperFactory {
+ /**
+ * Returns a new {@link PreviewPositionHelper} for use in a distinct scenario.
+ */
+ public PreviewPositionHelper create() {
+ return new PreviewPositionHelper();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8da06ac..099a7f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -140,6 +140,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -364,6 +365,7 @@
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
private final Lazy<ShadeController> mShadeController;
private final Lazy<CommunalSceneInteractor> mCommunalSceneInteractor;
+ private final Lazy<CommunalSettingsInteractor> mCommunalSettingsInteractor;
/*
* Records the user id on request to go away, for validation when WM calls back to start the
* exit animation.
@@ -1567,6 +1569,7 @@
KeyguardInteractor keyguardInteractor,
KeyguardTransitionBootInteractor transitionBootInteractor,
Lazy<CommunalSceneInteractor> communalSceneInteractor,
+ Lazy<CommunalSettingsInteractor> communalSettingsInteractor,
WindowManagerOcclusionManager wmOcclusionManager) {
mContext = context;
mUserTracker = userTracker;
@@ -1609,6 +1612,7 @@
mKeyguardInteractor = keyguardInteractor;
mTransitionBootInteractor = transitionBootInteractor;
mCommunalSceneInteractor = communalSceneInteractor;
+ mCommunalSettingsInteractor = communalSettingsInteractor;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -2429,9 +2433,18 @@
private void doKeyguardLocked(Bundle options) {
// If the power button behavior requests to open the glanceable hub.
if (options != null && options.getBoolean(EXTRA_TRIGGER_HUB)) {
- // Set the hub to show immediately when the SysUI window shows, then continue to lock
- // the device.
- mCommunalSceneInteractor.get().showHubFromPowerButton();
+ if (mCommunalSettingsInteractor.get().getAutoOpenEnabled().getValue()) {
+ // Set the hub to show immediately when the SysUI window shows, then continue to
+ // lock the device.
+ mCommunalSceneInteractor.get().showHubFromPowerButton();
+ } else {
+ // If the hub is not available, go to sleep instead of locking. This can happen
+ // because the power button behavior does not check all possible reasons the hub
+ // might be disabled.
+ mPM.goToSleep(android.os.SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ return;
+ }
}
int currentUserId = mSelectedUserInteractor.getSelectedUserId();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
index ddccc5d..41d14b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
@@ -20,7 +20,16 @@
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for reading or using the keyguard wm state refactor flag state. */
+/**
+ * Helper for reading or using the keyguard_wm_state_refactor flag state.
+ *
+ * keyguard_wm_state_refactor works both with and without flexiglass (scene_container), but
+ * flexiglass requires keyguard_wm_state_refactor. For this reason, this class will return isEnabled
+ * if either keyguard_wm_state_refactor OR scene_container are enabled. This enables us to roll out
+ * keyguard_wm_state_refactor independently of scene_container, while also ensuring that
+ * scene_container rolling out ahead of keyguard_wm_state_refactor causes code gated by
+ * KeyguardWmStateRefactor to be enabled as well.
+ */
@Suppress("NOTHING_TO_INLINE")
object KeyguardWmStateRefactor {
/** The aconfig flag name */
@@ -30,10 +39,9 @@
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
- /** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.keyguardWmStateRefactor()
+ get() = Flags.keyguardWmStateRefactor() || Flags.sceneContainer()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 6b1248b..1fe6eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -182,6 +183,7 @@
KeyguardInteractor keyguardInteractor,
KeyguardTransitionBootInteractor transitionBootInteractor,
Lazy<CommunalSceneInteractor> communalSceneInteractor,
+ Lazy<CommunalSettingsInteractor> communalSettingsInteractor,
WindowManagerOcclusionManager windowManagerOcclusionManager) {
return new KeyguardViewMediator(
context,
@@ -234,6 +236,7 @@
keyguardInteractor,
transitionBootInteractor,
communalSceneInteractor,
+ communalSettingsInteractor,
windowManagerOcclusionManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 9312bca..a0458f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -64,7 +64,7 @@
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
- duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ duration = 200.milliseconds,
onStep = alphaForAnimationStep,
// Rapid swipes to bouncer, and may end up skipping intermediate values that would've
// caused a complete fade out of lockscreen elements. Ensure it goes to 0f.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index b889c3e..9f04f69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -19,6 +19,8 @@
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
+import android.graphics.Canvas
+import android.graphics.Path
import android.graphics.PointF
import android.graphics.Rect
import android.os.Bundle
@@ -125,7 +127,6 @@
import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams
-import com.android.systemui.qs.composefragment.ui.notificationScrimClip
import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings
import com.android.systemui.qs.composefragment.ui.toEditMode
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
@@ -241,7 +242,7 @@
FrameLayoutTouchPassthrough(
context,
{ notificationScrimClippingParams.isEnabled },
- { notificationScrimClippingParams.params.top },
+ snapshotFlow { notificationScrimClippingParams.params },
// Only allow scrolling when we are fully expanded. That way, we don't intercept
// swipes in lockscreen (when somehow QS is receiving touches).
{ (scrollState.canScrollForward && viewModel.isQsFullyExpanded) || isCustomizing },
@@ -276,11 +277,6 @@
}
}
.graphicsLayer { alpha = viewModel.viewAlpha }
- .thenIf(notificationScrimClippingParams.isEnabled) {
- Modifier.notificationScrimClip {
- notificationScrimClippingParams.params
- }
- }
.thenIf(!Flags.notificationShadeBlur()) {
Modifier.offset {
IntOffset(
@@ -1061,17 +1057,75 @@
private class FrameLayoutTouchPassthrough(
context: Context,
private val clippingEnabledProvider: () -> Boolean,
- private val clippingTopProvider: () -> Int,
+ private val clippingParams: Flow<NotificationScrimClipParams>,
private val canScrollForwardQs: () -> Boolean,
private val emitMotionEventForFalsing: () -> Unit,
) : FrameLayout(context) {
+
+ init {
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ clippingParams.collect { currentClipParams = it }
+ }
+ }
+ }
+
+ private val currentClippingPath = Path()
+ private var lastWidth = -1
+ set(value) {
+ if (field != value) {
+ field = value
+ updateClippingPath()
+ }
+ }
+
+ private var currentClipParams = NotificationScrimClipParams()
+ set(value) {
+ if (field != value) {
+ field = value
+ updateClippingPath()
+ }
+ }
+
+ private fun updateClippingPath() {
+ currentClippingPath.rewind()
+ if (clippingEnabledProvider()) {
+ val right = width + currentClipParams.rightInset
+ val left = -currentClipParams.leftInset
+ val top = currentClipParams.top
+ val bottom = currentClipParams.bottom
+ currentClippingPath.addRoundRect(
+ left.toFloat(),
+ top.toFloat(),
+ right.toFloat(),
+ bottom.toFloat(),
+ currentClipParams.radius.toFloat(),
+ currentClipParams.radius.toFloat(),
+ Path.Direction.CW,
+ )
+ }
+ invalidate()
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ lastWidth = right - left
+ }
+
+ override fun dispatchDraw(canvas: Canvas) {
+ if (!currentClippingPath.isEmpty) {
+ canvas.clipOutPath(currentClippingPath)
+ }
+ super.dispatchDraw(canvas)
+ }
+
override fun isTransformedTouchPointInView(
x: Float,
y: Float,
child: View?,
outLocalPoint: PointF?,
): Boolean {
- return if (clippingEnabledProvider() && y + translationY > clippingTopProvider()) {
+ return if (clippingEnabledProvider() && y + translationY > currentClipParams.top) {
false
} else {
super.isTransformedTouchPointInView(x, y, child, outLocalPoint)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
deleted file mode 100644
index 3049a40..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.composefragment.ui
-
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.geometry.CornerRadius
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.ClipOp
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.CompositingStrategy
-import androidx.compose.ui.graphics.drawscope.clipRect
-import androidx.compose.ui.graphics.graphicsLayer
-
-/**
- * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
- * ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)`
- * from the QS container.
- */
-fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier {
- return this.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
- .drawWithContent {
- drawContent()
- val params = clipParams()
- val left = -params.leftInset.toFloat()
- val right = size.width + params.rightInset.toFloat()
- val top = params.top.toFloat()
- val bottom = params.bottom.toFloat()
- val clipSize = Size(right - left, bottom - top)
- if (!clipSize.isEmpty()) {
- clipRect {
- drawRoundRect(
- color = Color.Black,
- cornerRadius = CornerRadius(params.radius.toFloat()),
- blendMode = BlendMode.Clear,
- topLeft = Offset(left, top),
- size = Size(right - left, bottom - top),
- )
- }
- }
- }
-}
-
-/** Params for [notificationScrimClip]. */
-data class NotificationScrimClipParams(
- val top: Int = 0,
- val bottom: Int = 0,
- val leftInset: Int = 0,
- val rightInset: Int = 0,
- val radius: Int = 0,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt
new file mode 100644
index 0000000..db320d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.ui
+
+/** Params for [notificationScrimClip]. */
+data class NotificationScrimClipParams(
+ val top: Int = 0,
+ val bottom: Int = 0,
+ val leftInset: Int = 0,
+ val rightInset: Int = 0,
+ val radius: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
index 263ef09e..5d4a774 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
@@ -19,14 +19,18 @@
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.feature.flags.Flags
+import android.os.Handler
+import android.view.accessibility.AccessibilityManager
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
+import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -52,6 +56,8 @@
private val rearDisplayInnerDialogDelegateFactory: RearDisplayInnerDialogDelegate.Factory,
@Application private val scope: CoroutineScope,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val accessibilityManager: AccessibilityManager,
+ @Background private val handler: Handler,
) : CoreStartable, AutoCloseable {
companion object {
@@ -77,6 +83,12 @@
override fun start() {
if (Flags.deviceStateRdmV2()) {
var dialog: SystemUIDialog? = null
+ var touchExplorationEnabled = AtomicBoolean(false)
+
+ accessibilityManager.addTouchExplorationStateChangeListener(
+ { enabled -> touchExplorationEnabled.set(enabled) },
+ handler,
+ )
keyguardUpdateMonitor.registerCallback(keyguardCallback)
@@ -99,6 +111,7 @@
rearDisplayInnerDialogDelegateFactory.create(
rearDisplayContext,
deviceStateManager::cancelStateRequest,
+ touchExplorationEnabled.get(),
)
dialog = delegate.createDialog().apply { show() }
}
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt
index f5facf4..96f1bd2 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt
@@ -20,7 +20,10 @@
import android.content.Context
import android.os.Bundle
import android.view.MotionEvent
+import android.view.View
+import android.widget.Button
import android.widget.SeekBar
+import android.widget.TextView
import com.android.systemui.haptics.slider.HapticSlider
import com.android.systemui.haptics.slider.HapticSliderPlugin
import com.android.systemui.haptics.slider.HapticSliderViewBinder
@@ -45,6 +48,7 @@
internal constructor(
private val systemUIDialogFactory: SystemUIDialog.Factory,
@Assisted private val rearDisplayContext: Context,
+ @Assisted private val touchExplorationEnabled: Boolean,
private val vibratorHelper: VibratorHelper,
private val msdlPlayer: MSDLPlayer,
private val systemClock: SystemClock,
@@ -82,6 +86,7 @@
fun create(
rearDisplayContext: Context,
onCanceledRunnable: Runnable,
+ touchExplorationEnabled: Boolean,
): RearDisplayInnerDialogDelegate
}
@@ -95,11 +100,32 @@
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+
dialog.apply {
setContentView(R.layout.activity_rear_display_enabled)
setCanceledOnTouchOutside(false)
+ requireViewById<Button>(R.id.cancel_button).let { it ->
+ if (!touchExplorationEnabled) {
+ return@let
+ }
+
+ it.visibility = View.VISIBLE
+ it.setOnClickListener { onCanceledRunnable.run() }
+ }
+
+ requireViewById<TextView>(R.id.seekbar_instructions).let { it ->
+ if (touchExplorationEnabled) {
+ it.visibility = View.GONE
+ }
+ }
+
requireViewById<SeekBar>(R.id.seekbar).let { it ->
+ if (touchExplorationEnabled) {
+ it.visibility = View.GONE
+ return@let
+ }
+
// Create and bind the HapticSliderPlugin
val hapticSliderPlugin =
HapticSliderPlugin(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index fce5a16..e292bcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -24,6 +24,7 @@
import android.util.Log
import android.util.MathUtils
import android.view.Choreographer
+import android.view.Display
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -42,7 +43,9 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
@@ -52,6 +55,7 @@
import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
+import dagger.Lazy
import java.io.PrintWriter
import java.util.Optional
import javax.inject.Inject
@@ -83,6 +87,7 @@
private val appZoomOutOptional: Optional<AppZoomOut>,
@Application private val applicationScope: CoroutineScope,
dumpManager: DumpManager,
+ private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>,
) : ShadeExpansionListener, Dumpable {
companion object {
private const val WAKE_UP_ANIMATION_ENABLED = true
@@ -228,6 +233,14 @@
private data class WakeAndUnlockBlurData(val radius: Float, val useZoom: Boolean = true)
+ private val isShadeOnDefaultDisplay: Boolean
+ get() =
+ if (ShadeWindowGoesAround.isEnabled) {
+ shadeDisplaysRepository.get().displayId.value == Display.DEFAULT_DISPLAY
+ } else {
+ true
+ }
+
/** Blur radius of the wake and unlock animation on this frame, and whether to zoom out. */
private var wakeAndUnlockBlurData = WakeAndUnlockBlurData(0f)
set(value) {
@@ -265,9 +278,14 @@
var blur = shadeRadius.toInt()
// If the blur comes from waking up, we don't want to zoom out the background
val zoomOut =
- if (shadeRadius != wakeAndUnlockBlurData.radius || wakeAndUnlockBlurData.useZoom)
- blurRadiusToZoomOut(blurRadius = shadeRadius)
- else 0f
+ when {
+ // When the shade is in another display, we don't want to zoom out the background.
+ // Only the default display is supported right now.
+ !isShadeOnDefaultDisplay -> 0f
+ shadeRadius != wakeAndUnlockBlurData.radius || wakeAndUnlockBlurData.useZoom ->
+ blurRadiusToZoomOut(blurRadius = shadeRadius)
+ else -> 0f
+ }
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
if (!Flags.notificationShadeBlur()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index 1f2079d..356731c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.chips.notification.domain.model
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
/** Modeling all the data needed to render a status bar notification chip. */
data class NotificationChipModel(
@@ -25,7 +25,7 @@
/** The user-readable name of the app that posted this notification. */
val appName: String,
val statusBarChipIconView: StatusBarIconView?,
- val promotedContent: PromotedNotificationContentModel,
+ val promotedContent: PromotedNotificationContentModels,
/** The time when the notification first appeared as promoted. */
val creationTime: Long,
/** True if the app managing this notification is currently visible to the user. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index b303751..dfbd12d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -72,6 +72,8 @@
headsUpState: TopPinnedState
): OngoingActivityChipModel.Active {
StatusBarNotifChips.unsafeAssertInNewMode()
+ // Chips are never shown when locked, so it's safe to use the version with sensitive content
+ val chipContent = promotedContent.privateVersion
val contentDescription = getContentDescription(this.appName)
val icon =
if (this.statusBarChipIconView != null) {
@@ -123,21 +125,18 @@
)
}
- if (this.promotedContent.shortCriticalText != null) {
+ if (chipContent.shortCriticalText != null) {
return OngoingActivityChipModel.Active.Text(
key = this.key,
icon = icon,
colors = colors,
- text = this.promotedContent.shortCriticalText,
+ text = chipContent.shortCriticalText,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
)
}
- if (
- Flags.promoteNotificationsAutomatically() &&
- this.promotedContent.wasPromotedAutomatically
- ) {
+ if (Flags.promoteNotificationsAutomatically() && chipContent.wasPromotedAutomatically) {
// When we're promoting notifications automatically, the `when` time set on the
// notification will likely just be set to the current time, which would cause the chip
// to always show "now". We don't want early testers to get that experience since it's
@@ -151,7 +150,7 @@
)
}
- if (this.promotedContent.time == null) {
+ if (chipContent.time == null) {
return OngoingActivityChipModel.Active.IconOnly(
key = this.key,
icon = icon,
@@ -161,17 +160,17 @@
)
}
- when (this.promotedContent.time) {
+ when (chipContent.time) {
is PromotedNotificationContentModel.When.Time -> {
return if (
- this.promotedContent.time.currentTimeMillis >=
+ chipContent.time.currentTimeMillis >=
systemClock.currentTimeMillis() + FUTURE_TIME_THRESHOLD_MILLIS
) {
OngoingActivityChipModel.Active.ShortTimeDelta(
key = this.key,
icon = icon,
colors = colors,
- time = this.promotedContent.time.currentTimeMillis,
+ time = chipContent.time.currentTimeMillis,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
)
@@ -198,8 +197,8 @@
key = this.key,
icon = icon,
colors = colors,
- startTimeMs = this.promotedContent.time.elapsedRealtimeMillis,
- isEventInFuture = this.promotedContent.time.isCountDown,
+ startTimeMs = chipContent.time.elapsedRealtimeMillis,
+ isEventInFuture = chipContent.time.isCountDown,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
)
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 4558017..b5ab092 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
@@ -68,6 +68,7 @@
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
@@ -198,7 +199,7 @@
// TODO(b/377565433): Move into NotificationContentModel during/after
// NotificationRowContentBinderRefactor.
- private PromotedNotificationContentModel mPromotedNotificationContentModel;
+ private PromotedNotificationContentModels mPromotedNotificationContentModels;
/**
* True if both
@@ -1106,9 +1107,9 @@
* Gets the content needed to render this notification as a promoted notification on various
* surfaces (like status bar chips and AOD).
*/
- public PromotedNotificationContentModel getPromotedNotificationContentModel() {
+ public PromotedNotificationContentModels getPromotedNotificationContentModels() {
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- return mPromotedNotificationContentModel;
+ return mPromotedNotificationContentModels;
} else {
Log.wtf(TAG, "getting promoted content without feature flag enabled", new Throwable());
return null;
@@ -1127,10 +1128,10 @@
* Sets the content needed to render this notification as a promoted notification on various
* surfaces (like status bar chips and AOD).
*/
- public void setPromotedNotificationContentModel(
- @Nullable PromotedNotificationContentModel promotedNotificationContentModel) {
+ public void setPromotedNotificationContentModels(
+ @Nullable PromotedNotificationContentModels promotedNotificationContentModels) {
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- this.mPromotedNotificationContentModel = promotedNotificationContentModel;
+ this.mPromotedNotificationContentModels = promotedNotificationContentModels;
} else {
Log.wtf(TAG, "setting promoted content without feature flag enabled", new Throwable());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index a0eab43..26b86f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.systemui.Flags.notificationSkipSilentUpdates
+
import android.app.Notification
import android.app.Notification.GROUP_ALERT_SUMMARY
import android.util.ArrayMap
@@ -465,15 +467,32 @@
}
hunMutator.updateNotification(posted.key, pinnedStatus)
}
- } else {
+ } else { // shouldHeadsUpEver = false
if (posted.isHeadsUpEntry) {
- // We don't want this to be interrupting anymore, let's remove it
- // If the notification is pinned by the user, the only way a user can un-pin
- // it is by tapping the status bar notification chip. Since that's a clear
- // user action, we should remove the HUN immediately instead of waiting for
- // any sort of minimum timeout.
- val shouldRemoveImmediately = posted.isPinnedByUser
- hunMutator.removeNotification(posted.key, shouldRemoveImmediately)
+ if (notificationSkipSilentUpdates()) {
+ if (posted.isPinnedByUser) {
+ // We don't want this to be interrupting anymore, let's remove it
+ // If the notification is pinned by the user, the only way a user
+ // can un-pin it by tapping the status bar notification chip. Since
+ // that's a clear user action, we should remove the HUN immediately
+ // instead of waiting for any sort of minimum timeout.
+ // TODO(b/401068530) Ensure that status bar chip HUNs are not
+ // removed for silent update
+ hunMutator.removeNotification(posted.key,
+ /* releaseImmediately= */ true)
+ } else {
+ // Do NOT remove HUN for non-user update.
+ // Let the HUN show for its remaining duration.
+ }
+ } else {
+ // We don't want this to be interrupting anymore, let's remove it
+ // If the notification is pinned by the user, the only way a user can
+ // un-pin it is by tapping the status bar notification chip. Since
+ // that's a clear user action, we should remove the HUN immediately
+ // instead of waiting for any sort of minimum timeout.
+ val shouldRemoveImmediately = posted.isPinnedByUser
+ hunMutator.removeNotification(posted.key, shouldRemoveImmediately)
+ }
} else {
// Don't let the bind finish
cancelHeadsUpBind(posted.entry)
@@ -573,24 +592,34 @@
isBinding = isBinding,
)
}
- // Handle cancelling heads up here, rather than in the OnBeforeFinalizeFilter, so
- // that
- // work can be done before the ShadeListBuilder is run. This prevents re-entrant
- // behavior between this Coordinator, HeadsUpManager, and VisualStabilityManager.
- if (posted?.shouldHeadsUpEver == false) {
- if (posted.isHeadsUpEntry) {
- // We don't want this to be interrupting anymore, let's remove it
- mHeadsUpManager.removeNotification(
- posted.key,
- /* removeImmediately= */ false,
- "onEntryUpdated",
- )
- } else if (posted.isBinding) {
+ if (notificationSkipSilentUpdates()) {
+ // TODO(b/403703828) Move canceling to OnBeforeFinalizeFilter, since we are not
+ // removing from HeadsUpManager and don't need to deal with re-entrant behavior
+ // between HeadsUpCoordinator, HeadsUpManager, and VisualStabilityManager.
+ if (posted?.shouldHeadsUpEver == false
+ && !posted.isHeadsUpEntry && posted.isBinding) {
// Don't let the bind finish
cancelHeadsUpBind(posted.entry)
}
+ } else {
+ // Handle cancelling heads up here, rather than in the OnBeforeFinalizeFilter,
+ // so that work can be done before the ShadeListBuilder is run. This prevents
+ // re-entrant behavior between this Coordinator, HeadsUpManager, and
+ // VisualStabilityManager.
+ if (posted?.shouldHeadsUpEver == false) {
+ if (posted.isHeadsUpEntry) {
+ // We don't want this to be interrupting anymore, let's remove it
+ mHeadsUpManager.removeNotification(
+ posted.key,
+ /* removeImmediately= */ false,
+ "onEntryUpdated",
+ )
+ } else if (posted.isBinding) {
+ // Don't let the bind finish
+ cancelHeadsUpBind(posted.entry)
+ }
+ }
}
-
// Update last updated time for this entry
setUpdateTime(entry, mSystemClock.currentTimeMillis())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 1e5aa01..6042bff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -83,7 +83,6 @@
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
-import com.android.systemui.statusbar.notification.row.NotificationActionClickManager;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -325,7 +324,7 @@
if (PromotedNotificationContentModel.featureFlagEnabled()) {
return implProvider.get();
} else {
- return (entry, recoveredBuilder, imageModelProvider) -> null;
+ return (entry, recoveredBuilder, redactionType, imageModelProvider) -> null;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 2f9d86b..e7cc342 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -36,6 +36,7 @@
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -141,7 +142,7 @@
private fun NotificationEntry.toModel(): ActiveNotificationModel {
val promotedContent =
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- promotedNotificationContentModel
+ promotedNotificationContentModels
} else {
null
}
@@ -199,7 +200,7 @@
isGroupSummary: Boolean,
bucket: Int,
callType: CallType,
- promotedContent: PromotedNotificationContentModel?,
+ promotedContent: PromotedNotificationContentModels?,
): ActiveNotificationModel {
return individuals[key]?.takeIf {
it.isCurrent(
@@ -281,7 +282,7 @@
isGroupSummary: Boolean,
bucket: Int,
callType: CallType,
- promotedContent: PromotedNotificationContentModel?,
+ promotedContent: PromotedNotificationContentModels?,
): Boolean {
return when {
key != this.key -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index d09546f..3caaf54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -67,6 +67,7 @@
private FooterViewButton mSettingsButton;
private FooterViewButton mHistoryButton;
private boolean mShouldBeHidden;
+ private boolean mIsBlurSupported;
// Footer label
private TextView mSeenNotifsFooterTextView;
@@ -390,15 +391,20 @@
if (!notificationFooterBackgroundTintOptimization()) {
if (notificationShadeBlur()) {
- Color backgroundColor = Color.valueOf(
- SurfaceEffectColors.surfaceEffect1(getContext()));
- scHigh = ColorUtils.setAlphaComponent(backgroundColor.toArgb(), 0xFF);
- // Apply alpha on background drawables.
- int backgroundAlpha = (int) (backgroundColor.alpha() * 0xFF);
- clearAllBg.setAlpha(backgroundAlpha);
- settingsBg.setAlpha(backgroundAlpha);
- if (historyBg != null) {
- historyBg.setAlpha(backgroundAlpha);
+ if (mIsBlurSupported) {
+ Color backgroundColor = Color.valueOf(
+ SurfaceEffectColors.surfaceEffect1(getContext()));
+ scHigh = ColorUtils.setAlphaComponent(backgroundColor.toArgb(), 0xFF);
+ // Apply alpha on background drawables.
+ int backgroundAlpha = (int) (backgroundColor.alpha() * 0xFF);
+ clearAllBg.setAlpha(backgroundAlpha);
+ settingsBg.setAlpha(backgroundAlpha);
+ if (historyBg != null) {
+ historyBg.setAlpha(backgroundAlpha);
+ }
+ } else {
+ scHigh = mContext.getColor(
+ com.android.internal.R.color.materialColorSurfaceContainer);
}
} else {
scHigh = mContext.getColor(
@@ -438,6 +444,16 @@
}
}
+ public void setIsBlurSupported(boolean isBlurSupported) {
+ if (notificationShadeBlur()) {
+ if (mIsBlurSupported == isBlurSupported) {
+ return;
+ }
+ mIsBlurSupported = isBlurSupported;
+ updateColors();
+ }
+ }
+
@Override
@NonNull
public ExpandableViewState createExpandableViewState() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 3383ce9..3213754 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -20,6 +20,7 @@
import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.Flags.notificationShadeBlur
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent
@@ -81,6 +82,14 @@
launch { bindHistoryButton(footer, viewModel, notificationActivityStarter) }
}
launch { bindMessage(footer, viewModel) }
+
+ if (notificationShadeBlur()) {
+ launch {
+ viewModel.isBlurSupported.collect { supported ->
+ footer.setIsBlurSupported(supported)
+ }
+ }
+ }
}
private suspend fun bindClearAllButton(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index c895c419..c1fc72c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -31,6 +31,7 @@
import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
@@ -48,6 +49,7 @@
notificationSettingsInteractor: NotificationSettingsInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
+ windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
) {
/** A message to show instead of the footer buttons. */
val message: FooterMessageViewModel =
@@ -119,6 +121,8 @@
}
}
+ val isBlurSupported = windowRootViewBlurInteractor.isBlurCurrentlySupported
+
private val manageOrHistoryButtonText: Flow<Int> =
notificationSettingsInteractor.isNotificationHistoryEnabled.map { shouldLaunchHistory ->
if (shouldLaunchHistory) R.string.manage_notifications_history_text
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index 27b2788..a8a7e88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -40,6 +40,8 @@
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator.Companion.EXTRA_AUTOMATICALLY_EXTRACTED_SHORT_CRITICAL_TEXT
import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator.Companion.EXTRA_WAS_AUTOMATICALLY_PROMOTED
@@ -48,6 +50,7 @@
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.OldProgress
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.row.shared.ImageModel
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider.ImageSizeClass.MediumSquare
@@ -60,8 +63,9 @@
fun extractContent(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
+ @RedactionType redactionType: Int,
imageModelProvider: ImageModelProvider,
- ): PromotedNotificationContentModel?
+ ): PromotedNotificationContentModels?
}
@SysUISingleton
@@ -76,8 +80,9 @@
override fun extractContent(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
+ @RedactionType redactionType: Int,
imageModelProvider: ImageModelProvider,
- ): PromotedNotificationContentModel? {
+ ): PromotedNotificationContentModels? {
if (!PromotedNotificationContentModel.featureFlagEnabled()) {
logger.logExtractionSkipped(entry, "feature flags disabled")
return null
@@ -95,7 +100,55 @@
return null
}
- val contentBuilder = PromotedNotificationContentModel.Builder(entry.key)
+ val privateVersion =
+ extractPrivateContent(
+ key = entry.key,
+ notification = notification,
+ recoveredBuilder = recoveredBuilder,
+ lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs,
+ imageModelProvider = imageModelProvider,
+ )
+ val publicVersion =
+ if (redactionType == REDACTION_TYPE_NONE) {
+ privateVersion
+ } else {
+ if (notification.publicVersion == null) {
+ privateVersion.toDefaultPublicVersion()
+ } else {
+ // TODO(b/400991304): implement extraction for [Notification.publicVersion]
+ privateVersion.toDefaultPublicVersion()
+ }
+ }
+ return PromotedNotificationContentModels(
+ privateVersion = privateVersion,
+ publicVersion = publicVersion,
+ )
+ .also { logger.logExtractionSucceeded(entry, it) }
+ }
+
+ private fun PromotedNotificationContentModel.toDefaultPublicVersion():
+ PromotedNotificationContentModel =
+ PromotedNotificationContentModel.Builder(key = identity.key).let {
+ it.style = if (style == Style.Ineligible) Style.Ineligible else Style.Base
+ it.smallIcon = smallIcon
+ it.iconLevel = iconLevel
+ it.appName = appName
+ it.time = time
+ it.lastAudiblyAlertedMs = lastAudiblyAlertedMs
+ it.profileBadgeResId = profileBadgeResId
+ it.colors = colors
+ it.build()
+ }
+
+ private fun extractPrivateContent(
+ key: String,
+ notification: Notification,
+ recoveredBuilder: Notification.Builder,
+ lastAudiblyAlertedMs: Long,
+ imageModelProvider: ImageModelProvider,
+ ): PromotedNotificationContentModel {
+
+ val contentBuilder = PromotedNotificationContentModel.Builder(key)
// TODO: Pitch a fit if style is unsupported or mandatory fields are missing once
// FLAG_PROMOTED_ONGOING is set reliably and we're not testing status bar chips.
@@ -108,7 +161,7 @@
contentBuilder.subText = notification.subText()
contentBuilder.time = notification.extractWhen()
contentBuilder.shortCriticalText = notification.shortCriticalText()
- contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs
+ contentBuilder.lastAudiblyAlertedMs = lastAudiblyAlertedMs
contentBuilder.profileBadgeResId = null // TODO
contentBuilder.title = notification.title(recoveredBuilder.style)
contentBuilder.text = notification.text(recoveredBuilder.style)
@@ -124,7 +177,7 @@
recoveredBuilder.extractStyleContent(notification, contentBuilder, imageModelProvider)
- return contentBuilder.build().also { logger.logExtractionSucceeded(entry, it) }
+ return contentBuilder.build()
}
private fun Notification.smallIconModel(imageModelProvider: ImageModelProvider): ImageModel? =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
index 5f9678a..6b6203d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
@@ -23,7 +23,7 @@
import com.android.systemui.log.core.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import javax.inject.Inject
@OptIn(ExperimentalStdlibApi::class)
@@ -56,7 +56,7 @@
fun logExtractionSucceeded(
entry: NotificationEntry,
- content: PromotedNotificationContentModel,
+ content: PromotedNotificationContentModels,
) {
buffer.log(
EXTRACTION_TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
index ec4ee45..d9778bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@SysUISingleton
@@ -34,6 +35,16 @@
/** The content to show as the promoted notification on AOD */
val content: Flow<PromotedNotificationContentModel?> =
promotedNotificationsInteractor.aodPromotedNotification
+ .map {
+ // TODO(b/400991304): show the private version when unlocked
+ it?.publicVersion
+ }
+ .distinctUntilNewInstance()
val isPresent: Flow<Boolean> = content.map { it != null }.dumpWhileCollecting("isPresent")
+
+ /**
+ * Returns flow where all subsequent repetitions of the same object instance are filtered out.
+ */
+ private fun <T> Flow<T>.distinctUntilNewInstance() = distinctUntilChanged { a, b -> a === b }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
index 96d41f1..08e7528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
@@ -25,8 +25,8 @@
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style.Ineligible
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
@@ -201,13 +201,13 @@
* The top promoted notification represented by a chip, with the order determined by the order
* of the chips, not the notifications.
*/
- private val topPromotedChipNotification: Flow<PromotedNotificationContentModel?> =
+ private val topPromotedChipNotification: Flow<PromotedNotificationContentModels?> =
orderedChipNotifications
.map { list -> list.firstNotNullOfOrNull { it.promotedContent } }
.distinctUntilNewInstance()
/** This is the AOD promoted notification, which should avoid regular changing. */
- val aodPromotedNotification: Flow<PromotedNotificationContentModel?> =
+ val aodPromotedNotification: Flow<PromotedNotificationContentModels?> =
combine(
topPromotedChipNotification,
activeNotificationsInteractor.topLevelRepresentativeNotifications,
@@ -229,13 +229,13 @@
.flowOn(backgroundDispatcher)
private fun List<ActiveNotificationModel>.firstAodEligibleOrNull():
- PromotedNotificationContentModel? {
+ PromotedNotificationContentModels? {
return this.firstNotNullOfOrNull { it.promotedContent?.takeIfAodEligible() }
}
- private fun PromotedNotificationContentModel.takeIfAodEligible():
- PromotedNotificationContentModel? {
- return this.takeUnless { it.style == Ineligible }
+ private fun PromotedNotificationContentModels.takeIfAodEligible():
+ PromotedNotificationContentModels? {
+ return this.takeUnless { it.privateVersion.style == Ineligible }
}
/**
@@ -251,7 +251,7 @@
*/
private data class NotifAndPromotedContent(
val key: String,
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
) {
/**
* Define the equals of this object to only check the reference equality of the promoted
@@ -269,7 +269,7 @@
/** Define the hashCode to be very quick, even if it increases collisions. */
override fun hashCode(): Int {
var result = key.hashCode()
- result = 31 * result + (promotedContent?.identity?.hashCode() ?: 0)
+ result = 31 * result + (promotedContent?.key?.hashCode() ?: 0)
return result
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index 57b0720..ffacf62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -29,6 +29,31 @@
import com.android.systemui.statusbar.notification.row.LazyImage
import com.android.systemui.statusbar.notification.row.shared.ImageModel
+data class PromotedNotificationContentModels(
+ /** The potentially redacted version of the content that will be exposed to the public */
+ val publicVersion: PromotedNotificationContentModel,
+ /** The unredacted version of the content that will be kept private */
+ val privateVersion: PromotedNotificationContentModel,
+) {
+ val key: String
+ get() = privateVersion.identity.key
+
+ init {
+ check(publicVersion.identity.key == privateVersion.identity.key) {
+ "public and private models must have the same key"
+ }
+ }
+
+ fun toRedactedString(): String {
+ val publicVersionString =
+ "==privateVersion".takeIf { privateVersion === publicVersion }
+ ?: publicVersion.toRedactedString()
+ return ("PromotedNotificationContentModels(" +
+ "privateVersion=${privateVersion.toRedactedString()}, " +
+ "publicVersion=$publicVersionString)")
+ }
+}
+
/**
* The content needed to render a promoted notification to surfaces besides the notification stack,
* like the skeleton view on AOD or the status bar chip.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index d97e25f..57ceafc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -60,6 +60,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider;
@@ -1003,7 +1004,7 @@
row.mImageModelIndex = result.mRowImageInflater.getNewImageIndex();
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- entry.setPromotedNotificationContentModel(result.mPromotedContent);
+ entry.setPromotedNotificationContentModels(result.mPromotedContent);
}
boolean setRepliesAndActions = true;
@@ -1387,9 +1388,9 @@
mLogger.logAsyncTaskProgress(logKey, "extracting promoted notification content");
final ImageModelProvider imageModelProvider =
result.mRowImageInflater.useForContentModel();
- final PromotedNotificationContentModel promotedContent =
+ final PromotedNotificationContentModels promotedContent =
mPromotedNotificationContentExtractor.extractContent(mEntry,
- recoveredBuilder, imageModelProvider);
+ recoveredBuilder, mBindParams.redactionType, imageModelProvider);
mLogger.logAsyncTaskProgress(logKey, "extracted promoted notification content: "
+ promotedContent);
@@ -1503,7 +1504,7 @@
static class InflationProgress {
RowImageInflater mRowImageInflater;
- PromotedNotificationContentModel mPromotedContent;
+ PromotedNotificationContentModels mPromotedContent;
private RemoteViews newContentView;
private RemoteViews newHeadsUpView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index ae52db8..4f1b905 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -53,6 +53,7 @@
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -595,7 +596,7 @@
val rowImageInflater: RowImageInflater,
val remoteViews: NewRemoteViews,
val contentModel: NotificationContentModel,
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
) {
var inflatedContentView: View? = null
@@ -700,7 +701,12 @@
)
val imageModelProvider = rowImageInflater.useForContentModel()
promotedNotificationContentExtractor
- .extractContent(entry, builder, imageModelProvider)
+ .extractContent(
+ entry,
+ builder,
+ bindParams.redactionType,
+ imageModelProvider,
+ )
.also {
logger.logAsyncTaskProgress(
entry.logKey,
@@ -1519,7 +1525,7 @@
entry.setContentModel(result.contentModel)
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- entry.promotedNotificationContentModel = result.promotedContent
+ entry.promotedNotificationContentModels = result.promotedContent
}
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index 487cbce..9652738 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -20,6 +20,7 @@
import android.util.Log
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.stack.PriorityBucket
/**
@@ -88,7 +89,7 @@
* The content needed to render this as a promoted notification on various surfaces, or null if
* this notification cannot be rendered as a promoted notification.
*/
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
) : ActiveNotificationEntryModel() {
init {
if (!PromotedNotificationContentModel.featureFlagEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 61b7d80..b7eada1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -38,7 +38,7 @@
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
@@ -347,7 +347,7 @@
* If the call notification also meets promoted notification criteria, this field is filled
* in with the content related to promotion. Otherwise null.
*/
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
val isOngoing: Boolean,
/** True if the user has swiped away the status bar while in this phone call. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index 322dfff..9546d37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -18,7 +18,7 @@
import android.app.PendingIntent
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
/** Represents the state of any ongoing calls. */
sealed interface OngoingCallModel {
@@ -47,7 +47,7 @@
val intent: PendingIntent?,
val notificationKey: String,
val appName: String,
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
val isAppVisible: Boolean,
) : OngoingCallModel
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 206654a..9b314f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -1500,6 +1500,7 @@
mKosmos.getKeyguardInteractor(),
mKeyguardTransitionBootInteractor,
mKosmos::getCommunalSceneInteractor,
+ mKosmos::getCommunalSettingsInteractor,
mock(WindowManagerOcclusionManager.class));
mViewMediator.mUserChangedCallback = mUserTrackerCallback;
mViewMediator.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
index 86f7966..d6b778f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
@@ -35,8 +35,14 @@
import com.android.systemui.animation.activityTransitionAnimator
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.concurrency.fakeExecutor
@@ -81,8 +87,11 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
/** Kotlin version of KeyguardViewMediatorTest to allow for coroutine testing. */
@SmallTest
@@ -152,6 +161,7 @@
keyguardInteractor,
keyguardTransitionBootInteractor,
{ communalSceneInteractor },
+ { communalSettingsInteractor },
mock<WindowManagerOcclusionManager>(),
)
}
@@ -164,6 +174,10 @@
@Test
fun doKeyguardTimeout_changesCommunalScene() =
kosmos.runTest {
+ // Hub is enabled and hub condition is active.
+ setCommunalV2Enabled(true)
+ enableHubOnCharging()
+
// doKeyguardTimeout message received.
val timeoutOptions = Bundle()
timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
@@ -174,4 +188,56 @@
assertThat(communalSceneRepository.currentScene.value)
.isEqualTo(CommunalScenes.Communal)
}
+
+ @Test
+ fun doKeyguardTimeout_communalNotAvailable_sleeps() =
+ kosmos.runTest {
+ // Hub disabled.
+ setCommunalV2Enabled(false)
+
+ // doKeyguardTimeout message received.
+ val timeoutOptions = Bundle()
+ timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
+ underTest.doKeyguardTimeout(timeoutOptions)
+ testableLooper.processAllMessages()
+
+ // Sleep is requested.
+ verify(powerManager)
+ .goToSleep(anyOrNull(), eq(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON), eq(0))
+
+ // Hub scene is not changed.
+ assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
+ }
+
+ @Test
+ fun doKeyguardTimeout_hubConditionNotActive_sleeps() =
+ kosmos.runTest {
+ // Communal enabled, but hub condition set to never.
+ setCommunalV2Enabled(true)
+ disableHubShowingAutomatically()
+
+ // doKeyguardTimeout message received.
+ val timeoutOptions = Bundle()
+ timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
+ underTest.doKeyguardTimeout(timeoutOptions)
+ testableLooper.processAllMessages()
+
+ // Sleep is requested.
+ verify(powerManager)
+ .goToSleep(anyOrNull(), eq(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON), eq(0))
+
+ // Hub scene is not changed.
+ assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
+ }
+
+ private fun Kosmos.enableHubOnCharging() {
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
+ batteryRepository.fake.setDevicePluggedIn(true)
+ }
+
+ private fun Kosmos.disableHubShowingAutomatically() {
+ communalSettingsInteractor.setSuppressionReasons(
+ listOf(SuppressionReason.ReasonUnknown(FEATURE_AUTO_OPEN))
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
index 997cf41..f4d0c26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
@@ -18,9 +18,11 @@
import android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_RDM_V2
import android.hardware.display.rearDisplay
+import android.os.fakeExecutorHandler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display
+import android.view.accessibility.accessibilityManager
import androidx.test.filters.SmallTest
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
@@ -62,6 +64,8 @@
kosmos.rearDisplayInnerDialogDelegateFactory,
kosmos.testScope,
kosmos.keyguardUpdateMonitor,
+ kosmos.accessibilityManager,
+ kosmos.fakeExecutorHandler,
)
@Before
@@ -69,7 +73,7 @@
whenever(kosmos.rearDisplay.flags).thenReturn(Display.FLAG_REAR)
whenever(kosmos.rearDisplay.displayAdjustments)
.thenReturn(mContext.display.displayAdjustments)
- whenever(kosmos.rearDisplayInnerDialogDelegateFactory.create(any(), any()))
+ whenever(kosmos.rearDisplayInnerDialogDelegateFactory.create(any(), any(), any()))
.thenReturn(mockDelegate)
whenever(mockDelegate.createDialog()).thenReturn(mockDialog)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt
index fc76616..477e5ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt
@@ -17,7 +17,10 @@
package com.android.systemui.reardisplay
import android.testing.TestableLooper
+import android.view.View
+import android.widget.Button
import android.widget.SeekBar
+import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.msdl.msdlPlayer
@@ -28,6 +31,7 @@
import com.android.systemui.statusbar.phone.systemUIDialogDotFactory
import com.android.systemui.testKosmos
import com.android.systemui.util.time.systemClock
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Test
@@ -49,6 +53,7 @@
RearDisplayInnerDialogDelegate(
kosmos.systemUIDialogDotFactory,
mContext,
+ false /* touchExplorationEnabled */,
kosmos.vibratorHelper,
kosmos.msdlPlayer,
kosmos.systemClock,
@@ -68,6 +73,7 @@
RearDisplayInnerDialogDelegate(
kosmos.systemUIDialogDotFactory,
mContext,
+ false /* touchExplorationEnabled */,
kosmos.vibratorHelper,
kosmos.msdlPlayer,
kosmos.systemClock,
@@ -78,6 +84,9 @@
.apply {
show()
val seekbar = findViewById<SeekBar>(R.id.seekbar)
+ assertThat(seekbar.visibility).isEqualTo(View.VISIBLE)
+ assertThat(findViewById<TextView>(R.id.seekbar_instructions).visibility)
+ .isEqualTo(View.VISIBLE)
seekbar.progress = 50
seekbar.progress = 100
verify(mockCallback).run()
@@ -90,6 +99,7 @@
RearDisplayInnerDialogDelegate(
kosmos.systemUIDialogDotFactory,
mContext,
+ false /* touchExplorationEnabled */,
kosmos.vibratorHelper,
kosmos.msdlPlayer,
kosmos.systemClock,
@@ -118,4 +128,33 @@
// Progress is reset
verify(mockSeekbar).setProgress(eq(0))
}
+
+ @Test
+ fun testTouchExplorationEnabled() {
+ val mockCallback = mock<Runnable>()
+
+ RearDisplayInnerDialogDelegate(
+ kosmos.systemUIDialogDotFactory,
+ mContext,
+ true /* touchExplorationEnabled */,
+ kosmos.vibratorHelper,
+ kosmos.msdlPlayer,
+ kosmos.systemClock,
+ ) {
+ mockCallback.run()
+ }
+ .createDialog()
+ .apply {
+ show()
+ assertThat(findViewById<SeekBar>(R.id.seekbar).visibility).isEqualTo(View.GONE)
+ assertThat(findViewById<TextView>(R.id.seekbar_instructions).visibility)
+ .isEqualTo(View.GONE)
+
+ val cancelButton = findViewById<Button>(R.id.cancel_button)
+ assertThat(cancelButton.visibility).isEqualTo(View.VISIBLE)
+
+ cancelButton.performClick()
+ verify(mockCallback).run()
+ }
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 846db63..2facc1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -283,6 +283,9 @@
}
public FakeBroadcastDispatcher getFakeBroadcastDispatcher() {
+ if (mSysuiDependency == null) {
+ return null;
+ }
return mSysuiDependency.getFakeBroadcastDispatcher();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 165f85d..4af4e80 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -19,7 +19,7 @@
import android.app.PendingIntent
import android.graphics.drawable.Icon
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN
@@ -49,7 +49,7 @@
contentIntent: PendingIntent? = null,
bucket: Int = BUCKET_UNKNOWN,
callType: CallType = CallType.None,
- promotedContent: PromotedNotificationContentModel? = null,
+ promotedContent: PromotedNotificationContentModels? = null,
) =
ActiveNotificationModel(
key = key,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
index 99323db..ebe20af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
val Kosmos.footerViewModel by Fixture {
FooterViewModel(
@@ -29,6 +30,7 @@
notificationSettingsInteractor = notificationSettingsInteractor,
seenNotificationsInteractor = seenNotificationsInteractor,
shadeInteractor = shadeInteractor,
+ windowRootViewBlurInteractor = windowRootViewBlurInteractor,
)
}
val Kosmos.footerViewModelFactory: FooterViewModel.Factory by Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
index 8fdf5db..aaa86aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
@@ -17,21 +17,23 @@
package com.android.systemui.statusbar.notification.promoted
import android.app.Notification
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType
import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider
import org.junit.Assert
class FakePromotedNotificationContentExtractor : PromotedNotificationContentExtractor {
@JvmField
- val contentForEntry = mutableMapOf<NotificationEntry, PromotedNotificationContentModel?>()
+ val contentForEntry = mutableMapOf<NotificationEntry, PromotedNotificationContentModels?>()
@JvmField val extractCalls = mutableListOf<Pair<NotificationEntry, Notification.Builder>>()
override fun extractContent(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
+ @RedactionType redactionType: Int,
imageModelProvider: ImageModelProvider,
- ): PromotedNotificationContentModel? {
+ ): PromotedNotificationContentModels? {
extractCalls.add(entry to recoveredBuilder)
if (contentForEntry.isEmpty()) {
@@ -44,7 +46,7 @@
}
}
- fun resetForEntry(entry: NotificationEntry, content: PromotedNotificationContentModel?) {
+ fun resetForEntry(entry: NotificationEntry, content: PromotedNotificationContentModels?) {
contentForEntry.clear()
contentForEntry[entry] = content
extractCalls.clear()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
index 2b3158d..c4542c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
@@ -19,6 +19,7 @@
import android.app.Notification
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.RowImageInflater
import com.android.systemui.statusbar.notification.row.shared.skeletonImageTransform
@@ -39,9 +40,10 @@
promotedNotificationContentExtractor.extractContent(
entry,
Notification.Builder.recoverBuilder(applicationContext, entry.sbn.notification),
+ REDACTION_TYPE_NONE,
RowImageInflater.newInstance(previousIndex = null, reinflating = false)
.useForContentModel(),
)
- entry.promotedNotificationContentModel =
+ entry.promotedNotificationContentModels =
requireNotNull(extractedContent) { "extractContent returned null" }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentBuilder.kt
new file mode 100644
index 0000000..6916d560
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentBuilder.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.shared.model
+
+class PromotedNotificationContentBuilder(val key: String) {
+ private val sharedBuilder = PromotedNotificationContentModel.Builder(key)
+
+ fun applyToShared(
+ block: PromotedNotificationContentModel.Builder.() -> Unit
+ ): PromotedNotificationContentBuilder {
+ sharedBuilder.apply(block)
+ return this
+ }
+
+ fun build(): PromotedNotificationContentModels {
+ val sharedModel = sharedBuilder.build()
+ return PromotedNotificationContentModels(sharedModel, sharedModel)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
index 3e96fd7..e5e1a83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
@@ -26,7 +26,7 @@
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.addNotif
import com.android.systemui.statusbar.notification.data.repository.removeNotif
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -39,7 +39,7 @@
intent: PendingIntent? = null,
notificationKey: String = "test",
appName: String = "",
- promotedContent: PromotedNotificationContentModel? = null,
+ promotedContent: PromotedNotificationContentModels? = null,
isAppVisible: Boolean = false,
) =
OngoingCallModel.InCall(
@@ -77,7 +77,7 @@
key: String = "notif",
startTimeMs: Long = 1000L,
statusBarChipIconView: StatusBarIconView? = createStatusBarIconViewOrNull(),
- promotedContent: PromotedNotificationContentModel? = null,
+ promotedContent: PromotedNotificationContentModels? = null,
contentIntent: PendingIntent? = null,
uid: Int = DEFAULT_UID,
appName: String = "Fake name",
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 0b9c45d..60343e9 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -152,9 +152,20 @@
if (direction == AutoclickScrollPanel.DIRECTION_EXIT) {
return;
}
- // For direction buttons, perform scroll action immediately.
- if (hovered && direction != AutoclickScrollPanel.DIRECTION_NONE) {
- handleScroll(direction);
+
+ // Handle all non-exit buttons when hovered.
+ if (hovered) {
+ // Clear the indicator.
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.cancel();
+ if (mAutoclickIndicatorView != null) {
+ mAutoclickIndicatorView.clearIndicator();
+ }
+ }
+ // Perform scroll action.
+ if (direction != DIRECTION_NONE) {
+ handleScroll(direction);
+ }
}
}
};
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6b3661a..a8bb523 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -186,6 +186,7 @@
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.media.audiopolicy.IAudioPolicyCallback;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.SafeCloseable;
import android.media.projection.IMediaProjection;
@@ -1388,6 +1389,7 @@
mUseVolumeGroupAliases = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
+ mAudioVolumeChangeHandler = new AudioVolumeChangeHandler(mAudioSystem);
// Initialize volume
// Priority 1 - Android Property
// Priority 2 - Audio Policy Service
@@ -4452,6 +4454,27 @@
}
}
+ //================================
+ // Audio Volume Change Dispatcher
+ //================================
+ private final AudioVolumeChangeHandler mAudioVolumeChangeHandler;
+
+ /** @see AudioManager#registerVolumeGroupCallback(executor, callback) */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public void registerAudioVolumeCallback(IAudioVolumeChangeDispatcher callback) {
+ super.registerAudioVolumeCallback_enforcePermission();
+ mAudioVolumeChangeHandler.registerListener(callback);
+ }
+
+ /** @see AudioManager#unregisterVolumeGroupCallback(callback) */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public void unregisterAudioVolumeCallback(IAudioVolumeChangeDispatcher callback) {
+ super.unregisterAudioVolumeCallback_enforcePermission();
+ mAudioVolumeChangeHandler.unregisterListener(callback);
+ }
+
@Override
@android.annotation.EnforcePermission(anyOf = {
MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING })
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index a6267c1..ced5fae 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -23,6 +23,7 @@
import android.media.AudioMixerAttributes;
import android.media.AudioSystem;
import android.media.IDevicesForAttributesCallback;
+import android.media.INativeAudioVolumeGroupCallback;
import android.media.ISoundDose;
import android.media.ISoundDoseCallback;
import android.media.audiopolicy.AudioMix;
@@ -758,6 +759,29 @@
}
/**
+ * Same as {@link AudioSystem#registerAudioVolumeGroupCallback(INativeAudioVolumeGroupCallback)}
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public int registerAudioVolumeGroupCallback(INativeAudioVolumeGroupCallback callback) {
+ return AudioSystem.registerAudioVolumeGroupCallback(callback);
+ }
+
+ /**
+ * Same as
+ * {@link AudioSystem#unregisterAudioVolumeGroupCallback(INativeAudioVolumeGroupCallback)}.
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public int unregisterAudioVolumeGroupCallback(INativeAudioVolumeGroupCallback callback) {
+ return AudioSystem.unregisterAudioVolumeGroupCallback(callback);
+ }
+
+ /**
* Part of AudioService dump
* @param pw
*/
diff --git a/services/core/java/com/android/server/audio/AudioVolumeChangeHandler.java b/services/core/java/com/android/server/audio/AudioVolumeChangeHandler.java
new file mode 100644
index 0000000..2bb4301
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioVolumeChangeHandler.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.INativeAudioVolumeGroupCallback;
+import android.media.audio.common.AudioVolumeGroupChangeEvent;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+/**
+ * The AudioVolumeChangeHandler handles AudioVolume callbacks invoked by native
+ * {@link INativeAudioVolumeGroupCallback} callback.
+ */
+/* private package */ class AudioVolumeChangeHandler {
+ private static final String TAG = "AudioVolumeChangeHandler";
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final RemoteCallbackList<IAudioVolumeChangeDispatcher> mListeners =
+ new RemoteCallbackList<>();
+ private final @NonNull AudioSystemAdapter mAudioSystem;
+ private @Nullable AudioVolumeGroupCallback mAudioVolumeGroupCallback;
+
+ AudioVolumeChangeHandler(@NonNull AudioSystemAdapter asa) {
+ mAudioSystem = asa;
+ }
+
+ @GuardedBy("mLock")
+ private void lazyInitLocked() {
+ mAudioVolumeGroupCallback = new AudioVolumeGroupCallback();
+ mAudioSystem.registerAudioVolumeGroupCallback(mAudioVolumeGroupCallback);
+ }
+
+ private void sendAudioVolumeGroupChangedToClients(int groupId, int index) {
+ RemoteCallbackList<IAudioVolumeChangeDispatcher> listeners;
+ int nbDispatchers;
+ synchronized (mLock) {
+ listeners = mListeners;
+ nbDispatchers = mListeners.beginBroadcast();
+ }
+ for (int i = 0; i < nbDispatchers; i++) {
+ try {
+ listeners.getBroadcastItem(i).onAudioVolumeGroupChanged(groupId, index);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to broadcast Volume Changed event");
+ }
+ }
+ synchronized (mLock) {
+ mListeners.finishBroadcast();
+ }
+ }
+
+ /**
+ * @param cb the {@link IAudioVolumeChangeDispatcher} to register
+ */
+ public void registerListener(@NonNull IAudioVolumeChangeDispatcher cb) {
+ Preconditions.checkNotNull(cb, "Volume group callback must not be null");
+ synchronized (mLock) {
+ if (mAudioVolumeGroupCallback == null) {
+ lazyInitLocked();
+ }
+ mListeners.register(cb);
+ }
+ }
+
+ /**
+ * @param cb the {@link IAudioVolumeChangeDispatcher} to unregister
+ */
+ public void unregisterListener(@NonNull IAudioVolumeChangeDispatcher cb) {
+ Preconditions.checkNotNull(cb, "Volume group callback must not be null");
+ synchronized (mLock) {
+ mListeners.unregister(cb);
+ }
+ }
+
+ private final class AudioVolumeGroupCallback extends INativeAudioVolumeGroupCallback.Stub {
+ public void onAudioVolumeGroupChanged(AudioVolumeGroupChangeEvent volumeEvent) {
+ Slog.v(TAG, "onAudioVolumeGroupChanged volumeEvent=" + volumeEvent);
+ sendAudioVolumeGroupChangedToClients(volumeEvent.groupId, volumeEvent.flags);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 41b0b4d..a2d0654 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -252,17 +252,22 @@
static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_EARC =
new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_HDMI_EARC, "");
+ static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_LINE_DIGITAL =
+ new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_LINE_DIGITAL, "");
// Audio output devices used for absolute volume behavior
private static final List<AudioDeviceAttributes> AVB_AUDIO_OUTPUT_DEVICES =
List.of(AUDIO_OUTPUT_DEVICE_HDMI,
AUDIO_OUTPUT_DEVICE_HDMI_ARC,
- AUDIO_OUTPUT_DEVICE_HDMI_EARC);
+ AUDIO_OUTPUT_DEVICE_HDMI_EARC,
+ AUDIO_OUTPUT_DEVICE_LINE_DIGITAL);
// Audio output devices used for absolute volume behavior on TV panels
private static final List<AudioDeviceAttributes> TV_AVB_AUDIO_OUTPUT_DEVICES =
List.of(AUDIO_OUTPUT_DEVICE_HDMI_ARC,
- AUDIO_OUTPUT_DEVICE_HDMI_EARC);
+ AUDIO_OUTPUT_DEVICE_HDMI_EARC,
+ AUDIO_OUTPUT_DEVICE_LINE_DIGITAL);
// Audio output devices used for absolute volume behavior on Playback devices
private static final List<AudioDeviceAttributes> PLAYBACK_AVB_AUDIO_OUTPUT_DEVICES =
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index ccb9e3e..bbf7732 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -33,6 +33,7 @@
import android.hardware.contexthub.Reason;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubTransactionCallback;
+import android.hardware.location.NanoAppState;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
@@ -48,6 +49,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -182,8 +184,11 @@
long expiryMillis = RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT.toMillis();
if (nowMillis >= nextEntry.getValue() + expiryMillis) {
iterator.remove();
+ } else {
+ // Safe to break since LinkedHashMap is insertion-ordered, so the next entry
+ // will have a later timestamp and will not be expired.
+ break;
}
- break;
}
return mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber());
@@ -276,6 +281,7 @@
int sessionId = mEndpointManager.reserveSessionId();
EndpointInfo halEndpointInfo = ContextHubServiceUtil.convertHalEndpointInfo(destination);
+ Log.d(TAG, "openSession: sessionId=" + sessionId);
synchronized (mOpenSessionLock) {
try {
@@ -301,6 +307,7 @@
throw new IllegalArgumentException(
"Unknown session ID in closeSession: id=" + sessionId);
}
+ Log.d(TAG, "closeSession: sessionId=" + sessionId + " reason=" + reason);
mEndpointManager.halCloseEndpointSession(
sessionId, ContextHubServiceUtil.toHalReason(reason));
}
@@ -373,12 +380,43 @@
try {
mHubInterface.sendMessageToEndpoint(sessionId, halMessage);
} catch (RemoteException e) {
- Log.w(TAG, "Exception while sending message on session " + sessionId, e);
+ Log.e(
+ TAG,
+ "Exception while sending message on session "
+ + sessionId
+ + ", closing session",
+ e);
+ notifySessionClosedToBoth(sessionId, Reason.UNSPECIFIED);
}
} else {
+ IContextHubTransactionCallback wrappedCallback =
+ new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> appStates)
+ throws RemoteException {
+ Log.w(TAG, "Unexpected onQueryResponse callback");
+ }
+
+ @Override
+ public void onTransactionComplete(int result) throws RemoteException {
+ callback.onTransactionComplete(result);
+ if (result != ContextHubTransaction.RESULT_SUCCESS) {
+ Log.e(
+ TAG,
+ "Failed to send reliable message "
+ + message
+ + ", closing session");
+ notifySessionClosedToBoth(sessionId, Reason.UNSPECIFIED);
+ }
+ }
+ };
ContextHubServiceTransaction transaction =
mTransactionManager.createSessionMessageTransaction(
- mHubInterface, sessionId, halMessage, mPackageName, callback);
+ mHubInterface,
+ sessionId,
+ halMessage,
+ mPackageName,
+ wrappedCallback);
try {
mTransactionManager.addTransaction(transaction);
info.setReliableMessagePending(transaction.getMessageSequenceNumber());
@@ -445,10 +483,7 @@
int id = mSessionMap.keyAt(i);
HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo();
if (!hasEndpointPermissions(target)) {
- mEndpointManager.halCloseEndpointSessionNoThrow(
- id, Reason.PERMISSION_DENIED);
- onCloseEndpointSession(id, Reason.PERMISSION_DENIED);
- // Resource cleanup is done in onCloseEndpointSession
+ notifySessionClosedToBoth(id, Reason.PERMISSION_DENIED);
}
}
}
@@ -532,8 +567,17 @@
/* package */ void onMessageReceived(int sessionId, HubMessage message) {
byte errorCode = onMessageReceivedInternal(sessionId, message);
- if (errorCode != ErrorCode.OK && message.isResponseRequired()) {
- sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), errorCode);
+ if (errorCode != ErrorCode.OK) {
+ Log.e(TAG, "Failed to send message to endpoint: " + message + ", closing session");
+ if (message.isResponseRequired()) {
+ sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), errorCode);
+ } else {
+ notifySessionClosedToBoth(
+ sessionId,
+ (errorCode == ErrorCode.PERMISSION_DENIED)
+ ? Reason.PERMISSION_DENIED
+ : Reason.UNSPECIFIED);
+ }
}
}
@@ -800,4 +844,16 @@
+ "-0x"
+ Long.toHexString(endpoint.getIdentifier().getEndpoint()));
}
+
+ /**
+ * Notifies to both the HAL and the app that a session has been closed.
+ *
+ * @param sessionId The ID of the session that was closed
+ * @param halReason The HAL reason for closing the session
+ */
+ private void notifySessionClosedToBoth(int sessionId, byte halReason) {
+ Log.d(TAG, "notifySessionClosedToBoth: sessionId=" + sessionId + ", reason=" + halReason);
+ mEndpointManager.halCloseEndpointSessionNoThrow(sessionId, halReason);
+ onCloseEndpointSession(sessionId, halReason);
+ }
}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
index cf8b703..05aac55 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
@@ -18,13 +18,20 @@
import android.content.ContentValues;
import android.database.Cursor;
+import android.hardware.tv.mediaquality.ColorRange;
+import android.hardware.tv.mediaquality.ColorSpace;
+import android.hardware.tv.mediaquality.ColorTemperature;
import android.hardware.tv.mediaquality.DolbyAudioProcessing;
import android.hardware.tv.mediaquality.DtsVirtualX;
+import android.hardware.tv.mediaquality.Gamma;
import android.hardware.tv.mediaquality.ParameterDefaultValue;
import android.hardware.tv.mediaquality.ParameterName;
import android.hardware.tv.mediaquality.ParameterRange;
import android.hardware.tv.mediaquality.PictureParameter;
+import android.hardware.tv.mediaquality.PictureQualityEventType;
+import android.hardware.tv.mediaquality.QualityLevel;
import android.hardware.tv.mediaquality.SoundParameter;
+import android.media.quality.MediaQualityContract;
import android.media.quality.MediaQualityContract.BaseParameters;
import android.media.quality.MediaQualityContract.PictureQuality;
import android.media.quality.MediaQualityContract.SoundQuality;
@@ -371,7 +378,7 @@
}
List<PictureParameter> pictureParams = new ArrayList<>();
if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) {
- pictureParams.add(PictureParameter.brightness(params.getLong(
+ pictureParams.add(PictureParameter.brightness((float) params.getDouble(
PictureQuality.PARAMETER_BRIGHTNESS)));
params.remove(PictureQuality.PARAMETER_BRIGHTNESS);
}
@@ -441,28 +448,46 @@
params.remove(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN);
}
if (params.containsKey(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
- pictureParams.add(PictureParameter.noiseReduction(
- (byte) params.getInt(PictureQuality.PARAMETER_NOISE_REDUCTION)));
+ String noiseReductionString = params.getString(
+ PictureQuality.PARAMETER_NOISE_REDUCTION);
+ if (noiseReductionString != null) {
+ byte noiseReductionByte = mapQualityLevel(noiseReductionString);
+ pictureParams.add(PictureParameter.noiseReduction(noiseReductionByte));
+ }
params.remove(PictureQuality.PARAMETER_NOISE_REDUCTION);
}
if (params.containsKey(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
- pictureParams.add(PictureParameter.mpegNoiseReduction(
- (byte) params.getInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)));
+ String mpegNoiseReductionString = params.getString(
+ PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION);
+ if (mpegNoiseReductionString != null) {
+ byte mpegNoiseReductionByte = mapQualityLevel(mpegNoiseReductionString);
+ pictureParams.add(PictureParameter.mpegNoiseReduction(mpegNoiseReductionByte));
+ }
params.remove(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION);
}
if (params.containsKey(PictureQuality.PARAMETER_FLESH_TONE)) {
- pictureParams.add(PictureParameter.fleshTone(
- (byte) params.getInt(PictureQuality.PARAMETER_FLESH_TONE)));
+ String fleshToneString = params.getString(PictureQuality.PARAMETER_FLESH_TONE);
+ if (fleshToneString != null) {
+ byte fleshToneByte = mapQualityLevel(fleshToneString);
+ pictureParams.add(PictureParameter.fleshTone(fleshToneByte));
+ }
params.remove(PictureQuality.PARAMETER_FLESH_TONE);
}
if (params.containsKey(PictureQuality.PARAMETER_DECONTOUR)) {
- pictureParams.add(PictureParameter.deContour(
- (byte) params.getInt(PictureQuality.PARAMETER_DECONTOUR)));
+ String decontourString = params.getString(PictureQuality.PARAMETER_DECONTOUR);
+ if (decontourString != null) {
+ byte decontourByte = mapQualityLevel(decontourString);
+ pictureParams.add(PictureParameter.deContour(decontourByte));
+ }
params.remove(PictureQuality.PARAMETER_DECONTOUR);
}
if (params.containsKey(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
- pictureParams.add(PictureParameter.dynamicLumaControl(
- (byte) params.getInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)));
+ String dynamicLunaControlString = params.getString(
+ PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL);
+ if (dynamicLunaControlString != null) {
+ byte dynamicLunaControlByte = mapQualityLevel(dynamicLunaControlString);
+ pictureParams.add(PictureParameter.dynamicLumaControl(dynamicLunaControlByte));
+ }
params.remove(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL);
}
if (params.containsKey(PictureQuality.PARAMETER_FILM_MODE)) {
@@ -481,9 +506,48 @@
params.remove(PictureQuality.PARAMETER_COLOR_TUNE);
}
if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
- pictureParams.add(PictureParameter.colorTemperature(
- (byte) params.getInt(
- PictureQuality.PARAMETER_COLOR_TEMPERATURE)));
+ String colorTemperatureString = params.getString(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE);
+ if (colorTemperatureString != null) {
+ byte colorTemperatureByte;
+ switch (colorTemperatureString) {
+ case MediaQualityContract.COLOR_TEMP_USER:
+ colorTemperatureByte = ColorTemperature.USER;
+ break;
+ case MediaQualityContract.COLOR_TEMP_COOL:
+ colorTemperatureByte = ColorTemperature.COOL;
+ break;
+ case MediaQualityContract.COLOR_TEMP_STANDARD:
+ colorTemperatureByte = ColorTemperature.STANDARD;
+ break;
+ case MediaQualityContract.COLOR_TEMP_WARM:
+ colorTemperatureByte = ColorTemperature.WARM;
+ break;
+ case MediaQualityContract.COLOR_TEMP_USER_HDR10PLUS:
+ colorTemperatureByte = ColorTemperature.USER_HDR10PLUS;
+ break;
+ case MediaQualityContract.COLOR_TEMP_COOL_HDR10PLUS:
+ colorTemperatureByte = ColorTemperature.COOL_HDR10PLUS;
+ break;
+ case MediaQualityContract.COLOR_TEMP_STANDARD_HDR10PLUS:
+ colorTemperatureByte = ColorTemperature.STANDARD_HDR10PLUS;
+ break;
+ case MediaQualityContract.COLOR_TEMP_WARM_HDR10PLUS:
+ colorTemperatureByte = ColorTemperature.WARM_HDR10PLUS;
+ break;
+ case MediaQualityContract.COLOR_TEMP_FMMSDR:
+ colorTemperatureByte = ColorTemperature.FMMSDR;
+ break;
+ case MediaQualityContract.COLOR_TEMP_FMMHDR:
+ colorTemperatureByte = ColorTemperature.FMMHDR;
+ break;
+ default:
+ colorTemperatureByte = ColorTemperature.STANDARD;
+ Log.e("PictureParams", "Invalid color_temp string: "
+ + colorTemperatureString);
+ }
+ pictureParams.add(PictureParameter.colorTemperature(colorTemperatureByte));
+ }
params.remove(PictureQuality.PARAMETER_COLOR_TEMPERATURE);
}
if (params.containsKey(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
@@ -517,8 +581,26 @@
params.remove(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN);
}
if (params.containsKey(PictureQuality.PARAMETER_LEVEL_RANGE)) {
- pictureParams.add(PictureParameter.levelRange(
- (byte) params.getInt(PictureQuality.PARAMETER_LEVEL_RANGE)));
+ String levelRangeString = params.getString(PictureQuality.PARAMETER_LEVEL_RANGE);
+ if (levelRangeString != null) {
+ byte levelRangeByte;
+ switch (levelRangeString) {
+ case "AUTO":
+ levelRangeByte = ColorRange.AUTO;
+ break;
+ case "LIMITED":
+ levelRangeByte = ColorRange.LIMITED;
+ break;
+ case "FULL":
+ levelRangeByte = ColorRange.FULL;
+ break;
+ default:
+ levelRangeByte = ColorRange.AUTO;
+ Log.e("PictureParams", "Invalid color_range string: "
+ + levelRangeString);
+ }
+ pictureParams.add(PictureParameter.levelRange(levelRangeByte));
+ }
params.remove(PictureQuality.PARAMETER_LEVEL_RANGE);
}
if (params.containsKey(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
@@ -547,13 +629,61 @@
params.remove(PictureQuality.PARAMETER_CVRR);
}
if (params.containsKey(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
- pictureParams.add(PictureParameter.hdmiRgbRange(
- (byte) params.getInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE)));
+ String hdmiRgbRangeString = params.getString(PictureQuality.PARAMETER_HDMI_RGB_RANGE);
+ if (hdmiRgbRangeString != null) {
+ byte hdmiRgbRangeByte;
+ switch (hdmiRgbRangeString) {
+ case "AUTO":
+ hdmiRgbRangeByte = ColorRange.AUTO;
+ break;
+ case "LIMITED":
+ hdmiRgbRangeByte = ColorRange.LIMITED;
+ break;
+ case "FULL":
+ hdmiRgbRangeByte = ColorRange.FULL;
+ break;
+ default:
+ hdmiRgbRangeByte = ColorRange.AUTO;
+ Log.e("PictureParams", "Invalid hdmi_rgb_range string: "
+ + hdmiRgbRangeByte);
+ }
+ pictureParams.add(PictureParameter.hdmiRgbRange(hdmiRgbRangeByte));
+ }
params.remove(PictureQuality.PARAMETER_HDMI_RGB_RANGE);
}
if (params.containsKey(PictureQuality.PARAMETER_COLOR_SPACE)) {
- pictureParams.add(PictureParameter.colorSpace(
- (byte) params.getInt(PictureQuality.PARAMETER_COLOR_SPACE)));
+ String colorSpaceString = params.getString(PictureQuality.PARAMETER_COLOR_SPACE);
+ if (colorSpaceString != null) {
+ byte colorSpaceByte;
+ switch (colorSpaceString) {
+ case "AUTO":
+ colorSpaceByte = ColorSpace.AUTO;
+ break;
+ case "S_RGB_BT_709":
+ colorSpaceByte = ColorSpace.S_RGB_BT_709;
+ break;
+ case "DCI":
+ colorSpaceByte = ColorSpace.DCI;
+ break;
+ case "ADOBE_RGB":
+ colorSpaceByte = ColorSpace.ADOBE_RGB;
+ break;
+ case "BT2020":
+ colorSpaceByte = ColorSpace.BT2020;
+ break;
+ case "ON":
+ colorSpaceByte = ColorSpace.ON;
+ break;
+ case "OFF":
+ colorSpaceByte = ColorSpace.OFF;
+ break;
+ default:
+ colorSpaceByte = ColorSpace.OFF;
+ Log.e("PictureParams", "Invalid color_space string: "
+ + colorSpaceString);
+ }
+ pictureParams.add(PictureParameter.colorSpace(colorSpaceByte));
+ }
params.remove(PictureQuality.PARAMETER_COLOR_SPACE);
}
if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)) {
@@ -567,8 +697,25 @@
params.remove(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID);
}
if (params.containsKey(PictureQuality.PARAMETER_GAMMA)) {
- pictureParams.add(PictureParameter.gamma(
- (byte) params.getInt(PictureQuality.PARAMETER_GAMMA)));
+ String gammaString = params.getString(PictureQuality.PARAMETER_GAMMA);
+ if (gammaString != null) {
+ byte gammaByte;
+ switch (gammaString) {
+ case "DARK":
+ gammaByte = Gamma.DARK;
+ break;
+ case "MIDDLE":
+ gammaByte = Gamma.MIDDLE;
+ break;
+ case "BRIGHT":
+ gammaByte = Gamma.BRIGHT;
+ break;
+ default:
+ gammaByte = Gamma.DARK;
+ Log.e("PictureParams", "Invalid gamma string: " + gammaString);
+ }
+ pictureParams.add(PictureParameter.gamma(gammaByte));
+ }
params.remove(PictureQuality.PARAMETER_GAMMA);
}
if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
@@ -602,13 +749,19 @@
params.remove(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE);
}
if (params.containsKey(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
- pictureParams.add(PictureParameter.lowBlueLight(
- (byte) params.getInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)));
+ String lowBlueLightString = params.getString(PictureQuality.PARAMETER_LOW_BLUE_LIGHT);
+ if (lowBlueLightString != null) {
+ byte lowBlueLightByte = mapQualityLevel(lowBlueLightString);
+ pictureParams.add(PictureParameter.lowBlueLight(lowBlueLightByte));
+ }
params.remove(PictureQuality.PARAMETER_LOW_BLUE_LIGHT);
}
if (params.containsKey(PictureQuality.PARAMETER_LD_MODE)) {
- pictureParams.add(PictureParameter.LdMode(
- (byte) params.getInt(PictureQuality.PARAMETER_LD_MODE)));
+ String ldModeString = params.getString(PictureQuality.PARAMETER_LD_MODE);
+ if (ldModeString != null) {
+ byte ldModeByte = mapQualityLevel(ldModeString);
+ pictureParams.add(PictureParameter.LdMode(ldModeByte));
+ }
params.remove(PictureQuality.PARAMETER_LD_MODE);
}
if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
@@ -767,8 +920,44 @@
params.remove(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH);
}
if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) {
- pictureParams.add(PictureParameter.pictureQualityEventType(
- (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)));
+ String pictureQualityEventTypeString = params.getString(
+ PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE);
+ if (pictureQualityEventTypeString != null) {
+ byte pictureQualityEventTypeByte;
+ switch (pictureQualityEventTypeString) {
+ case "NONE":
+ pictureQualityEventTypeByte = PictureQualityEventType.NONE;
+ break;
+ case "BBD_RESULT":
+ pictureQualityEventTypeByte = PictureQualityEventType.BBD_RESULT;
+ break;
+ case "VIDEO_DELAY_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.VIDEO_DELAY_CHANGE;
+ break;
+ case "CAPTUREPOINT_INFO_CHANGE":
+ pictureQualityEventTypeByte =
+ PictureQualityEventType.CAPTUREPOINT_INFO_CHANGE;
+ break;
+ case "VIDEOPATH_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.VIDEOPATH_CHANGE;
+ break;
+ case "EXTRA_FRAME_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.EXTRA_FRAME_CHANGE;
+ break;
+ case "DOLBY_IQ_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.DOLBY_IQ_CHANGE;
+ break;
+ case "DOLBY_APO_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.DOLBY_APO_CHANGE;
+ break;
+ default:
+ pictureQualityEventTypeByte = PictureQualityEventType.NONE;
+ Log.e("PictureParams", "Invalid event type string: "
+ + pictureQualityEventTypeString);
+ }
+ pictureParams.add(
+ PictureParameter.pictureQualityEventType(pictureQualityEventTypeByte));
+ }
params.remove(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE);
}
return pictureParams.toArray(new PictureParameter[0]);
@@ -1657,6 +1846,19 @@
return colIndex != -1 ? cursor.getString(colIndex) : null;
}
+ private static byte mapQualityLevel(String qualityLevel) {
+ return switch (qualityLevel) {
+ case MediaQualityContract.LEVEL_OFF -> QualityLevel.OFF;
+ case MediaQualityContract.LEVEL_LOW -> QualityLevel.LOW;
+ case MediaQualityContract.LEVEL_MEDIUM -> QualityLevel.MEDIUM;
+ case MediaQualityContract.LEVEL_HIGH -> QualityLevel.HIGH;
+ default -> {
+ Log.e("PictureParams", "Invalid noise_reduction string: " + qualityLevel);
+ yield QualityLevel.OFF;
+ }
+ };
+ }
+
private MediaQualityUtils() {
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index fcd1452..2744721 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -311,6 +311,7 @@
static final int SHORT_PRESS_POWER_LOCK_OR_SLEEP = 6;
static final int SHORT_PRESS_POWER_DREAM_OR_SLEEP = 7;
static final int SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP = 8;
+ static final int SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP = 9;
// must match: config_LongPressOnPowerBehavior in config.xml
// The config value can be overridden using Settings.Global.POWER_BUTTON_LONG_PRESS
@@ -1234,8 +1235,10 @@
break;
}
case SHORT_PRESS_POWER_DREAM_OR_SLEEP: {
- attemptToDreamFromShortPowerButtonPress(
- true,
+ attemptToDreamOrAwakeFromShortPowerButtonPress(
+ /* isScreenOn */ true,
+ /* awakeWhenDream */ false,
+ /* noDreamAction */
() -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
break;
}
@@ -1269,13 +1272,22 @@
lockNow(options);
} else {
// If the hub cannot be run, attempt to dream instead.
- attemptToDreamFromShortPowerButtonPress(
+ attemptToDreamOrAwakeFromShortPowerButtonPress(
/* isScreenOn */ true,
+ /* awakeWhenDream */ false,
/* noDreamAction */
() -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
}
break;
}
+ case SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP: {
+ attemptToDreamOrAwakeFromShortPowerButtonPress(
+ /* isScreenOn */ true,
+ /* awakeWhenDream */ true,
+ /* noDreamAction */
+ () -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
+ break;
+ }
}
}
}
@@ -1319,15 +1331,18 @@
}
/**
- * Attempt to dream from a power button press.
+ * Attempt to dream, awake or sleep from a power button press.
*
* @param isScreenOn Whether the screen is currently on.
+ * @param awakeWhenDream When it's set to {@code true}, awake the device from dreaming.
+ * Otherwise, go to sleep.
* @param noDreamAction The action to perform if dreaming is not possible.
*/
- private void attemptToDreamFromShortPowerButtonPress(
- boolean isScreenOn, Runnable noDreamAction) {
+ private void attemptToDreamOrAwakeFromShortPowerButtonPress(
+ boolean isScreenOn, boolean awakeWhenDream, Runnable noDreamAction) {
if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP
- && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP) {
+ && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP
+ && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP) {
// If the power button behavior isn't one that should be able to trigger the dream, give
// up.
noDreamAction.run();
@@ -1335,9 +1350,24 @@
}
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
- if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
- Slog.d(TAG, "Can't start dreaming when attempting to dream from short power"
- + " press (isScreenOn=" + isScreenOn + ")");
+ if (dreamManagerInternal == null) {
+ Slog.d(TAG,
+ "Can't access dream manager dreaming when attempting to start or stop dream "
+ + "from short power press (isScreenOn="
+ + isScreenOn + ", awakeWhenDream=" + awakeWhenDream + ")");
+ noDreamAction.run();
+ return;
+ }
+
+ if (!dreamManagerInternal.canStartDreaming(isScreenOn)) {
+ if (awakeWhenDream && dreamManagerInternal.isDreaming()) {
+ dreamManagerInternal.stopDream(false /*immediate*/, "short press power" /*reason*/);
+ return;
+ }
+ Slog.d(TAG,
+ "Can't start dreaming and the device is not dreaming when attempting to start "
+ + "or stop dream from short power press (isScreenOn="
+ + isScreenOn + ", awakeWhenDream=" + awakeWhenDream + ")");
noDreamAction.run();
return;
}
@@ -2312,6 +2342,10 @@
return ActivityManager.getService();
}
+ LockPatternUtils getLockPatternUtils() {
+ return new LockPatternUtils(mContext);
+ }
+
ButtonOverridePermissionChecker getButtonOverridePermissionChecker() {
return new ButtonOverridePermissionChecker();
}
@@ -2360,7 +2394,7 @@
mAccessibilityShortcutController = injector.getAccessibilityShortcutController(
mContext, new Handler(), mCurrentUserId);
mGlobalActionsFactory = injector.getGlobalActionsFactory();
- mLockPatternUtils = new LockPatternUtils(mContext);
+ mLockPatternUtils = injector.getLockPatternUtils();
mLogger = new MetricsLogger();
Resources res = mContext.getResources();
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index ac4aac6..11edb93 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -383,7 +383,9 @@
/* api_name */
initialPhaseMetric.getApiName(),
/* primary_candidates_indicated */
- candidatePrimaryProviderList
+ candidatePrimaryProviderList,
+ /* api_prepared */
+ initialPhaseMetric.hasApiUsedPrepareFlow()
);
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e);
@@ -442,7 +444,9 @@
/* autofill_session_id */
initialPhaseMetric.getAutofillSessionId(),
/* autofill_request_id */
- initialPhaseMetric.getAutofillRequestId()
+ initialPhaseMetric.getAutofillRequestId(),
+ /* api_prepared */
+ initialPhaseMetric.hasApiUsedPrepareFlow()
);
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during initial metric emit: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index d60807c..2d4360e 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -27,6 +27,7 @@
import android.credentials.IGetCredentialCallback;
import android.credentials.IPrepareGetCredentialCallback;
import android.credentials.PrepareGetCredentialResponseInternal;
+import android.credentials.flags.Flags;
import android.credentials.selection.GetCredentialProviderData;
import android.credentials.selection.ProviderData;
import android.credentials.selection.RequestInfo;
@@ -60,7 +61,12 @@
int numTypes = (request.getCredentialOptions().stream()
.map(CredentialOption::getType).collect(
Collectors.toSet())).size(); // Dedupe type strings
- mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
+ if (!Flags.fixMetricDuplicationEmits()) {
+ mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
+ } else {
+ mRequestSessionMetric.collectGetFlowInitialMetricInfo(request,
+ /*isApiPrepared=*/ true);
+ }
mPrepareGetCredentialCallback = prepareGetCredentialCallback;
Slog.i(TAG, "PrepareGetRequestSession constructed.");
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 8a4e86c..811b97a 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -55,6 +55,9 @@
// The request id of autofill if the request is from autofill, defaults to -1
private int mAutofillRequestId = -1;
+ // Indicates if this API call used the prepare flow, defaults to false
+ private boolean mApiUsedPrepareFlow = false;
+
public InitialPhaseMetric(int sessionIdTrackOne) {
mSessionIdCaller = sessionIdTrackOne;
@@ -173,4 +176,17 @@
public int[] getUniqueRequestCounts() {
return mRequestCounts.values().stream().mapToInt(Integer::intValue).toArray();
}
+
+ /* ------ API Prepared ------ */
+
+ public void setApiUsedPrepareFlow(boolean apiUsedPrepareFlow) {
+ mApiUsedPrepareFlow = apiUsedPrepareFlow;
+ }
+
+ /**
+ * @return a boolean indicating if this API call utilized a prepare flow
+ */
+ public boolean hasApiUsedPrepareFlow() {
+ return mApiUsedPrepareFlow;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 619a568..dc1747f 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -225,6 +225,22 @@
}
/**
+ * Collects initializations for Get flow metrics.
+ *
+ * @param request the get credential request containing information to parse for metrics
+ * @param isApiPrepared indicates this API flow utilized the 'prepare' flow
+ */
+ public void collectGetFlowInitialMetricInfo(GetCredentialRequest request,
+ boolean isApiPrepared) {
+ try {
+ collectGetFlowInitialMetricInfo(request);
+ mInitialPhaseMetric.setApiUsedPrepareFlow(isApiPrepared);
+ } catch (Exception e) {
+ Slog.i(TAG, "Unexpected error collecting get flow initial metric: " + e);
+ }
+ }
+
+ /**
* During browsing, where multiple entries can be selected, this collects the browsing phase
* metric information. This is emitted together with the final phase, and the recursive path
* with authentication entries, which may occur in rare circumstances, are captured.
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 232bb83..5a140d5 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1753,6 +1753,13 @@
}
val appIdPermissionFlags = newState.mutateUserState(userId)!!.mutateAppIdPermissionFlags()
val permissionFlags = appIdPermissionFlags.mutateOrPut(appId) { MutableIndexedMap() }
+ // for debugging possible races TODO(b/401768134)
+ oldState.userStates[userId]?.appIdPermissionFlags[appId]?.map?.let {
+ if (permissionFlags.map === it) {
+ throw IllegalStateException("Unexpected sharing between old/new state")
+ }
+ }
+
permissionFlags.putWithDefault(permissionName, newFlags, 0)
if (permissionFlags.isEmpty()) {
appIdPermissionFlags -= appId
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 99c922c..df77866 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 org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -866,6 +867,23 @@
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void scrollPanelController_directionalButtonsHideIndicator() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Create a spy on the real object to verify method calls.
+ AutoclickIndicatorView spyIndicatorView = spy(mController.mAutoclickIndicatorView);
+ mController.mAutoclickIndicatorView = spyIndicatorView;
+
+ // Simulate hover on direction button.
+ mController.mScrollPanelController.onHoverButtonChange(
+ AutoclickScrollPanel.DIRECTION_UP, true);
+
+ // Verify clearIndicator was called.
+ verify(spyIndicatorView).clearIndicator();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() {
MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
mController.setNext(motionEventCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioVolumeChangeHandlerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioVolumeChangeHandlerTest.java
new file mode 100644
index 0000000..f252a98
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioVolumeChangeHandlerTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.INativeAudioVolumeGroupCallback;
+import android.media.audio.common.AudioVolumeGroupChangeEvent;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioVolumeChangeHandlerTest {
+ private static final long DEFAULT_TIMEOUT_MS = 1000;
+
+ private AudioSystemAdapter mSpyAudioSystem;
+
+ AudioVolumeChangeHandler mAudioVolumeChangedHandler;
+
+ private final IAudioVolumeChangeDispatcher.Stub mMockDispatcher =
+ mock(IAudioVolumeChangeDispatcher.Stub.class);
+
+ @Before
+ public void setUp() {
+ mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+ when(mMockDispatcher.asBinder()).thenReturn(mock(IBinder.class));
+ mAudioVolumeChangedHandler = new AudioVolumeChangeHandler(mSpyAudioSystem);
+ }
+
+ @Test
+ public void registerListener_withInvalidCallback() {
+ IAudioVolumeChangeDispatcher.Stub nullCb = null;
+ NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+ mAudioVolumeChangedHandler.registerListener(nullCb);
+ });
+
+ assertWithMessage("Exception for invalid registration").that(thrown).hasMessageThat()
+ .contains("Volume group callback");
+ }
+
+ @Test
+ public void unregisterListener_withInvalidCallback() {
+ IAudioVolumeChangeDispatcher.Stub nullCb = null;
+ mAudioVolumeChangedHandler.registerListener(mMockDispatcher);
+
+ NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+ mAudioVolumeChangedHandler.unregisterListener(nullCb);
+ });
+
+ assertWithMessage("Exception for invalid un-registration").that(thrown).hasMessageThat()
+ .contains("Volume group callback");
+ }
+
+ @Test
+ public void registerListener() {
+ mAudioVolumeChangedHandler.registerListener(mMockDispatcher);
+
+ verify(mSpyAudioSystem).registerAudioVolumeGroupCallback(any());
+ }
+
+ @Test
+ public void onAudioVolumeGroupChanged() throws Exception {
+ mAudioVolumeChangedHandler.registerListener(mMockDispatcher);
+ AudioVolumeGroupChangeEvent volEvent = new AudioVolumeGroupChangeEvent();
+ volEvent.groupId = 666;
+ volEvent.flags = AudioVolumeGroupChangeEvent.VOLUME_FLAG_FROM_KEY;
+
+ captureRegisteredNativeCallback().onAudioVolumeGroupChanged(volEvent);
+
+ verify(mMockDispatcher, timeout(DEFAULT_TIMEOUT_MS)).onAudioVolumeGroupChanged(
+ eq(volEvent.groupId), eq(volEvent.flags));
+ }
+
+ @Test
+ public void onAudioVolumeGroupChanged_withMultipleCallback() throws Exception {
+ int callbackCount = 10;
+ List<IAudioVolumeChangeDispatcher.Stub> validCbs =
+ new ArrayList<IAudioVolumeChangeDispatcher.Stub>();
+ for (int i = 0; i < callbackCount; i++) {
+ IAudioVolumeChangeDispatcher.Stub cb = mock(IAudioVolumeChangeDispatcher.Stub.class);
+ when(cb.asBinder()).thenReturn(mock(IBinder.class));
+ validCbs.add(cb);
+ }
+ for (IAudioVolumeChangeDispatcher.Stub cb : validCbs) {
+ mAudioVolumeChangedHandler.registerListener(cb);
+ }
+ AudioVolumeGroupChangeEvent volEvent = new AudioVolumeGroupChangeEvent();
+ volEvent.groupId = 666;
+ volEvent.flags = AudioVolumeGroupChangeEvent.VOLUME_FLAG_FROM_KEY;
+ captureRegisteredNativeCallback().onAudioVolumeGroupChanged(volEvent);
+
+ for (IAudioVolumeChangeDispatcher.Stub cb : validCbs) {
+ verify(cb, timeout(DEFAULT_TIMEOUT_MS)).onAudioVolumeGroupChanged(
+ eq(volEvent.groupId), eq(volEvent.flags));
+ }
+ }
+
+ private INativeAudioVolumeGroupCallback captureRegisteredNativeCallback() {
+ ArgumentCaptor<INativeAudioVolumeGroupCallback> nativeAudioVolumeGroupCallbackCaptor =
+ ArgumentCaptor.forClass(INativeAudioVolumeGroupCallback.class);
+ verify(mSpyAudioSystem, timeout(DEFAULT_TIMEOUT_MS))
+ .registerAudioVolumeGroupCallback(nativeAudioVolumeGroupCallbackCaptor.capture());
+ return nativeAudioVolumeGroupCallbackCaptor.getValue();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 43b1ec3..87cd156 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -19,7 +19,9 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -29,6 +31,7 @@
import android.content.Context;
import android.hardware.contexthub.EndpointInfo;
import android.hardware.contexthub.ErrorCode;
+import android.hardware.contexthub.HubEndpoint;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier;
import android.hardware.contexthub.HubMessage;
@@ -385,6 +388,49 @@
unregisterExampleEndpoint(endpoint);
}
+ @Test
+ public void testUnreliableMessageFailureClosesSession() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ int sessionId = openTestSession(endpoint);
+
+ doThrow(new RemoteException("Intended exception in test"))
+ .when(mMockCallback)
+ .onMessageReceived(anyInt(), any(HubMessage.class));
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_UNRELIABLE_MESSAGE);
+ ArgumentCaptor<HubMessage> messageCaptor = ArgumentCaptor.forClass(HubMessage.class);
+ verify(mMockCallback).onMessageReceived(eq(sessionId), messageCaptor.capture());
+ assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE);
+
+ verify(mMockEndpointCommunications).closeEndpointSession(sessionId, Reason.UNSPECIFIED);
+ verify(mMockCallback).onSessionClosed(sessionId, HubEndpoint.REASON_FAILURE);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
+ public void testSendUnreliableMessageFailureClosesSession() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ int sessionId = openTestSession(endpoint);
+
+ doThrow(new RemoteException("Intended exception in test"))
+ .when(mMockEndpointCommunications)
+ .sendMessageToEndpoint(anyInt(), any(Message.class));
+ endpoint.sendMessage(sessionId, SAMPLE_UNRELIABLE_MESSAGE, /* callback= */ null);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(mMockEndpointCommunications)
+ .sendMessageToEndpoint(eq(sessionId), messageCaptor.capture());
+ Message message = messageCaptor.getValue();
+ assertThat(message.type).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE.getMessageType());
+ assertThat(message.content).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE.getMessageBody());
+
+ verify(mMockEndpointCommunications).closeEndpointSession(sessionId, Reason.UNSPECIFIED);
+ verify(mMockCallback).onSessionClosed(sessionId, HubEndpoint.REASON_FAILURE);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
/** A helper method to create a session and validates reliable message sending. */
private void testMessageTransactionInternal(
IContextHubEndpoint endpoint, boolean deliverMessageStatus) throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 6822968..f3d5e39 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -35,6 +35,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.policy.PhoneWindowManager.EXTRA_TRIGGER_HUB;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP;
import static com.google.common.truth.Truth.assertThat;
@@ -64,6 +65,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.LocalServiceKeeperRule;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.input.InputManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.keyguard.KeyguardServiceDelegate;
@@ -120,6 +122,8 @@
private DisplayPolicy mDisplayPolicy;
@Mock
private KeyguardServiceDelegate mKeyguardServiceDelegate;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
@Before
public void setUp() {
@@ -254,6 +258,7 @@
@Test
public void powerPress_hubOrDreamOrSleep_goesToSleepFromDream() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
initPhoneWindowManager();
// Set power button behavior.
@@ -275,6 +280,7 @@
@Test
public void powerPress_hubOrDreamOrSleep_hubAvailableLocks() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
mContext.getTestablePermissions().setPermission(android.Manifest.permission.DEVICE_POWER,
PERMISSION_GRANTED);
initPhoneWindowManager();
@@ -303,6 +309,7 @@
@Test
public void powerPress_hubOrDreamOrSleep_hubNotAvailableDreams() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
initPhoneWindowManager();
// Set power button behavior.
@@ -323,6 +330,77 @@
verify(mDreamManagerInternal).requestDream();
}
+ @Test
+ public void powerPress_dreamOrAwakeOrSleep_awakeFromDream() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Can not dream when device is dreaming.
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(false);
+ // Device is dreaming.
+ when(mDreamManagerInternal.isDreaming()).thenReturn(true);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Dream is stopped.
+ verify(mDreamManagerInternal)
+ .stopDream(false /*immediate*/, "short press power" /*reason*/);
+ }
+
+ @Test
+ public void powerPress_dreamOrAwakeOrSleep_canNotDreamGoToSleep() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Can not dream for other reasons.
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(false);
+ // Device is not dreaming.
+ when(mDreamManagerInternal.isDreaming()).thenReturn(false);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Device goes to sleep.
+ verify(mPowerManager).goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ }
+
+ @Test
+ public void powerPress_dreamOrAwakeOrSleep_dreamFromActive() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Can dream when active.
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(true);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Dream is requested.
+ verify(mDreamManagerInternal).requestDream();
+ }
+
private void initPhoneWindowManager() {
mPhoneWindowManager.mDefaultDisplayPolicy = mDisplayPolicy;
mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class);
@@ -346,6 +424,11 @@
return mKeyguardServiceDelegate;
}
+ @Override
+ LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
/**
* {@code WindowWakeUpPolicy} registers a local service in its constructor, easier to just
* mock it out so we don't have to unregister it after every test.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1491510..19574fd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -11493,17 +11493,17 @@
+ "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
PersistableBundle auto_data_switch_rat_signal_score_string_bundle = new PersistableBundle();
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "NR_SA_MMWAVE", new int[]{10000, 13227, 16000, 18488, 20017});
+ "NR_SA_MMWAVE", new int[]{6300, 10227, 16000, 18488, 19017});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "NR_NSA_MMWAVE", new int[]{8000, 10227, 12488, 15017, 15278});
+ "NR_NSA_MMWAVE", new int[]{5700, 9227, 12488, 13517, 15978});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"LTE", new int[]{3731, 5965, 8618, 11179, 13384});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484});
+ "LTE_CA", new int[]{3831, 6065, 8718, 11379, 14484});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713});
+ "NR_SA", new int[]{2288, 6795, 6955, 7562, 15484});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428});
+ "NR_NSA", new int[]{2463, 6827, 8029, 9007, 15884});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"UMTS", new int[]{100, 169, 183, 192, 300});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index f4735a2..380c5f2 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -50,8 +50,11 @@
// Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file).
repeated Symbol exported_symbol = 5;
- // The status of the flag the file is behind if any
+ // The status of the read only flag the file is behind if any
uint32 flag_status = 6;
bool flag_negated = 7;
string flag_name = 8;
+
+ // Whether the file uses read/write feature flags
+ bool uses_readwrite_feature_flags = 9;
}
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index a5e18d35..3b4f542 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -407,6 +407,45 @@
return true;
}
+class FindReadWriteFlagsVisitor : public xml::Visitor {
+ public:
+ FindReadWriteFlagsVisitor(const FeatureFlagValues& feature_flag_values)
+ : feature_flag_values_(feature_flag_values) {
+ }
+
+ void Visit(xml::Element* node) override {
+ if (had_flags_) {
+ return;
+ }
+ auto* attr = node->FindAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag);
+ if (attr != nullptr) {
+ std::string_view flag_name = util::TrimWhitespace(attr->value);
+ if (flag_name.starts_with('!')) {
+ flag_name = flag_name.substr(1);
+ }
+ if (auto it = feature_flag_values_.find(flag_name); it != feature_flag_values_.end()) {
+ if (!it->second.read_only) {
+ had_flags_ = true;
+ return;
+ }
+ } else {
+ // Flag not passed to aapt2, must evaluate at runtime
+ had_flags_ = true;
+ return;
+ }
+ }
+ VisitChildren(node);
+ }
+
+ bool HadFlags() const {
+ return had_flags_;
+ }
+
+ private:
+ bool had_flags_ = false;
+ const FeatureFlagValues& feature_flag_values_;
+};
+
static bool CompileXml(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
const std::string& output_path) {
@@ -436,6 +475,10 @@
xmlres->file.type = ResourceFile::Type::kProtoXml;
xmlres->file.flag = ParseFlag(path_data.flag_name);
+ FindReadWriteFlagsVisitor visitor(options.feature_flag_values);
+ xmlres->root->Accept(&visitor);
+ xmlres->file.uses_readwrite_feature_flags = visitor.HadFlags();
+
if (xmlres->file.flag) {
std::string error;
auto flag_status = GetFlagStatus(xmlres->file.flag, options.feature_flag_values, &error);
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index 9452e58..5576ec0 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -38,14 +38,14 @@
"--enable-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
- "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
- "the APK is O+",
+ "Only applies sparse encoding if minSdk of the APK is >= 32",
&enable_sparse_encoding_);
- AddOptionalSwitch("--force-sparse-encoding",
- "Enables encoding sparse entries using a binary search tree.\n"
- "This decreases APK size at the cost of resource retrieval performance.\n"
- "Applies sparse encoding to all resources regardless of minSdk.",
- &force_sparse_encoding_);
+ AddOptionalSwitch(
+ "--force-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding if minSdk of the APK is >= 32 or is not set",
+ &force_sparse_encoding_);
AddOptionalSwitch(
"--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 755dbb6..0e18ee2 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -615,6 +615,8 @@
file_op.xml_to_flatten->file.source = file_ref->GetSource();
file_op.xml_to_flatten->file.name =
ResourceName(pkg->name, type->named_type, entry->name);
+ file_op.xml_to_flatten->file.uses_readwrite_feature_flags =
+ config_value->uses_readwrite_feature_flags;
}
// NOTE(adamlesinski): Explicitly construct a StringPiece here, or
@@ -647,6 +649,17 @@
}
}
+ FeatureFlagsFilterOptions flags_filter_options;
+ // Don't fail on unrecognized flags or flags without values as these flags might be
+ // defined and have a value by the time they are evaluated at runtime.
+ flags_filter_options.fail_on_unrecognized_flags = false;
+ flags_filter_options.flags_must_have_value = false;
+ flags_filter_options.remove_disabled_elements = true;
+ FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
+ if (!flags_filter.Consume(context_, file_op.xml_to_flatten.get())) {
+ return 1;
+ }
+
std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
LinkAndVersionXmlFile(table, &file_op);
if (versioned_docs.empty()) {
@@ -673,6 +686,7 @@
// Update the output format of this XML file.
file_ref->type = XmlFileTypeForOutputFormat(options_.output_format);
+
bool result = table->AddResource(
NewResourceBuilder(file.name)
.SetValue(std::move(file_ref), file.config)
@@ -685,14 +699,6 @@
}
}
- FeatureFlagsFilterOptions flags_filter_options;
- flags_filter_options.fail_on_unrecognized_flags = false;
- flags_filter_options.flags_must_have_value = false;
- FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
- if (!flags_filter.Consume(context_, doc.get())) {
- return 1;
- }
-
error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values,
false /*utf16*/, options_.output_format, archive_writer);
}
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 9779788..54a8c86 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -164,9 +164,12 @@
AddOptionalSwitch("--no-resource-removal", "Disables automatic removal of resources without\n"
"defaults. Use this only when building runtime resource overlay packages.",
&options_.no_resource_removal);
- AddOptionalSwitch("--enable-sparse-encoding",
- "This decreases APK size at the cost of resource retrieval performance.",
- &options_.use_sparse_encoding);
+ AddOptionalSwitch(
+ "--enable-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding if minSdk of the APK is >= 32",
+ &options_.use_sparse_encoding);
AddOptionalSwitch("--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
&options_.table_flattener_options.use_compact_entries);
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 012b0f2..a8f547e 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -108,14 +108,14 @@
"--enable-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
- "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
- "the APK is O+",
+ "Only applies sparse encoding if minSdk of the APK is >= 32",
&options_.enable_sparse_encoding);
- AddOptionalSwitch("--force-sparse-encoding",
- "Enables encoding sparse entries using a binary search tree.\n"
- "This decreases APK size at the cost of resource retrieval performance.\n"
- "Applies sparse encoding to all resources regardless of minSdk.",
- &options_.force_sparse_encoding);
+ AddOptionalSwitch(
+ "--force-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding if minSdk of the APK is >= 32 or is not set",
+ &options_.force_sparse_encoding);
AddOptionalSwitch(
"--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 50144ae..d19c9f2 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -197,13 +197,16 @@
bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled ||
sparse_entries_ == SparseEntriesMode::Forced;
- if (sparse_entries_ == SparseEntriesMode::Forced ||
- (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0)) {
- // Sparse encode if forced or sdk version is not set in context and config.
- } else {
- // Otherwise, only sparse encode if the entries will be read on platforms S_V2+.
- sparse_encode = sparse_encode && (context_->GetMinSdkVersion() >= SDK_S_V2);
- }
+ // Only sparse encode if the entries will be read on platforms S_V2+. Sparse encoding
+ // is not supported on older platforms (b/197642721, b/197976367).
+ //
+ // We also allow sparse encoding for minSdk is 0 (not set) if sparse encoding is forced,
+ // in order to support Bundletool's usage of aapt2 where minSdk is not set in splits.
+ bool meets_min_sdk_requirement_for_sparse_encoding =
+ (context_->GetMinSdkVersion() >= SDK_S_V2) ||
+ (context_->GetMinSdkVersion() == 0 && sparse_entries_ == SparseEntriesMode::Forced);
+
+ sparse_encode = sparse_encode && meets_min_sdk_requirement_for_sparse_encoding;
// Only sparse encode if the offsets are representable in 2 bytes.
sparse_encode = sparse_encode && short_offsets;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 9156b96..0e8aae1 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -15,6 +15,7 @@
*/
#include "format/binary/TableFlattener.h"
+#include <string>
#include "android-base/stringprintf.h"
#include "androidfw/TypeWrappers.h"
@@ -326,6 +327,28 @@
return table;
}
+static void CheckSparseEntries(IAaptContext* context, const ConfigDescription& sparse_config,
+ const std::string& sparse_contents) {
+ ResourceTable sparse_table;
+ BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
+ sparse_contents.data(), sparse_contents.size());
+ ASSERT_TRUE(parser.Parse());
+
+ auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
+ sparse_config);
+ ASSERT_THAT(value, NotNull());
+ EXPECT_EQ(0u, value->value.data);
+
+ ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
+ sparse_config),
+ IsNull());
+
+ value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
+ sparse_config);
+ ASSERT_THAT(value, NotNull());
+ EXPECT_EQ(4u, value->value.data);
+}
+
TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
@@ -347,29 +370,56 @@
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
- // Attempt to parse the sparse contents.
-
- ResourceTable sparse_table;
- BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
- sparse_contents.data(), sparse_contents.size());
- ASSERT_TRUE(parser.Parse());
-
- auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(0u, value->value.data);
-
- ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
- sparse_config),
- IsNull());
-
- value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(4u, value->value.data);
+ CheckSparseEntries(context.get(), sparse_config, sparse_contents);
}
-TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionSV2) {
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2AndForced) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetMinSdkVersion(SDK_S_V2)
+ .Build();
+
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+ TableFlattenerOptions options;
+ options.sparse_entries = SparseEntriesMode::Forced;
+
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+
+ CheckSparseEntries(context.get(), sparse_config, sparse_contents);
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetMinSdkVersion(SDK_LOLLIPOP)
+ .Build();
+
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+ TableFlattenerOptions options;
+ options.sparse_entries = SparseEntriesMode::Enabled;
+
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2AndConfigSdkVersionSV2) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
.SetPackageId(0x01)
@@ -391,7 +441,7 @@
EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
}
-TEST_F(TableFlattenerTest, FlattenSparseEntryRegardlessOfMinSdkWhenForced) {
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2AndForced) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
.SetPackageId(0x01)
@@ -410,7 +460,7 @@
std::string sparse_contents;
ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
- EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
}
TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) {
@@ -429,28 +479,28 @@
std::string sparse_contents;
ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSetAndForced) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build();
+
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+ TableFlattenerOptions options;
+ options.sparse_entries = SparseEntriesMode::Forced;
+
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
- // Attempt to parse the sparse contents.
-
- ResourceTable sparse_table;
- BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
- sparse_contents.data(), sparse_contents.size());
- ASSERT_TRUE(parser.Parse());
-
- auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(0u, value->value.data);
-
- ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
- sparse_config),
- IsNull());
-
- value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(4u, value->value.data);
+ CheckSparseEntries(context.get(), sparse_config, sparse_contents);
}
TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 91ec348..b893655 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -640,6 +640,7 @@
out_file->name = name_ref.ToResourceName();
out_file->source.path = pb_file.source_path();
out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+ out_file->uses_readwrite_feature_flags = pb_file.uses_readwrite_feature_flags();
out_file->flag_status = (FlagStatus)pb_file.flag_status();
if (!pb_file.flag_name().empty()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index fcc77d5..da99c4f 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -767,6 +767,7 @@
out_file->set_flag_negated(file.flag->negated);
out_file->set_flag_name(file.flag->name);
}
+ out_file->set_uses_readwrite_feature_flags(file.uses_readwrite_feature_flags);
for (const SourcedResourceName& exported : file.exported_symbols) {
pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol();
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index 47a71fe..4dcb850 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -226,9 +226,11 @@
}
}
}
+
ASSERT_TRUE(found) << "No entry for layout1 at v36 with FLAG_USES_FEATURE_FLAGS bit set";
- // There should only be 1 entry that has the FLAG_USES_FEATURE_FLAGS bit of flags set to 1
- ASSERT_EQ(fields_flagged, 1);
+ // There should only be 2 entry that has the FLAG_USES_FEATURE_FLAGS bit of flags set to 1, the
+ // three versions of the layout file that has flags
+ ASSERT_EQ(fields_flagged, 3);
}
} // namespace aapt
diff --git a/tools/aapt2/link/FlaggedXmlVersioner.cpp b/tools/aapt2/link/FlaggedXmlVersioner.cpp
index 8a3337c..626cae7 100644
--- a/tools/aapt2/link/FlaggedXmlVersioner.cpp
+++ b/tools/aapt2/link/FlaggedXmlVersioner.cpp
@@ -35,10 +35,6 @@
VisitChildren(node);
}
- bool HadFlags() const {
- return had_flags_;
- }
-
private:
bool FixupOrShouldRemove(const std::unique_ptr<xml::Node>& node) {
if (auto* el = NodeCast<Element>(node.get())) {
@@ -47,7 +43,6 @@
return false;
}
- had_flags_ = true;
// This class assumes all flags are disabled so we want to remove any elements behind flags
// unless the flag specification is negated. In the negated case we remove the featureFlag
// attribute because we have already determined whether we are keeping the element or not.
@@ -62,56 +57,27 @@
return false;
}
-
- bool had_flags_ = false;
-};
-
-// An xml visitor that goes through the a doc and determines if any elements are behind a flag.
-class FindFlagsVisitor : public xml::Visitor {
- public:
- void Visit(xml::Element* node) override {
- if (had_flags_) {
- return;
- }
- auto* attr = node->FindAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag);
- if (attr != nullptr) {
- had_flags_ = true;
- return;
- }
- VisitChildren(node);
- }
-
- bool HadFlags() const {
- return had_flags_;
- }
-
- bool had_flags_ = false;
};
std::vector<std::unique_ptr<xml::XmlResource>> FlaggedXmlVersioner::Process(IAaptContext* context,
xml::XmlResource* doc) {
std::vector<std::unique_ptr<xml::XmlResource>> docs;
- if ((static_cast<ApiVersion>(doc->file.config.sdkVersion) >= SDK_BAKLAVA) ||
- (static_cast<ApiVersion>(context->GetMinSdkVersion()) >= SDK_BAKLAVA)) {
+ if (!doc->file.uses_readwrite_feature_flags) {
+ docs.push_back(doc->Clone());
+ } else if ((static_cast<ApiVersion>(doc->file.config.sdkVersion) >= SDK_BAKLAVA) ||
+ (static_cast<ApiVersion>(context->GetMinSdkVersion()) >= SDK_BAKLAVA)) {
// Support for read/write flags was added in baklava so if the doc will only get used on
// baklava or later we can just return the original doc.
docs.push_back(doc->Clone());
- FindFlagsVisitor visitor;
- doc->root->Accept(&visitor);
- docs.back()->file.uses_readwrite_feature_flags = visitor.HadFlags();
} else {
auto preBaklavaVersion = doc->Clone();
AllDisabledFlagsVisitor visitor;
preBaklavaVersion->root->Accept(&visitor);
- preBaklavaVersion->file.uses_readwrite_feature_flags = false;
docs.push_back(std::move(preBaklavaVersion));
- if (visitor.HadFlags()) {
- auto baklavaVersion = doc->Clone();
- baklavaVersion->file.config.sdkVersion = SDK_BAKLAVA;
- baklavaVersion->file.uses_readwrite_feature_flags = true;
- docs.push_back(std::move(baklavaVersion));
- }
+ auto baklavaVersion = doc->Clone();
+ baklavaVersion->file.config.sdkVersion = SDK_BAKLAVA;
+ docs.push_back(std::move(baklavaVersion));
}
return docs;
}
diff --git a/tools/aapt2/link/FlaggedXmlVersioner_test.cpp b/tools/aapt2/link/FlaggedXmlVersioner_test.cpp
index 0c1314f..0dc4642 100644
--- a/tools/aapt2/link/FlaggedXmlVersioner_test.cpp
+++ b/tools/aapt2/link/FlaggedXmlVersioner_test.cpp
@@ -101,6 +101,7 @@
<TextView android:featureFlag="package.flag" /><TextView /><TextView />
</LinearLayout>)");
doc->file.config.sdkVersion = SDK_GINGERBREAD;
+ doc->file.uses_readwrite_feature_flags = true;
FlaggedXmlVersioner versioner;
auto results = versioner.Process(context_.get(), doc.get());
@@ -131,6 +132,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:featureFlag="package.flag" /><TextView /><TextView />
</LinearLayout>)");
+ doc->file.uses_readwrite_feature_flags = true;
FlaggedXmlVersioner versioner;
auto results = versioner.Process(context_.get(), doc.get());
@@ -162,6 +164,7 @@
<TextView android:featureFlag="!package.flag" /><TextView /><TextView />
</LinearLayout>)");
doc->file.config.sdkVersion = SDK_GINGERBREAD;
+ doc->file.uses_readwrite_feature_flags = true;
FlaggedXmlVersioner versioner;
auto results = versioner.Process(context_.get(), doc.get());
@@ -192,6 +195,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:featureFlag="!package.flag" /><TextView /><TextView />
</LinearLayout>)");
+ doc->file.uses_readwrite_feature_flags = true;
FlaggedXmlVersioner versioner;
auto results = versioner.Process(context_.get(), doc.get());
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 1d4adc4..17f3323 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -295,6 +295,8 @@
dst_config_value =
dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product);
}
+ dst_config_value->uses_readwrite_feature_flags |=
+ src_config_value->uses_readwrite_feature_flags;
// Continue if we're taking the new resource.
CloningValueTransformer cloner(&main_table_->string_pool);
@@ -378,12 +380,13 @@
file_ref->file = file;
file_ref->SetFlagStatus(file_desc.flag_status);
file_ref->SetFlag(file_desc.flag);
-
ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package);
- pkg->FindOrCreateType(file_desc.name.type)
- ->FindOrCreateEntry(file_desc.name.entry)
- ->FindOrCreateValue(file_desc.config, {})
- ->value = std::move(file_ref);
+ ResourceConfigValue* config_value = pkg->FindOrCreateType(file_desc.name.type)
+ ->FindOrCreateEntry(file_desc.name.entry)
+ ->FindOrCreateValue(file_desc.config, {});
+
+ config_value->value = std::move(file_ref);
+ config_value->uses_readwrite_feature_flags = file_desc.uses_readwrite_feature_flags;
return DoMerge(file->GetSource(), pkg, false /*mangle*/, overlay /*overlay*/, true /*allow_new*/);
}
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 6bdbaae..413f817 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -5,6 +5,10 @@
2017. This README will be updated more frequently in the future.
- Added a new flag `--no-compress-fonts`. This can significantly speed up loading fonts from APK
assets, at the cost of increasing the storage size of the APK.
+- Changed the behavior of `--enable-sparse-encoding`. Sparse encoding is only applied if the
+ minSdkVersion is >= 32.
+- Changed the behavior of `--force-sparse-encoding`. Sparse encoding is only applied if the
+ minSdkVersion is >= 32 or is not set.
## Version 2.19
- Added navigation resource type.