summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/Android.bp1
-rw-r--r--apex/jobscheduler/service/aconfig/Android.bp12
-rw-r--r--apex/jobscheduler/service/aconfig/alarm.aconfig11
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java78
-rw-r--r--core/api/current.txt2
-rw-r--r--core/api/system-current.txt3
-rw-r--r--core/java/Android.bp1
-rw-r--r--core/java/android/app/assist/AssistStructure.java13
-rw-r--r--core/java/android/os/ExternalVibrationScale.aidl45
-rw-r--r--core/java/android/os/IExternalVibratorService.aidl26
-rw-r--r--core/java/android/provider/Settings.java18
-rw-r--r--core/java/android/view/BatchedInputEventReceiver.java13
-rw-r--r--core/java/android/view/InputEventReceiver.java17
-rw-r--r--core/java/android/view/TextureView.java2
-rw-r--r--core/java/android/view/View.java28
-rw-r--r--core/java/android/view/ViewRootImpl.java76
-rw-r--r--core/java/android/view/inputmethod/InputBinding.java2
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java15
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig8
-rw-r--r--core/jni/android_view_InputEventReceiver.cpp15
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java10
-rw-r--r--libs/hwui/Android.bp41
-rw-r--r--libs/hwui/ProfileData.cpp2
-rw-r--r--libs/hwui/platform/android/thread/ThreadBase.h (renamed from libs/hwui/thread/ThreadBase.h)6
-rw-r--r--libs/hwui/platform/host/ProfileDataContainer.cpp40
-rw-r--r--libs/hwui/platform/host/Readback.cpp50
-rw-r--r--libs/hwui/platform/host/WebViewFunctorManager.cpp75
-rw-r--r--libs/hwui/platform/host/renderthread/CacheManager.cpp64
-rw-r--r--libs/hwui/platform/host/renderthread/RenderThread.cpp141
-rw-r--r--libs/hwui/platform/host/thread/ThreadBase.h76
-rw-r--r--libs/hwui/private/hwui/WebViewFunctor.h8
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp3
-rw-r--r--libs/hwui/renderthread/RenderThread.h3
-rw-r--r--nfc/java/android/nfc/cardemulation/ApduServiceInfo.java26
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt123
-rw-r--r--packages/PrintRecommendationService/res/values/strings.xml1
-rw-r--r--packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java9
-rw-r--r--packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java166
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt4
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownBoxPageProvider.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuBoxPageProvider.kt)55
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt (renamed from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt)87
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBox.kt69
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt61
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBoxTest.kt109
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBoxTest.kt95
-rw-r--r--packages/SettingsLib/res/drawable/ic_bt_le_audio_sharing.xml87
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt18
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt93
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt33
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt86
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt2
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt18
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java4
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig17
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt10
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt83
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt55
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt306
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt184
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt60
-rw-r--r--packages/SystemUI/res/layout/screen_share_dialog.xml8
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt188
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt11
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt1
-rw-r--r--services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java60
-rw-r--r--services/backup/flags.aconfig9
-rw-r--r--services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java4
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java181
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/display/BrightnessThrottler.java61
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java29
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java6
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java136
-rw-r--r--services/core/java/com/android/server/display/config/SensorData.java30
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java10
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/display/utils/SensorUtils.java15
-rw-r--r--services/core/java/com/android/server/input/debug/FocusEventDebugView.java10
-rw-r--r--services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java13
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java16
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java29
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java7
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java22
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java2
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java25
-rw-r--r--services/core/java/com/android/server/search/SearchManagerService.java146
-rw-r--r--services/core/java/com/android/server/search/Searchables.java47
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationScaler.java42
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java34
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java13
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java13
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd3
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt2
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderGetSession.java49
-rw-r--r--services/tests/InputMethodSystemServerTests/Android.bp3
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java44
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java67
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java15
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java91
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java121
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java144
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java57
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java381
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/search/SearchablesTest.java6
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java4
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java63
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java37
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java172
-rw-r--r--services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java54
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java18
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java45
-rw-r--r--telephony/java/android/service/euicc/EuiccService.java18
-rw-r--r--telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl1
187 files changed, 5254 insertions, 1425 deletions
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 8b55e071e715..558629537253 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -28,6 +28,7 @@ java_library {
static_libs: [
"modules-utils-fastxmlserializer",
+ "service-jobscheduler-alarm.flags-aconfig-java",
"service-jobscheduler-job.flags-aconfig-java",
],
diff --git a/apex/jobscheduler/service/aconfig/Android.bp b/apex/jobscheduler/service/aconfig/Android.bp
index 3ba7aa201d27..4db39dc1976b 100644
--- a/apex/jobscheduler/service/aconfig/Android.bp
+++ b/apex/jobscheduler/service/aconfig/Android.bp
@@ -27,3 +27,15 @@ java_aconfig_library {
aconfig_declarations: "service-job.flags-aconfig",
visibility: ["//frameworks/base:__subpackages__"],
}
+
+// Alarm
+aconfig_declarations {
+ name: "alarm_flags",
+ package: "com.android.server.alarm",
+ srcs: ["alarm.aconfig"],
+}
+
+java_aconfig_library {
+ name: "service-jobscheduler-alarm.flags-aconfig-java",
+ aconfig_declarations: "alarm_flags",
+}
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig
new file mode 100644
index 000000000000..3b9b4e70b310
--- /dev/null
+++ b/apex/jobscheduler/service/aconfig/alarm.aconfig
@@ -0,0 +1,11 @@
+package: "com.android.server.alarm"
+
+flag {
+ name: "use_frozen_state_to_drop_listener_alarms"
+ namespace: "backstage_power"
+ description: "Use frozen state callback to drop listener alarms for cached apps"
+ bug: "324470945"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 39de0af3c8d0..e728a2c55765 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.alarm;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
@@ -75,6 +76,7 @@ import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AlarmManager;
@@ -103,6 +105,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -145,6 +148,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.LocalLog;
@@ -293,6 +297,7 @@ public class AlarmManagerService extends SystemService {
private final Injector mInjector;
int mBroadcastRefCount = 0;
+ boolean mUseFrozenStateToDropListenerAlarms;
MetricsHelper mMetricsHelper;
PowerManager.WakeLock mWakeLock;
SparseIntArray mAlarmsPerUid = new SparseIntArray();
@@ -1856,15 +1861,47 @@ public class AlarmManagerService extends SystemService {
@Override
public void onStart() {
mInjector.init();
+ mHandler = new AlarmHandler();
+
mOptsWithFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
mOptsWithFgsForAlarmClock.setPendingIntentBackgroundActivityLaunchAllowed(false);
mOptsWithoutFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
mOptsTimeBroadcast.setPendingIntentBackgroundActivityLaunchAllowed(false);
mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
mBroadcastOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
+
mMetricsHelper = new MetricsHelper(getContext(), mLock);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
+ if (mUseFrozenStateToDropListenerAlarms) {
+ final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
+ final int size = frozenStates.length;
+ if (uids.length != size) {
+ Slog.wtf(TAG, "Got different length arrays in frozen state callback!"
+ + " uids.length: " + uids.length + " frozenStates.length: " + size);
+ // Cannot process received data in any meaningful way.
+ return;
+ }
+ final IntArray affectedUids = new IntArray();
+ for (int i = 0; i < size; i++) {
+ if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) {
+ continue;
+ }
+ if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED,
+ uids[i])) {
+ continue;
+ }
+ affectedUids.add(uids[i]);
+ }
+ if (affectedUids.size() > 0) {
+ removeExactListenerAlarms(affectedUids.toArray());
+ }
+ };
+ final ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback);
+ }
+
mListenerDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
@@ -1880,7 +1917,6 @@ public class AlarmManagerService extends SystemService {
};
synchronized (mLock) {
- mHandler = new AlarmHandler();
mConstants = new Constants(mHandler);
mAlarmStore = new LazyAlarmStore();
@@ -1960,6 +1996,21 @@ public class AlarmManagerService extends SystemService {
publishBinderService(Context.ALARM_SERVICE, mService);
}
+ private void removeExactListenerAlarms(int... whichUids) {
+ synchronized (mLock) {
+ removeAlarmsInternalLocked(a -> {
+ if (!ArrayUtils.contains(whichUids, a.uid) || a.listener == null
+ || a.windowLength != 0) {
+ return false;
+ }
+ Slog.w(TAG, "Alarm " + a.listenerTag + " being removed for "
+ + UserHandle.formatUid(a.uid) + ":" + a.packageName
+ + " because the app got frozen");
+ return true;
+ }, REMOVE_REASON_LISTENER_CACHED);
+ }
+ }
+
void refreshExactAlarmCandidates() {
final String[] candidates = mLocalPermissionManager.getAppOpPermissionPackages(
Manifest.permission.SCHEDULE_EXACT_ALARM);
@@ -3074,6 +3125,14 @@ public class AlarmManagerService extends SystemService {
mConstants.dump(pw);
pw.println();
+ pw.println("Feature Flags:");
+ pw.increaseIndent();
+ pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
+ mUseFrozenStateToDropListenerAlarms);
+ pw.decreaseIndent();
+ pw.println();
+ pw.println();
+
if (mConstants.USE_TARE_POLICY == EconomyManager.ENABLED_MODE_ON) {
pw.println("TARE details:");
pw.increaseIndent();
@@ -4959,18 +5018,7 @@ public class AlarmManagerService extends SystemService {
break;
case REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED:
uid = (Integer) msg.obj;
- synchronized (mLock) {
- removeAlarmsInternalLocked(a -> {
- if (a.uid != uid || a.listener == null || a.windowLength != 0) {
- return false;
- }
- // TODO (b/265195908): Change to .w once we have some data on breakages.
- Slog.wtf(TAG, "Alarm " + a.listenerTag + " being removed for "
- + UserHandle.formatUid(a.uid) + ":" + a.packageName
- + " because the app went into cached state");
- return true;
- }, REMOVE_REASON_LISTENER_CACHED);
- }
+ removeExactListenerAlarms(uid);
break;
default:
// nope, just ignore it
@@ -5322,6 +5370,10 @@ public class AlarmManagerService extends SystemService {
@Override
public void handleUidCachedChanged(int uid, boolean cached) {
+ if (mUseFrozenStateToDropListenerAlarms) {
+ // Use ActivityManager#UidFrozenStateChangedCallback instead.
+ return;
+ }
if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, uid)) {
return;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index e8eace1c51c8..3fd67db95e1b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -56279,7 +56279,7 @@ package android.view.inputmethod {
method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View);
method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String);
method @FlaggedApi("android.view.inputmethod.use_zero_jank_proxy") public void acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
- method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, int);
+ method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public void acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
method @Nullable public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfo();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index cc7d97a6ee50..2ff1f766ad8f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14204,7 +14204,8 @@ package android.telecom {
method @Deprecated public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
- method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle, boolean);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
diff --git a/core/java/Android.bp b/core/java/Android.bp
index eba500dd32b4..34b8a878b3d1 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -126,6 +126,7 @@ filegroup {
srcs: [
"android/os/IExternalVibrationController.aidl",
"android/os/IExternalVibratorService.aidl",
+ "android/os/ExternalVibrationScale.aidl",
],
}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index d139134c7c1a..fb1b17bd23d4 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -10,6 +10,7 @@ import android.annotation.SystemApi;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.CredentialOption;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
@@ -29,6 +30,7 @@ import android.os.PooledStringWriter;
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.autofill.FillRequest;
+import android.service.credentials.CredentialProviderService;
import android.text.InputType;
import android.text.Spanned;
import android.text.TextUtils;
@@ -2258,6 +2260,17 @@ public class AssistStructure implements Parcelable {
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
mNode.mGetCredentialRequest = request;
mNode.mGetCredentialCallback = callback;
+ for (CredentialOption option : request.getCredentialOptions()) {
+ ArrayList<AutofillId> ids = option.getCandidateQueryData()
+ .getParcelableArrayList(
+ CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId.class);
+ ids = ids != null ? ids : new ArrayList<>();
+ if (!ids.contains(getAutofillId())) {
+ ids.add(getAutofillId());
+ }
+ option.getCandidateQueryData()
+ .putParcelableArrayList(CredentialProviderService.EXTRA_AUTOFILL_ID, ids);
+ }
}
@Override
diff --git a/core/java/android/os/ExternalVibrationScale.aidl b/core/java/android/os/ExternalVibrationScale.aidl
new file mode 100644
index 000000000000..cf6f8ed52f7d
--- /dev/null
+++ b/core/java/android/os/ExternalVibrationScale.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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.os;
+
+/**
+ * ExternalVibrationScale holds the vibration scale level and adaptive haptics scale. These
+ * can be used to scale external vibrations.
+ *
+ * @hide
+ */
+parcelable ExternalVibrationScale {
+ @Backing(type="int")
+ enum ScaleLevel {
+ SCALE_MUTE = -100,
+ SCALE_VERY_LOW = -2,
+ SCALE_LOW = -1,
+ SCALE_NONE = 0,
+ SCALE_HIGH = 1,
+ SCALE_VERY_HIGH = 2
+ }
+
+ /**
+ * The scale level that will be applied to external vibrations.
+ */
+ ScaleLevel scaleLevel = ScaleLevel.SCALE_NONE;
+
+ /**
+ * The adaptive haptics scale that will be applied to external vibrations.
+ */
+ float adaptiveHapticsScale = 1f;
+}
diff --git a/core/java/android/os/IExternalVibratorService.aidl b/core/java/android/os/IExternalVibratorService.aidl
index 666171fcbcb3..a9df15a088bf 100644
--- a/core/java/android/os/IExternalVibratorService.aidl
+++ b/core/java/android/os/IExternalVibratorService.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.ExternalVibration;
+import android.os.ExternalVibrationScale;
/**
* The communication channel by which an external system that wants to control the system
@@ -32,29 +33,24 @@ import android.os.ExternalVibration;
* {@hide}
*/
interface IExternalVibratorService {
- const int SCALE_MUTE = -100;
- const int SCALE_VERY_LOW = -2;
- const int SCALE_LOW = -1;
- const int SCALE_NONE = 0;
- const int SCALE_HIGH = 1;
- const int SCALE_VERY_HIGH = 2;
-
/**
* A method called by the external system to start a vibration.
*
- * If this returns {@code SCALE_MUTE}, then the vibration should <em>not</em> play. If this
- * returns any other scale level, then any currently playing vibration controlled by the
- * requesting system must be muted and this vibration can begin playback.
+ * This returns an {@link ExternalVibrationScale} which includes the vibration scale level and
+ * the adaptive haptics scale.
+ *
+ * If the returned scale level is {@link ExternalVibrationScale.ScaleLevel#SCALE_MUTE}, then
+ * the vibration should <em>not</em> play. If it returns any other scale level, then
+ * any currently playing vibration controlled by the requesting system must be muted and this
+ * vibration can begin playback.
*
* Note that the IExternalVibratorService implementation will not call mute on any currently
* playing external vibrations in order to avoid re-entrancy with the system on the other side.
*
- * @param vibration An ExternalVibration
- *
- * @return {@code SCALE_MUTE} if the external vibration should not play, and any other scale
- * level if it should.
+ * @param vib The external vibration starting.
+ * @return {@link ExternalVibrationScale} including scale level and adaptive haptics scale.
*/
- int onExternalVibrationStart(in ExternalVibration vib);
+ ExternalVibrationScale onExternalVibrationStart(in ExternalVibration vib);
/**
* A method called by the external system when a vibration no longer wants to play.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ec4d5876070a..50adc40e719d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12481,6 +12481,24 @@ public final class Settings {
public static void setLocationProviderEnabled(ContentResolver cr,
String provider, boolean enabled) {
}
+
+ /**
+ * List of system components that support restore in a V-> U OS downgrade but do not have
+ * RestoreAnyVersion set to true. Value set before system restore.
+ * This setting is not B&Rd
+ * List is stored as a comma-separated string of package names e.g. "a,b,c"
+ * @hide
+ */
+ public static final String V_TO_U_RESTORE_ALLOWLIST = "v_to_u_restore_allowlist";
+
+ /**
+ * List of system components that have RestoreAnyVersion set to true but do not support
+ * restore in a V-> U OS downgrade. Value set before system restore.
+ * This setting is not B&Rd
+ * List is stored as a comma-separated string of package names e.g. "a,b,c"
+ * @hide
+ */
+ public static final String V_TO_U_RESTORE_DENYLIST = "v_to_u_restore_denylist";
}
/**
diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java
index e679f2998ca1..ca2e56d383e5 100644
--- a/core/java/android/view/BatchedInputEventReceiver.java
+++ b/core/java/android/view/BatchedInputEventReceiver.java
@@ -19,6 +19,7 @@ package android.view;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.Looper;
+import android.os.Trace;
/**
* Similar to {@link InputEventReceiver}, but batches events to vsync boundaries when possible.
@@ -42,6 +43,8 @@ public class BatchedInputEventReceiver extends InputEventReceiver {
super(inputChannel, looper);
mChoreographer = choreographer;
mBatchingEnabled = true;
+ traceBoolVariable("mBatchingEnabled", mBatchingEnabled);
+ traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
mHandler = new Handler(looper);
}
@@ -71,6 +74,7 @@ public class BatchedInputEventReceiver extends InputEventReceiver {
}
mBatchingEnabled = batchingEnabled;
+ traceBoolVariable("mBatchingEnabled", mBatchingEnabled);
mHandler.removeCallbacks(mConsumeBatchedInputEvents);
if (!batchingEnabled) {
unscheduleBatchedInput();
@@ -81,6 +85,7 @@ public class BatchedInputEventReceiver extends InputEventReceiver {
protected void doConsumeBatchedInput(long frameTimeNanos) {
if (mBatchedInputScheduled) {
mBatchedInputScheduled = false;
+ traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
if (consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) {
// If we consumed a batch here, we want to go ahead and schedule the
// consumption of batched input events on the next frame. Otherwise, we would
@@ -95,6 +100,7 @@ public class BatchedInputEventReceiver extends InputEventReceiver {
private void scheduleBatchedInput() {
if (!mBatchedInputScheduled) {
mBatchedInputScheduled = true;
+ traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
}
}
@@ -102,11 +108,18 @@ public class BatchedInputEventReceiver extends InputEventReceiver {
private void unscheduleBatchedInput() {
if (mBatchedInputScheduled) {
mBatchedInputScheduled = false;
+ traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
}
}
+ // @TODO(b/311142655): Delete this temporary tracing. It's only used here to debug a very
+ // specific issue.
+ private void traceBoolVariable(String name, boolean value) {
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, name, value ? 1 : 0);
+ }
+
private final class BatchedInputRunnable implements Runnable {
@Override
public void run() {
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 9c430cd4acb4..2cc05b0bc4b0 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -271,12 +271,29 @@ public abstract class InputEventReceiver {
return mInputChannel.getToken();
}
+ private String getShortDescription(InputEvent event) {
+ if (event instanceof MotionEvent motion) {
+ return "MotionEvent " + MotionEvent.actionToString(motion.getAction()) + " deviceId="
+ + motion.getDeviceId() + " source=0x"
+ + Integer.toHexString(motion.getSource()) + " historySize="
+ + motion.getHistorySize();
+ } else if (event instanceof KeyEvent key) {
+ return "KeyEvent " + KeyEvent.actionToString(key.getAction())
+ + " deviceId=" + key.getDeviceId();
+ } else {
+ Log.wtf(TAG, "Illegal InputEvent type: " + event);
+ return "InputEvent";
+ }
+ }
+
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void dispatchInputEvent(int seq, InputEvent event) {
+ Trace.traceBegin(Trace.TRACE_TAG_INPUT, "dispatchInputEvent " + getShortDescription(event));
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
+ Trace.traceEnd(Trace.TRACE_TAG_INPUT);
}
/**
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 021bbf7b9c9f..896b3f4bd5c3 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -196,8 +196,6 @@ public class TextureView extends View {
private Canvas mCanvas;
private int mSaveCount;
- @Surface.FrameRateCompatibility int mFrameRateCompatibility;
-
private final Object[] mNativeWindowLock = new Object[0];
// Set by native code, do not write!
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 028448ccf871..596c52dcfdf6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24,6 +24,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
@@ -85,6 +86,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.credentials.CredentialManager;
+import android.credentials.CredentialOption;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
@@ -129,6 +131,7 @@ import android.os.SystemClock;
import android.os.Trace;
import android.os.Vibrator;
import android.os.vibrator.Flags;
+import android.service.credentials.CredentialProviderService;
import android.sysprop.DisplayProperties;
import android.text.InputType;
import android.text.TextUtils;
@@ -2366,6 +2369,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
+ // Used to set frame rate compatibility.
+ @Surface.FrameRateCompatibility int mFrameRateCompatibility =
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
static {
EMPTY_STATE_SET = StateSet.get(0);
@@ -7033,6 +7039,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
Preconditions.checkNotNull(request, "request must not be null");
Preconditions.checkNotNull(callback, "request must not be null");
+
+ for (CredentialOption option : request.getCredentialOptions()) {
+ ArrayList<AutofillId> ids = option.getCandidateQueryData()
+ .getParcelableArrayList(
+ CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId.class);
+ ids = ids != null ? ids : new ArrayList<>();
+ if (!ids.contains(getAutofillId())) {
+ ids.add(getAutofillId());
+ }
+ option.getCandidateQueryData()
+ .putParcelableArrayList(CredentialProviderService.EXTRA_AUTOFILL_ID, ids);
+ }
mViewCredentialHandler = new ViewCredentialHandler(request, callback);
}
@@ -10875,8 +10893,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
structure.setAutofillId(new AutofillId(getAutofillId(),
AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId())));
}
- structure.setCredentialManagerRequest(getCredentialManagerRequest(),
- getCredentialManagerCallback());
+ if (getViewCredentialHandler() != null) {
+ structure.setCredentialManagerRequest(
+ getViewCredentialHandler().getRequest(),
+ getViewCredentialHandler().getCallback());
+ }
CharSequence cname = info.getClassName();
structure.setClassName(cname != null ? cname.toString() : null);
structure.setContentDescription(info.getContentDescription());
@@ -33524,7 +33545,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
}
} else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
+ mFrameRateCompatibility);
return;
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1e79786d7554..94260b223dd2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -30,6 +30,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -1027,6 +1028,13 @@ public final class ViewRootImpl implements ViewParent,
// as needed and can be useful for power saving.
// Should not enable the dVRR feature if the value is false.
private boolean mIsFrameRatePowerSavingsBalanced = true;
+ // Used to check if there is a conflict between different frame rate voting.
+ // Take 24 and 30 as an example, 24 is not a divisor of 30.
+ // We consider there is a conflict.
+ private boolean mIsFrameRateConflicted = false;
+ // Used to set frame rate compatibility.
+ @Surface.FrameRateCompatibility int mFrameRateCompatibility =
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
// time for touch boost period.
private static final int FRAME_RATE_TOUCH_BOOST_TIME = 3000;
// time for checking idle status periodically.
@@ -4110,6 +4118,7 @@ public final class ViewRootImpl implements ViewParent,
? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount;
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
mPreferredFrameRate = -1;
+ mIsFrameRateConflicted = false;
}
private void createSyncIfNeeded() {
@@ -6508,6 +6517,7 @@ public final class ViewRootImpl implements ViewParent,
break;
case MSG_FRAME_RATE_SETTING:
mPreferredFrameRate = 0;
+ mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
setPreferredFrameRate(mPreferredFrameRate);
break;
}
@@ -12286,9 +12296,15 @@ public final class ViewRootImpl implements ViewParent,
if (!shouldSetFrameRateCategory()) {
return;
}
+ int categoryFromConflictedFrameRates = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ if (mIsFrameRateConflicted) {
+ categoryFromConflictedFrameRates = mPreferredFrameRate > 60
+ ? FRAME_RATE_CATEGORY_HIGH : FRAME_RATE_CATEGORY_NORMAL;
+ }
int frameRateCategory = mIsTouchBoosting
- ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
+ ? FRAME_RATE_CATEGORY_HIGH_HINT
+ : Math.max(preferredFrameRateCategory, categoryFromConflictedFrameRates);
// FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
// For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
@@ -12338,7 +12354,7 @@ public final class ViewRootImpl implements ViewParent,
+ preferredFrameRate);
}
mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
- Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).applyAsyncUnsafe();
+ mFrameRateCompatibility).applyAsyncUnsafe();
mLastPreferredFrameRate = preferredFrameRate;
}
} catch (Exception e) {
@@ -12361,7 +12377,8 @@ public final class ViewRootImpl implements ViewParent,
private boolean shouldSetFrameRate() {
// use toolkitSetFrameRate flag to gate the change
- return mSurface.isValid() && mPreferredFrameRate > 0 && shouldEnableDvrr();
+ return mSurface.isValid() && mPreferredFrameRate > 0
+ && shouldEnableDvrr() && !mIsFrameRateConflicted;
}
private boolean shouldTouchBoost(int motionEventAction, int windowType) {
@@ -12404,29 +12421,45 @@ public final class ViewRootImpl implements ViewParent,
}
/**
- * Allow Views to vote for the preferred frame rate
+ * Allow Views to vote for the preferred frame rate and compatibility.
* When determining the preferred frame rate value,
* we follow this logic: If no preferred frame rate has been set yet,
* we assign the value of frameRate as the preferred frame rate.
- * If either the current or the new preferred frame rate exceeds 60 Hz,
- * we select the higher value between them.
- * However, if both values are 60 Hz or lower, we set the preferred frame rate
- * to 60 Hz to maintain optimal performance.
+ * IF there are multiple frame rates are voted:
+ * 1. There is a frame rate is a multiple of all other frame rates.
+ * We choose this frmae rate to be the one to be set.
+ * 2. There is no frame rate can be a multiple of others
+ * We set category to HIGH if the maximum frame rate is greater than 60.
+ * Otherwise, we set category to NORMAL.
+ *
+ * Use FRAME_RATE_COMPATIBILITY_GTE for velocity and FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+ * for TextureView video play and user requested frame rate.
*
* @param frameRate the preferred frame rate of a View
+ * @param frameRateCompatibility the preferred frame rate compatibility of a View
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
- public void votePreferredFrameRate(float frameRate) {
+ public void votePreferredFrameRate(float frameRate, int frameRateCompatibility) {
if (frameRate <= 0) {
return;
}
+ if (mPreferredFrameRate > 0 && mPreferredFrameRate % frameRate != 0
+ && frameRate % mPreferredFrameRate != 0) {
+ mIsFrameRateConflicted = true;
+ mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+ }
+ if (frameRate > mPreferredFrameRate) {
+ mFrameRateCompatibility = frameRateCompatibility;
+ }
mPreferredFrameRate = Math.max(mPreferredFrameRate, frameRate);
-
mHasInvalidation = true;
- mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
- mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
- FRAME_RATE_SETTING_REEVALUATE_TIME);
+
+ if (!mIsFrameRateConflicted) {
+ mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
+ FRAME_RATE_SETTING_REEVALUATE_TIME);
+ }
}
/**
@@ -12454,6 +12487,14 @@ public final class ViewRootImpl implements ViewParent,
}
/**
+ * Get the value of mFrameRateCompatibility
+ */
+ @VisibleForTesting
+ public int getFrameRateCompatibility() {
+ return mFrameRateCompatibility;
+ }
+
+ /**
* Get the value of mIsFrameRateBoosting
*/
@VisibleForTesting
@@ -12503,6 +12544,15 @@ public final class ViewRootImpl implements ViewParent,
}
/**
+ * Get the value of mIsFrameRateConflicted
+ * Can be used to checked if there is a conflict of frame rate votes.
+ */
+ @VisibleForTesting
+ public boolean isFrameRateConflicted() {
+ return mIsFrameRateConflicted;
+ }
+
+ /**
* Set the value of mIsFrameRatePowerSavingsBalanced
* Can be used to checked if toolkit dVRR feature is enabled.
*/
diff --git a/core/java/android/view/inputmethod/InputBinding.java b/core/java/android/view/inputmethod/InputBinding.java
index 2bfeb5abb395..fedee9de1372 100644
--- a/core/java/android/view/inputmethod/InputBinding.java
+++ b/core/java/android/view/inputmethod/InputBinding.java
@@ -19,11 +19,13 @@ package android.view.inputmethod;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
/**
* Information given to an {@link InputMethod} about a client connecting
* to it.
*/
+@RavenwoodKeepWholeClass
public final class InputBinding implements Parcelable {
static final String TAG = "InputBinding";
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 52384791f74b..3bce155049c8 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -462,8 +462,8 @@ public final class InputMethodManager {
* Flag indicating that views from the default home screen ({@link Intent#CATEGORY_HOME}) may
* act as a handwriting delegator for the delegate editor view. If set, views from the home
* screen package will be trusted for handwriting delegation, in addition to views in the {@code
- * delegatorPackageName} passed to {@link #acceptStylusHandwritingDelegation(View, String,
- * int)}.
+ * delegatorPackageName} passed to
+ * {@link #acceptStylusHandwritingDelegation(View, String, int, Executor, Consumer)} .
*/
@FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
public static final int HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED = 0x0001;
@@ -2896,6 +2896,8 @@ public final class InputMethodManager {
* @param delegateView delegate view capable of receiving input via {@link InputConnection}
* @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
* @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
+ * @param executor The executor to run the callback on.
+ * @param callback {@code true>} would be received if delegation was accepted.
* @return {@code true} if view belongs to allowed delegate package declared in {@link
* #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View, String)
@@ -2908,13 +2910,16 @@ public final class InputMethodManager {
// session to the delegate view.
// @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
// CursorAnchorInfo, String)
+ //
@FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
- public boolean acceptStylusHandwritingDelegation(
+ public void acceptStylusHandwritingDelegation(
@NonNull View delegateView, @NonNull String delegatorPackageName,
- @HandwritingDelegateFlags int flags) {
+ @HandwritingDelegateFlags int flags, @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Boolean> callback) {
Objects.requireNonNull(delegatorPackageName);
- return startStylusHandwritingInternal(delegateView, delegatorPackageName, flags);
+ startStylusHandwritingInternal(
+ delegateView, delegatorPackageName, flags, executor, callback);
}
/**
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 55986e7ada47..8d3920f8b1da 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -78,3 +78,11 @@ flag {
bug: "300979854"
is_fixed_read_only: true
}
+
+flag {
+ name: "predictive_back_ime"
+ namespace: "input_method"
+ description: "Predictive back animation for IMEs"
+ bug: "322836622"
+ is_fixed_read_only: true
+}
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index f1b93db3e731..07cbaadf0580 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "InputEventReceiver"
+#define ATRACE_TAG ATRACE_TAG_INPUT
//#define LOG_NDEBUG 0
@@ -46,6 +47,16 @@ static const char* toString(bool value) {
return value ? "true" : "false";
}
+/**
+ * Trace a bool variable, writing "1" if the value is "true" and "0" otherwise.
+ * TODO(b/311142655): delete this tracing. It's only useful for debugging very specific issues.
+ * @param var the name of the variable
+ * @param value the value of the variable
+ */
+static void traceBoolVariable(const char* var, bool value) {
+ ATRACE_INT(var, value ? 1 : 0);
+}
+
static struct {
jclass clazz;
@@ -130,6 +141,7 @@ NativeInputEventReceiver::NativeInputEventReceiver(
mMessageQueue(messageQueue),
mBatchedInputEventPending(false),
mFdEvents(0) {
+ traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending);
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName().c_str());
}
@@ -311,6 +323,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
if (consumeBatches) {
mBatchedInputEventPending = false;
+ traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending);
}
if (outConsumedBatch) {
*outConsumedBatch = false;
@@ -344,6 +357,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
}
mBatchedInputEventPending = true;
+ traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending);
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Dispatching batched input event pending notification.",
getInputChannelName().c_str());
@@ -355,6 +369,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching batched input events.");
mBatchedInputEventPending = false; // try again later
+ traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending);
}
}
return OK;
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 038c00e3823c..64cbe7f1b079 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -24,6 +24,8 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -705,17 +707,51 @@ public class ViewRootImplTest {
public void votePreferredFrameRate_voteFrameRate_aggregate() {
View view = new View(sContext);
attachViewToWindow(view);
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- viewRootImpl.votePreferredFrameRate(24);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_GTE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
- viewRootImpl.votePreferredFrameRate(30);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_GTE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(30, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 30, 0.1);
- viewRootImpl.votePreferredFrameRate(60);
+ // If there is a conflict, then set compatibility to
+ // FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ // Should be true since there is a conflict between 24 and 30.
+ assertEquals(viewRootImpl.isFrameRateConflicted(), true);
+ view.invalidate();
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 60, 0.1);
- viewRootImpl.votePreferredFrameRate(120);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_GTE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(120, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 120, 0.1);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ // Should be false since 60 is a divisor of 120.
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 120, 0.1);
+ // compatibility should be remained the same (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
+ // since the frame rate 60 is smaller than 120.
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ // Should be false since 60 is a divisor of 120.
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+
});
}
@@ -842,14 +878,26 @@ public class ViewRootImplTest {
sInstrumentation.runOnMainSync(() -> {
assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- viewRootImpl.votePreferredFrameRate(24);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
view.invalidate();
assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
});
Thread.sleep(delay);
assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 34be9b097e81..2606fb661e80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -114,6 +114,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
private boolean mOnBackStartDispatched = false;
+ private boolean mPointerPilfered = false;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -404,11 +405,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
void onPilferPointers() {
+ mPointerPilfered = true;
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
if (!shouldDispatchToAnimator() && mActiveCallback != null) {
mCurrentTracker.updateStartLocation();
- tryDispatchAppOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
+ tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
}
}
@@ -511,7 +513,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
// App is handling back animation. Cancel system animation latency tracking.
cancelLatencyTracking();
- tryDispatchAppOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
+ tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
}
}
@@ -555,14 +557,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
&& mBackNavigationInfo.isPrepareRemoteAnimation();
}
- private void tryDispatchAppOnBackStarted(
+ private void tryDispatchOnBackStarted(
IOnBackInvokedCallback callback,
BackMotionEvent backEvent) {
- if (mOnBackStartDispatched && callback != null) {
+ if (mOnBackStartDispatched || callback == null || !mPointerPilfered) {
return;
}
dispatchOnBackStarted(callback, backEvent);
- mOnBackStartDispatched = true;
}
private void dispatchOnBackStarted(
@@ -573,6 +574,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
try {
callback.onBackStarted(backEvent);
+ mOnBackStartDispatched = true;
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
@@ -872,6 +874,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mActiveCallback = null;
mShouldStartOnNextMoveEvent = false;
mOnBackStartDispatched = false;
+ mPointerPilfered = false;
mShellBackAnimationRegistry.resetDefaultCrossActivity();
cancelLatencyTracking();
if (mBackNavigationInfo != null) {
@@ -957,15 +960,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mCurrentTracker.updateStartLocation();
BackMotionEvent startEvent =
mCurrentTracker.createStartEvent(apps[0]);
- // {@code mActiveCallback} is the callback from
- // the BackAnimationRunners and not a real app-side
- // callback. We also dispatch to the app-side callback
- // (which should be a system callback with PRIORITY_SYSTEM)
- // to keep consistent with app registered callbacks.
dispatchOnBackStarted(mActiveCallback, startEvent);
- tryDispatchAppOnBackStarted(
- mBackNavigationInfo.getOnBackInvokedCallback(),
- startEvent);
}
// Dispatch the first progress after animation start for
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index e0f0556d03f0..369258e15454 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -683,6 +683,17 @@ public class BubbleController implements ConfigurationChangeListener,
mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
}
+ /** Called when sensitive notification state has changed */
+ public void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ if (mStackView != null) {
+ mStackView.onSensitiveNotificationProtectionStateChanged(
+ sensitiveNotificationProtectionActive);
+ ProtoLog.d(WM_SHELL_BUBBLES, "onSensitiveNotificationProtectionStateChanged=%b",
+ sensitiveNotificationProtectionActive);
+ }
+ }
+
/** Whether bubbles are showing in the bubble bar. */
public boolean isShowingAsBubbleBar() {
return canShowAsBubbleBar() && mBubbleStateListener != null;
@@ -2583,6 +2594,14 @@ public class BubbleController implements ConfigurationChangeListener,
mMainExecutor.execute(
() -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
}
+
+ @Override
+ public void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
+ sensitiveNotificationProtectionActive));
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index b23fd5269eae..e7da034a7422 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -291,6 +291,11 @@ public class BubbleStackView extends FrameLayout
*/
private boolean mRemovingLastBubbleWhileExpanded = false;
+ /**
+ * Whether sensitive notification protection should disable flyout
+ */
+ private boolean mSensitiveNotificationProtectionActive = false;
+
/** Animator for animating the expanded view's alpha (including the TaskView inside it). */
private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
@@ -2199,6 +2204,11 @@ public class BubbleStackView extends FrameLayout
}
}
+ void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ mSensitiveNotificationProtectionActive = sensitiveNotificationProtectionActive;
+ }
+
/**
* Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or
* not.
@@ -2842,6 +2852,7 @@ public class BubbleStackView extends FrameLayout
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
+ || mSensitiveNotificationProtectionActive
|| mBubbleToExpandAfterFlyoutCollapse != null
|| bubbleView == null) {
if (bubbleView != null && mFlyout.getVisibility() != VISIBLE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 28af0ca6ac6c..26077cf7057b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -286,6 +286,16 @@ public interface Bubbles {
void onUserRemoved(int removedUserId);
/**
+ * Called when the Sensitive notification protection state has changed, such as when media
+ * projection starts and stops.
+ *
+ * @param sensitiveNotificationProtectionActive {@code true} if notifications should be
+ * protected
+ */
+ void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive);
+
+ /**
* A listener to be notified of bubble state changes, used by launcher to render bubbles in
* its process.
*/
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e4f3e2defb25..abd84de7da3c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -155,6 +155,7 @@ cc_defaults {
host: {
static_libs: [
"libandroidfw",
+ "libhostgraphics",
"libutils",
],
},
@@ -501,6 +502,17 @@ cc_library_headers {
],
header_libs: ["android_graphics_jni_headers"],
export_header_lib_headers: ["android_graphics_jni_headers"],
+ target: {
+ android: {
+ export_include_dirs: ["platform/android"],
+ },
+ host: {
+ export_include_dirs: ["platform/host"],
+ },
+ windows: {
+ enabled: true,
+ },
+ },
}
cc_defaults {
@@ -538,6 +550,7 @@ cc_defaults {
"utils/Blur.cpp",
"utils/Color.cpp",
"utils/LinearAllocator.cpp",
+ "utils/StringUtils.cpp",
"utils/TypefaceUtils.cpp",
"utils/VectorDrawableUtils.cpp",
"AnimationContext.cpp",
@@ -552,6 +565,7 @@ cc_defaults {
"Mesh.cpp",
"MemoryPolicy.cpp",
"PathParser.cpp",
+ "ProfileData.cpp",
"Properties.cpp",
"PropertyValuesAnimatorSet.cpp",
"PropertyValuesHolder.cpp",
@@ -569,12 +583,13 @@ cc_defaults {
export_proto_headers: true,
},
+ header_libs: ["libandroid_headers_private"],
+
target: {
android: {
- header_libs: [
- "libandroid_headers_private",
- "libtonemap_headers",
- ],
+ header_libs: ["libtonemap_headers"],
+
+ local_include_dirs: ["platform/android"],
srcs: [
"hwui/AnimatedImageThread.cpp",
@@ -605,7 +620,6 @@ cc_defaults {
"thread/CommonPool.cpp",
"utils/GLUtils.cpp",
"utils/NdkUtils.cpp",
- "utils/StringUtils.cpp",
"AutoBackendTextureRelease.cpp",
"DeferredLayerUpdater.cpp",
"DeviceInfo.cpp",
@@ -617,7 +631,6 @@ cc_defaults {
"FrameMetricsReporter.cpp",
"Layer.cpp",
"LayerUpdateQueue.cpp",
- "ProfileData.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"TreeInfo.cpp",
@@ -628,6 +641,21 @@ cc_defaults {
// Allow implicit fallthroughs in HardwareBitmapUploader.cpp until they are fixed.
cflags: ["-Wno-implicit-fallthrough"],
},
+ host: {
+ header_libs: ["libnativebase_headers"],
+
+ local_include_dirs: ["platform/host"],
+
+ srcs: [
+ "platform/host/renderthread/CacheManager.cpp",
+ "platform/host/renderthread/RenderThread.cpp",
+ "platform/host/ProfileDataContainer.cpp",
+ "platform/host/Readback.cpp",
+ "platform/host/WebViewFunctorManager.cpp",
+ ],
+
+ cflags: ["-Wno-unused-private-field"],
+ },
},
}
@@ -663,6 +691,7 @@ cc_defaults {
header_libs: ["libandroid_headers_private"],
target: {
android: {
+ local_include_dirs: ["platform/android"],
shared_libs: [
"libgui",
"libui",
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 3d0ca0a10851..7be9541f6b99 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -110,6 +110,7 @@ void ProfileData::mergeWith(const ProfileData& other) {
}
void ProfileData::dump(int fd) const {
+#ifdef __ANDROID__
dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime);
dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount);
dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount,
@@ -138,6 +139,7 @@ void ProfileData::dump(int fd) const {
dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount);
});
dprintf(fd, "\n");
+#endif
}
uint32_t ProfileData::findPercentile(int percentile) const {
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/platform/android/thread/ThreadBase.h
index 0289d3fd2ef7..2f3581f8b355 100644
--- a/libs/hwui/thread/ThreadBase.h
+++ b/libs/hwui/platform/android/thread/ThreadBase.h
@@ -17,14 +17,14 @@
#ifndef HWUI_THREADBASE_H
#define HWUI_THREADBASE_H
-#include "WorkQueue.h"
-#include "utils/Macros.h"
-
#include <utils/Looper.h>
#include <utils/Thread.h>
#include <algorithm>
+#include "thread/WorkQueue.h"
+#include "utils/Macros.h"
+
namespace android::uirenderer {
class ThreadBase : public Thread {
diff --git a/libs/hwui/platform/host/ProfileDataContainer.cpp b/libs/hwui/platform/host/ProfileDataContainer.cpp
new file mode 100644
index 000000000000..9ed1b02a8082
--- /dev/null
+++ b/libs/hwui/platform/host/ProfileDataContainer.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProfileDataContainer.h"
+
+#include <log/log.h>
+
+namespace android {
+namespace uirenderer {
+
+void ProfileDataContainer::freeData() REQUIRES(mJankDataMutex) {
+ delete mData;
+ mIsMapped = false;
+ mData = nullptr;
+}
+
+void ProfileDataContainer::rotateStorage() {
+ std::lock_guard lock(mJankDataMutex);
+ mData->reset();
+}
+
+void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
+ ALOGE("Ashmem is not supported for non-Android configurations");
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/Readback.cpp b/libs/hwui/platform/host/Readback.cpp
new file mode 100644
index 000000000000..b024ec02efc3
--- /dev/null
+++ b/libs/hwui/platform/host/Readback.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#include "Readback.h"
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+
+void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request) {
+}
+
+CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect,
+ SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
+ SkBitmap* bitmap) {
+ return false;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/WebViewFunctorManager.cpp b/libs/hwui/platform/host/WebViewFunctorManager.cpp
new file mode 100644
index 000000000000..1d16655bf73c
--- /dev/null
+++ b/libs/hwui/platform/host/WebViewFunctorManager.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#include "WebViewFunctorManager.h"
+
+namespace android::uirenderer {
+
+WebViewFunctor::WebViewFunctor(void* data, const WebViewFunctorCallbacks& callbacks,
+ RenderMode functorMode)
+ : mData(data) {}
+
+WebViewFunctor::~WebViewFunctor() {}
+
+void WebViewFunctor::sync(const WebViewSyncData& syncData) const {}
+
+void WebViewFunctor::onRemovedFromTree() {}
+
+bool WebViewFunctor::prepareRootSurfaceControl() {
+ return true;
+}
+
+void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) {}
+
+void WebViewFunctor::initVk(const VkFunctorInitParams& params) {}
+
+void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) {}
+
+void WebViewFunctor::postDrawVk() {}
+
+void WebViewFunctor::destroyContext() {}
+
+void WebViewFunctor::removeOverlays() {}
+
+ASurfaceControl* WebViewFunctor::getSurfaceControl() {
+ return mSurfaceControl;
+}
+
+void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {}
+
+void WebViewFunctor::reparentSurfaceControl(ASurfaceControl* parent) {}
+
+WebViewFunctorManager& WebViewFunctorManager::instance() {
+ static WebViewFunctorManager sInstance;
+ return sInstance;
+}
+
+int WebViewFunctorManager::createFunctor(void* data, const WebViewFunctorCallbacks& callbacks,
+ RenderMode functorMode) {
+ return 0;
+}
+
+void WebViewFunctorManager::releaseFunctor(int functor) {}
+
+void WebViewFunctorManager::onContextDestroyed() {}
+
+void WebViewFunctorManager::destroyFunctor(int functor) {}
+
+sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) {
+ return nullptr;
+}
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/platform/host/renderthread/CacheManager.cpp b/libs/hwui/platform/host/renderthread/CacheManager.cpp
new file mode 100644
index 000000000000..b03f400cd7d6
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/CacheManager.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#include "renderthread/CacheManager.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+CacheManager::CacheManager(RenderThread& thread)
+ : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) {}
+
+void CacheManager::setupCacheLimits() {}
+
+void CacheManager::destroy() {}
+
+void CacheManager::trimMemory(TrimLevel mode) {}
+
+void CacheManager::trimCaches(CacheTrimLevel mode) {}
+
+void CacheManager::trimStaleResources() {}
+
+void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {}
+
+void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {}
+
+void CacheManager::onFrameCompleted() {}
+
+void CacheManager::onThreadIdle() {}
+
+void CacheManager::scheduleDestroyContext() {}
+
+void CacheManager::cancelDestroyContext() {}
+
+bool CacheManager::areAllContextsStopped() {
+ return false;
+}
+
+void CacheManager::checkUiHidden() {}
+
+void CacheManager::registerCanvasContext(CanvasContext* context) {}
+
+void CacheManager::unregisterCanvasContext(CanvasContext* context) {}
+
+void CacheManager::onContextStopped(CanvasContext* context) {}
+
+void CacheManager::notifyNextFrameSize(int width, int height) {}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/renderthread/RenderThread.cpp b/libs/hwui/platform/host/renderthread/RenderThread.cpp
new file mode 100644
index 000000000000..6f08b5979772
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/RenderThread.cpp
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+#include "renderthread/RenderThread.h"
+
+#include "Readback.h"
+#include "renderthread/VulkanManager.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+static bool gHasRenderThreadInstance = false;
+static JVMAttachHook gOnStartHook = nullptr;
+
+ASurfaceControlFunctions::ASurfaceControlFunctions() {}
+
+bool RenderThread::hasInstance() {
+ return gHasRenderThreadInstance;
+}
+
+void RenderThread::setOnStartHook(JVMAttachHook onStartHook) {
+ LOG_ALWAYS_FATAL_IF(hasInstance(), "can't set an onStartHook after we've started...");
+ gOnStartHook = onStartHook;
+}
+
+JVMAttachHook RenderThread::getOnStartHook() {
+ return gOnStartHook;
+}
+
+RenderThread& RenderThread::getInstance() {
+ [[clang::no_destroy]] static sp<RenderThread> sInstance = []() {
+ sp<RenderThread> thread = sp<RenderThread>::make();
+ thread->start("RenderThread");
+ return thread;
+ }();
+ gHasRenderThreadInstance = true;
+ return *sInstance;
+}
+
+RenderThread::RenderThread()
+ : ThreadBase()
+ , mVsyncSource(nullptr)
+ , mVsyncRequested(false)
+ , mFrameCallbackTaskPending(false)
+ , mRenderState(nullptr)
+ , mEglManager(nullptr)
+ , mFunctorManager(WebViewFunctorManager::instance())
+ , mGlobalProfileData(mJankDataMutex) {
+ Properties::load();
+}
+
+RenderThread::~RenderThread() {}
+
+void RenderThread::initThreadLocals() {
+ mCacheManager = new CacheManager(*this);
+}
+
+void RenderThread::requireGlContext() {}
+
+void RenderThread::requireVkContext() {}
+
+void RenderThread::initGrContextOptions(GrContextOptions& options) {}
+
+void RenderThread::destroyRenderingContext() {}
+
+VulkanManager& RenderThread::vulkanManager() {
+ return *mVkManager;
+}
+
+void RenderThread::dumpGraphicsMemory(int fd, bool includeProfileData) {}
+
+void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {}
+
+Readback& RenderThread::readback() {
+ if (!mReadback) {
+ mReadback = new Readback(*this);
+ }
+
+ return *mReadback;
+}
+
+void RenderThread::setGrContext(sk_sp<GrDirectContext> context) {}
+
+sk_sp<GrDirectContext> RenderThread::requireGrContext() {
+ return mGrContext;
+}
+
+bool RenderThread::threadLoop() {
+ if (gOnStartHook) {
+ gOnStartHook("RenderThread");
+ }
+ initThreadLocals();
+
+ while (true) {
+ waitForWork();
+ processQueue();
+ mCacheManager->onThreadIdle();
+ }
+
+ return false;
+}
+
+void RenderThread::postFrameCallback(IFrameCallback* callback) {}
+
+bool RenderThread::removeFrameCallback(IFrameCallback* callback) {
+ return false;
+}
+
+void RenderThread::pushBackFrameCallback(IFrameCallback* callback) {}
+
+sk_sp<Bitmap> RenderThread::allocateHardwareBitmap(SkBitmap& skBitmap) {
+ return nullptr;
+}
+
+bool RenderThread::isCurrent() {
+ return true;
+}
+
+void RenderThread::preload() {}
+
+void RenderThread::trimMemory(TrimLevel level) {}
+
+void RenderThread::trimCaches(CacheTrimLevel level) {}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/thread/ThreadBase.h b/libs/hwui/platform/host/thread/ThreadBase.h
new file mode 100644
index 000000000000..d709430cc9b6
--- /dev/null
+++ b/libs/hwui/platform/host/thread/ThreadBase.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+#ifndef HWUI_THREADBASE_H
+#define HWUI_THREADBASE_H
+
+#include <utils/Thread.h>
+
+#include <algorithm>
+
+#include "thread/WorkQueue.h"
+#include "utils/Macros.h"
+
+namespace android::uirenderer {
+
+class ThreadBase : public Thread {
+ PREVENT_COPY_AND_ASSIGN(ThreadBase);
+
+public:
+ ThreadBase() : Thread(false), mQueue([this]() { mCondition.notify_all(); }, mLock) {}
+
+ WorkQueue& queue() { return mQueue; }
+
+ void requestExit() { Thread::requestExit(); }
+
+ void start(const char* name = "ThreadBase") { Thread::run(name); }
+
+ void join() { Thread::join(); }
+
+ bool isRunning() const { return Thread::isRunning(); }
+
+protected:
+ void waitForWork() {
+ std::unique_lock lock{mLock};
+ nsecs_t nextWakeup = mQueue.nextWakeup(lock);
+ std::chrono::nanoseconds duration = std::chrono::nanoseconds::max();
+ if (nextWakeup < std::numeric_limits<nsecs_t>::max()) {
+ int timeout = nextWakeup - WorkQueue::clock::now();
+ if (timeout < 0) timeout = 0;
+ duration = std::chrono::nanoseconds(timeout);
+ }
+ mCondition.wait_for(lock, duration);
+ }
+
+ void processQueue() { mQueue.process(); }
+
+ virtual bool threadLoop() override {
+ while (!exitPending()) {
+ waitForWork();
+ processQueue();
+ }
+ return false;
+ }
+
+private:
+ WorkQueue mQueue;
+ std::mutex mLock;
+ std::condition_variable mCondition;
+};
+
+} // namespace android::uirenderer
+
+#endif // HWUI_THREADBASE_H
diff --git a/libs/hwui/private/hwui/WebViewFunctor.h b/libs/hwui/private/hwui/WebViewFunctor.h
index 22ae59e5137b..493c943079ab 100644
--- a/libs/hwui/private/hwui/WebViewFunctor.h
+++ b/libs/hwui/private/hwui/WebViewFunctor.h
@@ -17,15 +17,7 @@
#ifndef FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H
#define FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H
-#ifdef __ANDROID__ // Layoutlib does not support surface control
#include <android/surface_control.h>
-#else
-// To avoid ifdefs around overlay implementation all over the place we typedef these to void *. They
-// won't be used.
-typedef void* ASurfaceControl;
-typedef void* ASurfaceTransaction;
-#endif
-
#include <cutils/compiler.h>
#include <private/hwui/DrawGlInfo.h>
#include <private/hwui/DrawVkInfo.h>
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 623ee4e6c27e..a024aeb285f9 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -17,11 +17,12 @@
#include "RenderThread.h"
#include <GrContextOptions.h>
-#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <android-base/properties.h>
#include <dlfcn.h>
#include <gl/GrGLInterface.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
+#include <private/android/choreographer.h>
#include <sys/resource.h>
#include <ui/FatVector.h>
#include <utils/Condition.h>
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 79e57de9d66f..045d26f1d329 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -20,10 +20,7 @@
#include <GrDirectContext.h>
#include <SkBitmap.h>
#include <cutils/compiler.h>
-#include <private/android/choreographer.h>
#include <surface_control_private.h>
-#include <thread/ThreadBase.h>
-#include <utils/Looper.h>
#include <utils/Thread.h>
#include <memory>
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 5242a7d32930..c81b95b7c81b 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -176,11 +176,25 @@ public final class ApduServiceInfo implements Parcelable {
List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) {
+ this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+ requiresUnlock, requiresScreenOn, bannerResource, uid,
+ settingsActivityName, offHost, staticOffHost, isEnabled,
+ new HashMap<String, Boolean>());
+ }
+
+ /**
+ * @hide
+ */
+ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+ List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
+ boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
+ String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled,
+ HashMap<String, Boolean> autoTransact) {
this.mService = info;
this.mDescription = description;
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
- this.mAutoTransact = new HashMap<String, Boolean>();
+ this.mAutoTransact = autoTransact;
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
this.mOnHost = onHost;
@@ -196,7 +210,6 @@ public final class ApduServiceInfo implements Parcelable {
this.mUid = uid;
this.mSettingsActivityName = settingsActivityName;
this.mCategoryOtherServiceEnabled = isEnabled;
-
}
/**
@@ -857,6 +870,8 @@ public final class ApduServiceInfo implements Parcelable {
dest.writeString(mSettingsActivityName);
dest.writeInt(mCategoryOtherServiceEnabled ? 1 : 0);
+ dest.writeInt(mAutoTransact.size());
+ dest.writeMap(mAutoTransact);
};
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
@@ -885,10 +900,15 @@ public final class ApduServiceInfo implements Parcelable {
int uid = source.readInt();
String settingsActivityName = source.readString();
boolean isEnabled = source.readInt() != 0;
+ int autoTransactSize = source.readInt();
+ HashMap<String, Boolean> autoTransact =
+ new HashMap<String, Boolean>(autoTransactSize);
+ source.readMap(autoTransact, getClass().getClassLoader(),
+ String.class, Boolean.class);
return new ApduServiceInfo(info, onHost, description, staticAidGroups,
dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
settingsActivityName, offHostName, staticOffHostName,
- isEnabled);
+ isEnabled, autoTransact);
}
@Override
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 25608fedc976..eef75c7b707b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -40,6 +40,7 @@ import android.service.autofill.Field
import android.service.autofill.FillCallback
import android.service.autofill.FillRequest
import android.service.autofill.FillResponse
+import android.service.autofill.Flags
import android.service.autofill.InlinePresentation
import android.service.autofill.Presentations
import android.service.autofill.SaveCallback
@@ -479,18 +480,28 @@ class CredentialAutofillService : AutofillService() {
val autofillIdToCredentialEntries:
MutableMap<AutofillId, ArrayList<Entry>> = mutableMapOf()
credentialEntryList.forEach entryLoop@{ credentialEntry ->
- val autofillId: AutofillId? = credentialEntry
- .frameworkExtrasIntent
- ?.getParcelableExtra(
- CredentialProviderService.EXTRA_AUTOFILL_ID,
- AutofillId::class.java)
- if (autofillId == null) {
- Log.e(TAG, "AutofillId is missing from credential entry. Credential" +
- " Integration might be disabled.")
- return@entryLoop
- }
- autofillIdToCredentialEntries.getOrPut(autofillId) { ArrayList() }
- .add(credentialEntry)
+ val intent = credentialEntry.frameworkExtrasIntent
+ intent?.getParcelableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
+ android.service.credentials.GetCredentialRequest::class.java)
+ ?.credentialOptions
+ ?.forEach { credentialOption ->
+ credentialOption.candidateQueryData.getParcelableArrayList(
+ CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId::class.java)
+ ?.forEach { autofillId ->
+ intent.putExtra(
+ CredentialProviderService.EXTRA_AUTOFILL_ID,
+ autofillId)
+ val entry = Entry(
+ credentialEntry.key,
+ credentialEntry.subkey,
+ credentialEntry.slice,
+ intent)
+ autofillIdToCredentialEntries
+ .getOrPut(autofillId) { ArrayList() }
+ .add(entry)
+ }
+ }
}
return autofillIdToCredentialEntries
}
@@ -573,23 +584,31 @@ class CredentialAutofillService : AutofillService() {
cmRequests: MutableList<CredentialOption>,
responseClientState: Bundle
) {
+ val traversedViewNodes: MutableSet<AutofillId> = mutableSetOf()
+ val credentialOptionsFromHints: MutableMap<String, CredentialOption> = mutableMapOf()
val windowNodes: List<AssistStructure.WindowNode> =
structure.run {
(0 until windowNodeCount).map { getWindowNodeAt(it) }
}
windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
- traverseNodeForRequest(windowNode.rootViewNode, cmRequests, responseClientState)
+ traverseNodeForRequest(
+ windowNode.rootViewNode, cmRequests, responseClientState, traversedViewNodes,
+ credentialOptionsFromHints)
}
}
private fun traverseNodeForRequest(
viewNode: AssistStructure.ViewNode,
cmRequests: MutableList<CredentialOption>,
- responseClientState: Bundle
+ responseClientState: Bundle,
+ traversedViewNodes: MutableSet<AutofillId>,
+ credentialOptionsFromHints: MutableMap<String, CredentialOption>
) {
viewNode.autofillId?.let {
- cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState))
+ cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState,
+ traversedViewNodes, credentialOptionsFromHints))
+ traversedViewNodes.add(it)
}
val children: List<AssistStructure.ViewNode> =
@@ -598,26 +617,37 @@ class CredentialAutofillService : AutofillService() {
}
children.forEach { childNode: AssistStructure.ViewNode ->
- traverseNodeForRequest(childNode, cmRequests, responseClientState)
+ traverseNodeForRequest(childNode, cmRequests, responseClientState, traversedViewNodes,
+ credentialOptionsFromHints)
}
}
private fun getCredentialOptionsFromViewNode(
viewNode: AssistStructure.ViewNode,
autofillId: AutofillId,
- responseClientState: Bundle
+ responseClientState: Bundle,
+ traversedViewNodes: MutableSet<AutofillId>,
+ credentialOptionsFromHints: MutableMap<String, CredentialOption>
): MutableList<CredentialOption> {
- if (viewNode.credentialManagerRequest != null) {
- val options = viewNode.credentialManagerRequest?.getCredentialOptions()
- if (options != null) {
- for (option in options) {
- option.candidateQueryData.putParcelable(
- CredentialProviderService.EXTRA_AUTOFILL_ID, autofillId
- )
- }
- return options
+ val credentialOptions: MutableList<CredentialOption> = mutableListOf()
+ if (Flags.autofillCredmanDevIntegration() && viewNode.credentialManagerRequest != null) {
+ viewNode.credentialManagerRequest
+ ?.getCredentialOptions()
+ ?.forEach { credentialOption ->
+ credentialOption.candidateQueryData
+ .getParcelableArrayList(
+ CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId::class.java)
+ ?.let { associatedAutofillIds ->
+ // Check whether any of the associated autofill ids have already been
+ // traversed. If so, skip, to dedupe on duplicate credential options.
+ if ((traversedViewNodes intersect associatedAutofillIds.toSet())
+ .isEmpty()) {
+ credentialOptions.add(credentialOption)
+ }
+ }
}
}
+ // TODO(b/325502552): clean up cred option logic in autofill hint
val credentialHints: MutableList<String> = mutableListOf()
if (viewNode.autofillHints != null) {
@@ -631,10 +661,10 @@ class CredentialAutofillService : AutofillService() {
}
}
- val credentialOptions: MutableList<CredentialOption> = mutableListOf()
for (credentialHint in credentialHints) {
try {
- convertJsonToCredentialOption(credentialHint, autofillId)
+ convertJsonToCredentialOption(
+ credentialHint, autofillId, credentialOptionsFromHints)
.let { credentialOptions.addAll(it) }
} catch (e: JSONException) {
Log.i(TAG, "Exception while parsing response: " + e.message)
@@ -643,10 +673,11 @@ class CredentialAutofillService : AutofillService() {
return credentialOptions
}
- private fun convertJsonToCredentialOption(jsonString: String, autofillId: AutofillId):
- List<CredentialOption> {
- // TODO(b/302000646) Move this logic to jetpack so that is consistent
- // with building the json
+ private fun convertJsonToCredentialOption(
+ jsonString: String,
+ autofillId: AutofillId,
+ credentialOptionsFromHints: MutableMap<String, CredentialOption>
+ ): List<CredentialOption> {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
val json = JSONObject(jsonString)
@@ -654,16 +685,34 @@ class CredentialAutofillService : AutofillService() {
val options = jsonGet.getJSONArray(CRED_OPTIONS_KEY)
for (i in 0 until options.length()) {
val option = options.getJSONObject(i)
- val candidateBundle = convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY))
- candidateBundle.putParcelable(
+ val optionString = option.toString()
+ credentialOptionsFromHints[optionString]
+ ?.let { credentialOption ->
+ // if the current credential option was seen before, add the current
+ // viewNode to the credential option, but do not add it to the option list
+ // again. This will result in the same result as deduping based on
+ // traversed viewNode.
+ credentialOption.candidateQueryData.getParcelableArrayList(
+ CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId::class.java)
+ ?.let {
+ it.add(autofillId)
+ credentialOption.candidateQueryData.putParcelableArrayList(
+ CredentialProviderService.EXTRA_AUTOFILL_ID, it)
+ }
+ } ?: run {
+ val candidateBundle = convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY))
+ candidateBundle.putParcelableArrayList(
CredentialProviderService.EXTRA_AUTOFILL_ID,
- autofillId)
- credentialOptions.add(CredentialOption(
+ arrayListOf(autofillId))
+ val credentialOption = CredentialOption(
option.getString(TYPE_KEY),
convertJsonToBundle(option.getJSONObject(REQUEST_DATA_KEY)),
candidateBundle,
option.getBoolean(SYS_PROVIDER_REQ_KEY),
- ))
+ )
+ credentialOptions.add(credentialOption)
+ credentialOptionsFromHints[optionString] = credentialOption
+ }
}
return credentialOptions
}
diff --git a/packages/PrintRecommendationService/res/values/strings.xml b/packages/PrintRecommendationService/res/values/strings.xml
index 2bab1b65529b..b6c45b7a23c8 100644
--- a/packages/PrintRecommendationService/res/values/strings.xml
+++ b/packages/PrintRecommendationService/res/values/strings.xml
@@ -18,7 +18,6 @@
-->
<resources>
- <string name="plugin_vendor_google_cloud_print">Cloud Print</string>
<string name="plugin_vendor_hp">HP</string>
<string name="plugin_vendor_lexmark">Lexmark</string>
<string name="plugin_vendor_brother">Brother</string>
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java
index 5a756fe50209..4ec88830386b 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java
@@ -23,7 +23,6 @@ import android.printservice.recommendation.RecommendationInfo;
import android.printservice.recommendation.RecommendationService;
import android.util.Log;
-import com.android.printservice.recommendation.plugin.google.CloudPrintPlugin;
import com.android.printservice.recommendation.plugin.hp.HPRecommendationPlugin;
import com.android.printservice.recommendation.plugin.mdnsFilter.MDNSFilterPlugin;
import com.android.printservice.recommendation.plugin.mdnsFilter.VendorConfig;
@@ -77,14 +76,6 @@ public class RecommendationServiceImpl extends RecommendationService
}
try {
- mPlugins.add(new RemotePrintServicePlugin(new CloudPrintPlugin(this), this,
- true));
- } catch (Exception e) {
- Log.e(LOG_TAG, "Could not initiate "
- + getString(R.string.plugin_vendor_google_cloud_print) + " plugin", e);
- }
-
- try {
mPlugins.add(new RemotePrintServicePlugin(new HPRecommendationPlugin(this), this,
false));
} catch (Exception e) {
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
deleted file mode 100644
index 3029d10d4cf3..000000000000
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2017 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.printservice.recommendation.plugin.google;
-
-import static com.android.printservice.recommendation.util.MDNSUtils.ATTRIBUTE_TY;
-
-import android.content.Context;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
-
-import com.android.printservice.recommendation.PrintServicePlugin;
-import com.android.printservice.recommendation.R;
-import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.nio.charset.StandardCharsets;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Plugin detecting <a href="https://developers.google.com/cloud-print/docs/privet">Google Cloud
- * Print</a> printers.
- */
-public class CloudPrintPlugin implements PrintServicePlugin {
- private static final String LOG_TAG = CloudPrintPlugin.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private static final String ATTRIBUTE_TXTVERS = "txtvers";
- private static final String ATTRIBUTE_URL = "url";
- private static final String ATTRIBUTE_TYPE = "type";
- private static final String ATTRIBUTE_ID = "id";
- private static final String ATTRIBUTE_CS = "cs";
-
- private static final String TYPE = "printer";
-
- private static final String PRIVET_SERVICE = "_privet._tcp";
-
- /** The required mDNS service types */
- private static final Set<String> PRINTER_SERVICE_TYPE = Set.of(
- PRIVET_SERVICE); // Not checking _printer_._sub
-
- /** All possible connection states */
- private static final Set<String> POSSIBLE_CONNECTION_STATES = Set.of(
- "online",
- "offline",
- "connecting",
- "not-configured");
-
- private static final byte SUPPORTED_TXTVERS = '1';
-
- /** The mDNS filtered discovery */
- private final MDNSFilteredDiscovery mMDNSFilteredDiscovery;
-
- /**
- * Create a plugin detecting Google Cloud Print printers.
- *
- * @param context The context the plugin runs in
- */
- public CloudPrintPlugin(@NonNull Context context) {
- mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, PRINTER_SERVICE_TYPE,
- nsdServiceInfo -> {
- // The attributes are case insensitive. For faster searching create a clone of
- // the map with the attribute-keys all in lower case.
- ArrayMap<String, byte[]> caseInsensitiveAttributes =
- new ArrayMap<>(nsdServiceInfo.getAttributes().size());
- for (Map.Entry<String, byte[]> entry : nsdServiceInfo.getAttributes()
- .entrySet()) {
- caseInsensitiveAttributes.put(entry.getKey().toLowerCase(),
- entry.getValue());
- }
-
- if (DEBUG) {
- Log.i(LOG_TAG, nsdServiceInfo.getServiceName() + ":");
- Log.i(LOG_TAG, "type: " + nsdServiceInfo.getServiceType());
- Log.i(LOG_TAG, "host: " + nsdServiceInfo.getHost());
- for (Map.Entry<String, byte[]> entry : caseInsensitiveAttributes.entrySet()) {
- if (entry.getValue() == null) {
- Log.i(LOG_TAG, entry.getKey() + "= null");
- } else {
- Log.i(LOG_TAG, entry.getKey() + "=" + new String(entry.getValue(),
- StandardCharsets.UTF_8));
- }
- }
- }
-
- byte[] txtvers = caseInsensitiveAttributes.get(ATTRIBUTE_TXTVERS);
- if (txtvers == null || txtvers.length != 1 || txtvers[0] != SUPPORTED_TXTVERS) {
- // The spec requires this to be the first attribute, but at this time we
- // lost the order of the attributes
- return false;
- }
-
- if (caseInsensitiveAttributes.get(ATTRIBUTE_TY) == null) {
- return false;
- }
-
- byte[] url = caseInsensitiveAttributes.get(ATTRIBUTE_URL);
- if (url == null || url.length == 0) {
- return false;
- }
-
- byte[] type = caseInsensitiveAttributes.get(ATTRIBUTE_TYPE);
- if (type == null || !TYPE.equals(
- new String(type, StandardCharsets.UTF_8).toLowerCase())) {
- return false;
- }
-
- if (caseInsensitiveAttributes.get(ATTRIBUTE_ID) == null) {
- return false;
- }
-
- byte[] cs = caseInsensitiveAttributes.get(ATTRIBUTE_CS);
- if (cs == null || !POSSIBLE_CONNECTION_STATES.contains(
- new String(cs, StandardCharsets.UTF_8).toLowerCase())) {
- return false;
- }
-
- InetAddress address = nsdServiceInfo.getHost();
- if (!(address instanceof Inet4Address)) {
- // Not checking for link local address
- return false;
- }
-
- return true;
- });
- }
-
- @Override
- @NonNull public CharSequence getPackageName() {
- return "com.google.android.apps.cloudprint";
- }
-
- @Override
- public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception {
- mMDNSFilteredDiscovery.start(callback);
- }
-
- @Override
- @StringRes public int getName() {
- return R.string.plugin_vendor_google_cloud_print;
- }
-
- @Override
- public void stop() throws Exception {
- mMDNSFilteredDiscovery.stop();
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index e1853675d6d4..761bb7918afd 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -27,7 +27,7 @@ import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
-import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider
+import com.android.settingslib.spa.gallery.editor.SettingsDropdownBoxPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsDropdownCheckBoxProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
@@ -99,7 +99,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
OperateListPageProvider,
EditorMainPageProvider,
SettingsOutlinedTextFieldPageProvider,
- SettingsExposedDropdownMenuBoxPageProvider,
+ SettingsDropdownBoxPageProvider,
SettingsDropdownCheckBoxProvider,
SettingsTextFieldPasswordPageProvider,
SearchScaffoldPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
index 9f2158a13f25..c511542f265a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
@@ -35,7 +35,7 @@ object EditorMainPageProvider : SettingsPageProvider {
return listOf(
SettingsOutlinedTextFieldPageProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
- SettingsExposedDropdownMenuBoxPageProvider.buildInjectEntry().setLink(fromPage = owner)
+ SettingsDropdownBoxPageProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
SettingsDropdownCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuBoxPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownBoxPageProvider.kt
index 5ffbe8ba8a26..2ebb5f5eba27 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuBoxPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownBoxPageProvider.kt
@@ -28,16 +28,15 @@ import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox
+import com.android.settingslib.spa.widget.editor.SettingsDropdownBox
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-private const val TITLE = "Sample SettingsExposedDropdownMenuBox"
+private const val TITLE = "Sample SettingsDropdownBox"
-object SettingsExposedDropdownMenuBoxPageProvider : SettingsPageProvider {
- override val name = "SettingsExposedDropdownMenuBox"
- private const val exposedDropdownMenuBoxLabel = "ExposedDropdownMenuBoxLabel"
+object SettingsDropdownBoxPageProvider : SettingsPageProvider {
+ override val name = "SettingsDropdownBox"
override fun getTitle(arguments: Bundle?): String {
return TITLE
@@ -45,18 +44,44 @@ object SettingsExposedDropdownMenuBoxPageProvider : SettingsPageProvider {
@Composable
override fun Page(arguments: Bundle?) {
- var selectedItem by remember { mutableIntStateOf(-1) }
- val options = listOf("item1", "item2", "item3")
RegularScaffold(title = TITLE) {
- SettingsExposedDropdownMenuBox(
- label = exposedDropdownMenuBoxLabel,
- options = options,
- selectedOptionIndex = selectedItem,
- enabled = true,
- onselectedOptionTextChange = { selectedItem = it })
+ Regular()
+ NotEnabled()
+ Empty()
}
}
+ @Composable
+ private fun Regular() {
+ var selectedItem by remember { mutableIntStateOf(-1) }
+ SettingsDropdownBox(
+ label = "SettingsDropdownBox",
+ options = listOf("item1", "item2", "item3"),
+ selectedOptionIndex = selectedItem,
+ ) { selectedItem = it }
+ }
+
+ @Composable
+ private fun NotEnabled() {
+ var selectedItem by remember { mutableIntStateOf(0) }
+ SettingsDropdownBox(
+ label = "Not enabled",
+ options = listOf("item1", "item2", "item3"),
+ enabled = false,
+ selectedOptionIndex = selectedItem,
+ ) { selectedItem = it }
+ }
+
+ @Composable
+ private fun Empty() {
+ var selectedItem by remember { mutableIntStateOf(-1) }
+ SettingsDropdownBox(
+ label = "Empty",
+ options = emptyList(),
+ selectedOptionIndex = selectedItem,
+ ) { selectedItem = it }
+ }
+
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = createSettingsPage())
.setUiLayoutFn {
@@ -70,8 +95,8 @@ object SettingsExposedDropdownMenuBoxPageProvider : SettingsPageProvider {
@Preview(showBackground = true)
@Composable
-private fun SettingsExposedDropdownMenuBoxPagePreview() {
+private fun SettingsDropdownBoxPagePreview() {
SettingsTheme {
- SettingsExposedDropdownMenuBoxPageProvider.Page(null)
+ SettingsDropdownBoxPageProvider.Page(null)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
index f6692a356899..679c562ac92d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -19,7 +19,6 @@ package com.android.settingslib.spa.widget.editor
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
-import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
@@ -31,80 +30,58 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-@Composable
+internal interface DropdownTextBoxScope {
+ fun dismiss()
+}
+
@OptIn(ExperimentalMaterial3Api::class)
-fun SettingsExposedDropdownMenuBox(
+@Composable
+internal fun DropdownTextBox(
label: String,
- options: List<String>,
- selectedOptionIndex: Int,
- enabled: Boolean,
- onselectedOptionTextChange: (Int) -> Unit,
+ text: String,
+ enabled: Boolean = true,
+ errorMessage: String? = null,
+ content: @Composable DropdownTextBoxScope.() -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
+ val scope = remember {
+ object : DropdownTextBoxScope {
+ override fun dismiss() {
+ expanded = false
+ }
+ }
+ }
ExposedDropdownMenuBox(
expanded = expanded,
- onExpandedChange = { expanded = it },
+ onExpandedChange = { expanded = enabled && it },
modifier = Modifier
- .width(350.dp)
- .padding(SettingsDimension.menuFieldPadding),
+ .padding(SettingsDimension.menuFieldPadding)
+ .width(Width),
) {
OutlinedTextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
modifier = Modifier
.menuAnchor()
.fillMaxWidth(),
- value = options.getOrElse(selectedOptionIndex) { "" },
+ value = text,
onValueChange = { },
label = { Text(text = label) },
- trailingIcon = {
- ExposedDropdownMenuDefaults.TrailingIcon(
- expanded = expanded
- )
- },
+ trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
singleLine = true,
readOnly = true,
- enabled = enabled
+ enabled = enabled,
+ isError = errorMessage != null,
+ supportingText = errorMessage?.let { { Text(text = it) } },
)
- if (options.isNotEmpty()) {
- ExposedDropdownMenu(
- expanded = expanded,
- modifier = Modifier
- .fillMaxWidth(),
- onDismissRequest = { expanded = false },
- ) {
- options.forEach { option ->
- DropdownMenuItem(
- text = { Text(option) },
- onClick = {
- onselectedOptionTextChange(options.indexOf(option))
- expanded = false
- },
- contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
- )
- }
- }
- }
+ ExposedDropdownMenu(
+ expanded = expanded,
+ modifier = Modifier.width(Width),
+ onDismissRequest = { expanded = false },
+ ) { scope.content() }
}
}
-@Preview
-@Composable
-private fun SettingsExposedDropdownMenuBoxsPreview() {
- val item1 = "item1"
- val item2 = "item2"
- val item3 = "item3"
- val options = listOf(item1, item2, item3)
- SettingsTheme {
- SettingsExposedDropdownMenuBox(
- label = "ExposedDropdownMenuBoxLabel",
- options = options,
- selectedOptionIndex = 0,
- enabled = true,
- onselectedOptionTextChange = {})
- }
-} \ No newline at end of file
+private val Width = 310.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBox.kt
new file mode 100644
index 000000000000..ff141c2b383c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBox.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.settingslib.spa.widget.editor
+
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+fun SettingsDropdownBox(
+ label: String,
+ options: List<String>,
+ selectedOptionIndex: Int,
+ enabled: Boolean = true,
+ onSelectedOptionChange: (Int) -> Unit,
+) {
+ DropdownTextBox(
+ label = label,
+ text = options.getOrElse(selectedOptionIndex) { "" },
+ enabled = enabled && options.isNotEmpty(),
+ ) {
+ options.forEachIndexed { index, option ->
+ DropdownMenuItem(
+ text = { Text(option) },
+ onClick = {
+ dismiss()
+ onSelectedOptionChange(index)
+ },
+ contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun SettingsDropdownBoxPreview() {
+ val item1 = "item1"
+ val item2 = "item2"
+ val item3 = "item3"
+ val options = listOf(item1, item2, item3)
+ SettingsTheme {
+ SettingsDropdownBox(
+ label = "ExposedDropdownMenuBoxLabel",
+ options = options,
+ selectedOptionIndex = 0,
+ enabled = true,
+ ) {}
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
index 57963e6eaa40..0e7e49960be1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
@@ -19,28 +19,15 @@ package com.android.settingslib.spa.widget.editor
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
import androidx.compose.material3.Checkbox
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExposedDropdownMenuBox
-import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -68,7 +55,6 @@ data class SettingsDropdownCheckOption(
}
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsDropdownCheckBox(
label: String,
@@ -78,43 +64,18 @@ fun SettingsDropdownCheckBox(
errorMessage: String? = null,
onSelectedStateChange: () -> Unit = {},
) {
- var dropDownWidth by remember { mutableIntStateOf(0) }
- var expanded by remember { mutableStateOf(false) }
- val changeable = enabled && options.changeable
- ExposedDropdownMenuBox(
- expanded = expanded,
- onExpandedChange = { expanded = changeable && it },
- modifier = Modifier
- .width(350.dp)
- .padding(SettingsDimension.textFieldPadding)
- .onSizeChanged { dropDownWidth = it.width },
+ DropdownTextBox(
+ label = label,
+ text = getDisplayText(options) ?: emptyText,
+ enabled = enabled && options.changeable,
+ errorMessage = errorMessage,
) {
- OutlinedTextField(
- // The `menuAnchor` modifier must be passed to the text field for correctness.
- modifier = Modifier
- .menuAnchor()
- .fillMaxWidth(),
- value = getDisplayText(options) ?: emptyText,
- onValueChange = {},
- label = { Text(text = label) },
- trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
- readOnly = true,
- enabled = changeable,
- isError = errorMessage != null,
- supportingText = errorMessage?.let { { Text(text = it) } },
- )
- ExposedDropdownMenu(
- expanded = expanded,
- modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
- onDismissRequest = { expanded = false },
- ) {
- for (option in options) {
- CheckboxItem(option) {
- option.onClick()
- if (option.changeable) {
- checkboxItemOnClick(options, option)
- onSelectedStateChange()
- }
+ for (option in options) {
+ CheckboxItem(option) {
+ option.onClick()
+ if (option.changeable) {
+ checkboxItemOnClick(options, option)
+ onSelectedStateChange()
}
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBoxTest.kt
new file mode 100644
index 000000000000..c34742461774
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBoxTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.settingslib.spa.widget.editor
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsDropdownBoxTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun dropdownMenuBox_displayed() {
+ composeTestRule.setContent {
+ var selectedItem by remember { mutableStateOf(0) }
+ SettingsDropdownBox(
+ label = LABEL,
+ options = options,
+ selectedOptionIndex = selectedItem,
+ ) { selectedItem = it }
+ }
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun dropdownMenuBox_enabled_expanded() {
+ composeTestRule.setContent {
+ var selectedItem by remember { mutableIntStateOf(0) }
+ SettingsDropdownBox(
+ label = LABEL,
+ options = options,
+ selectedOptionIndex = selectedItem
+ ) { selectedItem = it }
+ }
+ composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ composeTestRule.onNodeWithText(ITEM2).assertIsDisplayed()
+ }
+
+ @Test
+ fun dropdownMenuBox_notEnabled_notExpanded() {
+ composeTestRule.setContent {
+ var selectedItem by remember { mutableIntStateOf(0) }
+ SettingsDropdownBox(
+ label = LABEL,
+ options = options,
+ enabled = false,
+ selectedOptionIndex = selectedItem
+ ) { selectedItem = it }
+ }
+ composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
+ }
+
+ @Test
+ fun dropdownMenuBox_valueChanged() {
+ composeTestRule.setContent {
+ var selectedItem by remember { mutableIntStateOf(0) }
+ SettingsDropdownBox(
+ label = LABEL,
+ options = options,
+ selectedOptionIndex = selectedItem
+ ) { selectedItem = it }
+ }
+ composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+ composeTestRule.onNodeWithText(ITEM2).performClick()
+
+ composeTestRule.onNodeWithText(ITEM2).assertIsDisplayed()
+ }
+ private companion object {
+ const val LABEL = "Label"
+ const val ITEM2 = "item2"
+ val options = listOf("item1", ITEM2, "item3")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBoxTest.kt
deleted file mode 100644
index bc67e4c61ea5..000000000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBoxTest.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 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.settingslib.spa.widget.editor
-
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SettingsExposedDropdownMenuBoxTest {
- @get:Rule
- val composeTestRule = createComposeRule()
- private val options = listOf("item1", "item2", "item3")
- private val item2 = "item2"
- private val exposedDropdownMenuBoxLabel = "ExposedDropdownMenuBoxLabel"
-
- @Test
- fun exposedDropdownMenuBoxs_displayed() {
- composeTestRule.setContent {
- var selectedItem by remember { mutableStateOf(0) }
- SettingsExposedDropdownMenuBox(
- label = exposedDropdownMenuBoxLabel,
- options = options,
- selectedOptionIndex = selectedItem,
- enabled = true,
- onselectedOptionTextChange = { selectedItem = it })
- }
- composeTestRule.onNodeWithText(exposedDropdownMenuBoxLabel, substring = true)
- .assertIsDisplayed()
- }
-
- @Test
- fun exposedDropdownMenuBoxs_expanded() {
- composeTestRule.setContent {
- var selectedItem by remember { mutableIntStateOf(0) }
- SettingsExposedDropdownMenuBox(
- label = exposedDropdownMenuBoxLabel,
- options = options,
- selectedOptionIndex = selectedItem,
- enabled = true,
- onselectedOptionTextChange = { selectedItem = it })
- }
- composeTestRule.onNodeWithText(item2, substring = true)
- .assertDoesNotExist()
- composeTestRule.onNodeWithText(exposedDropdownMenuBoxLabel, substring = true)
- .performClick()
- composeTestRule.onNodeWithText(item2, substring = true)
- .assertIsDisplayed()
- }
-
- @Test
- fun exposedDropdownMenuBoxs_valueChanged() {
- composeTestRule.setContent {
- var selectedItem by remember { mutableIntStateOf(0) }
- SettingsExposedDropdownMenuBox(
- label = exposedDropdownMenuBoxLabel,
- options = options,
- selectedOptionIndex = selectedItem,
- enabled = true,
- onselectedOptionTextChange = { selectedItem = it })
- }
- composeTestRule.onNodeWithText(item2, substring = true)
- .assertDoesNotExist()
- composeTestRule.onNodeWithText(exposedDropdownMenuBoxLabel, substring = true)
- .performClick()
- composeTestRule.onNodeWithText(item2, substring = true)
- .performClick()
- composeTestRule.onNodeWithText(item2, substring = true)
- .assertIsDisplayed()
- }
-} \ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_bt_le_audio_sharing.xml b/packages/SettingsLib/res/drawable/ic_bt_le_audio_sharing.xml
new file mode 100644
index 000000000000..618677389ce1
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_le_audio_sharing.xml
@@ -0,0 +1,87 @@
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true"
+ android:height="24dp"
+ android:width="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M16.984,24H7.279L12.131,15.508L16.984,24ZM10.481,22.144H13.781L12.131,19.257L10.481,22.144Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M12.131,14.295C13.471,14.295 14.558,13.209 14.558,11.869C14.558,10.529 13.471,9.442 12.131,9.442C10.791,9.442 9.705,10.529 9.705,11.869C9.705,13.209 10.791,14.295 12.131,14.295Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M4.573,21.368C4.052,20.943 3.967,20.179 4.379,19.657C4.804,19.136 5.568,19.051 6.09,19.463C6.611,19.876 6.696,20.64 6.284,21.174C6.041,21.465 5.689,21.623 5.338,21.623C5.071,21.623 4.804,21.538 4.573,21.368Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M17.991,21.162C17.579,20.628 17.663,19.876 18.185,19.451C18.707,19.039 19.471,19.124 19.896,19.646C20.308,20.167 20.223,20.931 19.702,21.344C19.471,21.526 19.204,21.611 18.949,21.611C18.586,21.611 18.234,21.453 17.991,21.162Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M1.213,17.145C0.91,16.551 1.165,15.823 1.771,15.532C2.378,15.241 3.093,15.495 3.397,16.09C3.688,16.697 3.433,17.424 2.827,17.715C2.657,17.8 2.475,17.837 2.305,17.837C1.844,17.837 1.419,17.582 1.213,17.145Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M21.449,17.691C20.842,17.4 20.588,16.684 20.879,16.077C21.17,15.471 21.898,15.216 22.504,15.507C23.099,15.798 23.354,16.526 23.062,17.133C22.856,17.557 22.419,17.812 21.971,17.812C21.789,17.812 21.619,17.776 21.449,17.691Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M0,11.892C0,11.225 0.546,10.679 1.213,10.679C1.88,10.679 2.426,11.212 2.426,11.892C2.426,12.559 1.88,13.105 1.213,13.105C0.546,13.105 0,12.559 0,11.892Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M21.837,11.869C21.837,11.857 21.837,11.845 21.837,11.833C21.824,11.153 22.37,10.62 23.05,10.607C23.717,10.607 24.251,11.153 24.263,11.821C24.263,11.833 24.263,11.845 24.263,11.845C24.263,11.857 24.263,11.869 24.263,11.869C24.263,12.536 23.717,13.082 23.05,13.082C22.382,13.082 21.837,12.536 21.837,11.869Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M1.759,8.242C1.152,7.963 0.898,7.235 1.189,6.628C1.48,6.022 2.196,5.767 2.802,6.058C3.409,6.349 3.664,7.077 3.372,7.684C3.166,8.108 2.729,8.363 2.281,8.363C2.099,8.363 1.929,8.327 1.759,8.242Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M20.866,7.622C20.563,7.028 20.818,6.3 21.424,6.009C22.019,5.706 22.747,5.96 23.038,6.567C23.038,6.567 23.038,6.567 23.05,6.567C23.341,7.161 23.087,7.889 22.48,8.181C22.31,8.265 22.128,8.302 21.958,8.302C21.509,8.302 21.073,8.059 20.866,7.622Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M4.355,4.104C3.931,3.582 4.016,2.818 4.537,2.406C5.071,1.981 5.823,2.066 6.248,2.588C6.672,3.109 6.588,3.874 6.066,4.298C5.835,4.48 5.569,4.565 5.302,4.565C4.95,4.565 4.598,4.407 4.355,4.104Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M18.161,4.262C17.627,3.838 17.542,3.073 17.955,2.552C18.379,2.03 19.132,1.945 19.666,2.358C20.187,2.77 20.272,3.534 19.86,4.068C19.617,4.359 19.265,4.517 18.913,4.517C18.646,4.517 18.379,4.432 18.161,4.262Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M8.492,1.497C8.334,0.854 8.747,0.199 9.402,0.041C10.057,-0.105 10.7,0.308 10.858,0.963C11.003,1.606 10.591,2.261 9.948,2.407C9.851,2.431 9.754,2.443 9.669,2.443C9.123,2.443 8.613,2.067 8.492,1.497Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M14.267,2.395C13.599,2.249 13.199,1.606 13.345,0.951C13.49,0.296 14.133,-0.116 14.788,0.029C15.443,0.175 15.856,0.83 15.71,1.485C15.589,2.043 15.08,2.431 14.534,2.431C14.437,2.431 14.352,2.419 14.267,2.395Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M7,17.037C6.527,16.564 6.527,15.8 7,15.326C7.473,14.841 8.237,14.841 8.71,15.314C9.196,15.787 9.196,16.552 8.723,17.025C8.48,17.267 8.177,17.389 7.861,17.389C7.546,17.389 7.242,17.267 7,17.037Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M15.565,17.012C15.092,16.539 15.092,15.762 15.565,15.289C16.038,14.816 16.814,14.816 17.288,15.289C17.761,15.762 17.761,16.539 17.288,17.012C17.045,17.243 16.742,17.364 16.426,17.364C16.111,17.364 15.807,17.243 15.565,17.012Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M4.853,11.917C4.853,11.237 5.386,10.691 6.054,10.691C6.721,10.691 7.279,11.225 7.279,11.892C7.279,12.56 6.745,13.106 6.078,13.118C5.398,13.118 4.853,12.584 4.853,11.917Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M16.984,11.868C16.984,11.856 16.984,11.844 16.984,11.832C16.984,11.832 16.984,11.82 16.984,11.807C16.972,11.14 17.506,10.582 18.185,10.582C18.852,10.57 19.398,11.116 19.41,11.783C19.41,11.795 19.41,11.82 19.41,11.832C19.41,11.844 19.41,11.856 19.41,11.868C19.41,12.535 18.865,13.081 18.197,13.081C17.53,13.081 16.984,12.535 16.984,11.868Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M6.952,8.471C6.478,7.997 6.478,7.233 6.952,6.76C6.952,6.76 6.952,6.76 6.939,6.76C7.413,6.275 8.189,6.275 8.662,6.748C9.135,7.221 9.147,7.985 8.674,8.458C8.432,8.701 8.116,8.822 7.813,8.822C7.497,8.822 7.194,8.701 6.952,8.471Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M15.529,8.399C15.043,7.938 15.043,7.161 15.504,6.688C15.977,6.203 16.742,6.203 17.227,6.664C17.7,7.137 17.712,7.901 17.239,8.387C17.009,8.629 16.693,8.751 16.378,8.751C16.075,8.751 15.759,8.629 15.529,8.399Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M10.87,5.815C10.858,5.148 11.392,4.59 12.071,4.59C12.738,4.578 13.284,5.124 13.284,5.791C13.296,6.458 12.762,7.016 12.083,7.016C11.416,7.016 10.87,6.483 10.87,5.815Z"/>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
index 60983070b1cf..2a44511599f1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
@@ -40,3 +40,21 @@ class FakeNotificationsSoundPolicyRepository : NotificationsSoundPolicyRepositor
mutableZenMode.value = zenMode
}
}
+
+fun FakeNotificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories: Int = 0,
+ priorityCallSenders: Int = NotificationManager.Policy.PRIORITY_SENDERS_ANY,
+ priorityMessageSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
+ suppressedVisualEffects: Int = NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET,
+ state: Int = NotificationManager.Policy.STATE_UNSET,
+ priorityConversationSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
+) = updateNotificationPolicy(
+ NotificationManager.Policy(
+ priorityCategories,
+ priorityCallSenders,
+ priorityMessageSenders,
+ suppressedVisualEffects,
+ state,
+ priorityConversationSenders,
+ )
+) \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
new file mode 100644
index 000000000000..794cf832f48b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.settingslib.statusbar.notification.domain.interactor
+
+import android.app.NotificationManager
+import android.media.AudioManager
+import android.provider.Settings
+import android.service.notification.ZenModeConfig
+import com.android.settingslib.statusbar.notification.data.model.ZenMode
+import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository
+import com.android.settingslib.volume.shared.model.AudioStream
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/** Determines notification sounds state and limitations. */
+class NotificationsSoundPolicyInteractor(
+ private val repository: NotificationsSoundPolicyRepository
+) {
+
+ /** @see NotificationManager.getNotificationPolicy */
+ val notificationPolicy: StateFlow<NotificationManager.Policy?>
+ get() = repository.notificationPolicy
+
+ /** @see NotificationManager.getZenMode */
+ val zenMode: StateFlow<ZenMode?>
+ get() = repository.zenMode
+
+ /** Checks if [notificationPolicy] allows alarms. */
+ val areAlarmsAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowAlarms() }
+
+ /** Checks if [notificationPolicy] allows media. */
+ val isMediaAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowMedia() }
+
+ /** Checks if [notificationPolicy] allows ringer. */
+ val isRingerAllowed: Flow<Boolean?> =
+ notificationPolicy.map { policy ->
+ policy ?: return@map null
+ !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(policy)
+ }
+
+ /** Checks if the [stream] is muted by either [zenMode] or [notificationPolicy]. */
+ fun isZenMuted(stream: AudioStream): Flow<Boolean> {
+ return combine(
+ zenMode.filterNotNull(),
+ areAlarmsAllowed.filterNotNull(),
+ isMediaAllowed.filterNotNull(),
+ isRingerAllowed.filterNotNull(),
+ ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed ->
+ if (zenMode.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ return@combine true
+ }
+
+ val isNotificationOrRing =
+ stream.value == AudioManager.STREAM_RING ||
+ stream.value == AudioManager.STREAM_NOTIFICATION
+ if (isNotificationOrRing && zenMode.zenMode == Settings.Global.ZEN_MODE_ALARMS) {
+ return@combine true
+ }
+ if (zenMode.zenMode != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+ return@combine false
+ }
+
+ if (stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed) {
+ return@combine true
+ }
+ if (stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed) {
+ return@combine true
+ }
+ if (isNotificationOrRing && !isRingerAllowed) {
+ return@combine true
+ }
+
+ return@combine false
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 6851997ac323..0df4615c8b7c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -64,10 +65,10 @@ interface AudioRepository {
val communicationDevice: StateFlow<AudioDeviceInfo?>
/** State of the [AudioStream]. */
- suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
+ fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
- /** Current state of the [AudioStream]. */
- suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel
+ /** Returns the last audible volume before stream was muted. */
+ suspend fun getLastAudibleVolume(audioStream: AudioStream): Int
suspend fun setVolume(audioStream: AudioStream, volume: Int)
@@ -122,7 +123,7 @@ class AudioRepositoryImpl(
audioManager.communicationDevice,
)
- override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
+ override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
return audioManagerEventsReceiver.events
.filter {
if (it is StreamAudioManagerEvent) {
@@ -132,20 +133,24 @@ class AudioRepositoryImpl(
}
}
.map { getCurrentAudioStream(audioStream) }
+ .onStart { emit(getCurrentAudioStream(audioStream)) }
.flowOn(backgroundCoroutineContext)
}
- override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+ private fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+ return AudioStreamModel(
+ audioStream = audioStream,
+ minVolume = getMinVolume(audioStream),
+ maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
+ volume = audioManager.getStreamVolume(audioStream.value),
+ isAffectedByRingerMode = audioManager.isStreamAffectedByRingerMode(audioStream.value),
+ isMuted = audioManager.isStreamMute(audioStream.value),
+ )
+ }
+
+ override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int {
return withContext(backgroundCoroutineContext) {
- AudioStreamModel(
- audioStream = audioStream,
- minVolume = getMinVolume(audioStream),
- maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
- volume = audioManager.getStreamVolume(audioStream.value),
- isAffectedByRingerMode =
- audioManager.isStreamAffectedByRingerMode(audioStream.value),
- isMuted = audioManager.isStreamMute(audioStream.value)
- )
+ audioManager.getLastAudibleStreamVolume(audioStream.value)
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
new file mode 100644
index 000000000000..56b0bf74574f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.settingslib.volume.domain.interactor
+
+import android.media.AudioManager
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Provides audio stream state and an ability to change it */
+class AudioVolumeInteractor(
+ private val audioRepository: AudioRepository,
+ private val notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
+) {
+
+ /** State of the [AudioStream]. */
+ fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+ combine(
+ audioRepository.getAudioStream(audioStream),
+ audioRepository.ringerMode,
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream)
+ ) { streamModel: AudioStreamModel, ringerMode: RingerMode, isZenMuted: Boolean ->
+ streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted))
+ }
+
+ suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+ audioRepository.setVolume(audioStream, volume)
+
+ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+ audioRepository.setMuted(audioStream, isMuted)
+
+ /** Checks if the volume can be changed via the UI. */
+ fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
+ return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
+ getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted }
+ } else {
+ flowOf(true)
+ }
+ }
+
+ private suspend fun processVolume(
+ audioStreamModel: AudioStreamModel,
+ ringerMode: RingerMode,
+ isZenMuted: Boolean,
+ ): Int {
+ if (isZenMuted) {
+ return audioRepository.getLastAudibleVolume(audioStreamModel.audioStream)
+ }
+ val isNotificationOrRing =
+ audioStreamModel.audioStream.value == AudioManager.STREAM_RING ||
+ audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION
+ if (isNotificationOrRing && ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ // For ringer-mode affected streams, show volume as zero when ringer mode is vibrate
+ if (
+ audioStreamModel.audioStream.value == AudioManager.STREAM_RING ||
+ (audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
+ audioStreamModel.isMuted)
+ ) {
+ return 0
+ }
+ } else if (audioStreamModel.isMuted) {
+ return 0
+ }
+ return audioStreamModel.volume
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
index 13ed9a802318..c3b1a7cb16e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
@@ -54,6 +54,7 @@ class AudioManagerEventsReceiverImpl(
AudioManager.VOLUME_CHANGED_ACTION,
AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+ AudioManager.ACTION_VOLUME_CHANGED,
)
override val events: SharedFlow<AudioManagerEvent> =
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
index 58f3c2d61f3b..9c48299d81be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
@@ -25,7 +25,7 @@ value class AudioStream(val value: Int) {
require(value in supportedStreamTypes) { "Unsupported stream=$value" }
}
- private companion object {
+ companion object {
val supportedStreamTypes =
setOf(
AudioManager.STREAM_VOICE_CALL,
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 9ddf876be68e..1728a8022ce5 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -181,24 +181,6 @@ class AudioRepositoryTest {
}
@Test
- fun adjustingVolume_currentModeIsUpToDate() {
- testScope.runTest {
- val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
- var streamModel: AudioStreamModel? = null
- underTest
- .getAudioStream(audioStream)
- .onEach { streamModel = it }
- .launchIn(backgroundScope)
- runCurrent()
-
- underTest.setVolume(audioStream, 50)
- runCurrent()
-
- assertThat(underTest.getCurrentAudioStream(audioStream)).isEqualTo(streamModel)
- }
- }
-
- @Test
fun muteStream_mutesTheStream() {
testScope.runTest {
val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index b58187d8e95e..28cdc6db192b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -807,7 +807,9 @@ public class SettingsBackupTest {
Settings.Secure.UI_TRANSLATION_ENABLED,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
Settings.Secure.DND_CONFIGS_MIGRATED,
- Settings.Secure.NAVIGATION_MODE_RESTORE);
+ Settings.Secure.NAVIGATION_MODE_RESTORE,
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
+ Settings.Secure.V_TO_U_RESTORE_DENYLIST);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 71f9ba2788a1..a69a2a64dccb 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -377,6 +377,13 @@ flag {
}
flag {
+ name: "screenshot_action_dismiss_system_windows"
+ namespace: "systemui"
+ description: "Dismiss existing system windows when starting action from screenshot UI"
+ bug: "309933761"
+}
+
+flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -529,3 +536,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "bind_keyguard_media_visibility"
+ namespace: "systemui"
+ description: "Binds Keyguard Media Controller Visibility to MediaContainerView"
+ bug: "298213983"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
index 535c2d32ed09..e862f0c43a58 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
@@ -17,7 +17,6 @@ package com.android.systemui.surfaceeffects.turbulencenoise
import android.view.View
import androidx.annotation.VisibleForTesting
-import java.util.Random
/** Plays [TurbulenceNoiseView] in ease-in, main (no easing), and ease-out order. */
class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
@@ -37,8 +36,6 @@ class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoise
}
}
- private val random = Random()
-
/** Current state of the animation. */
@VisibleForTesting
var state: AnimationState = AnimationState.NOT_PLAYING
@@ -95,12 +92,7 @@ class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoise
}
state = AnimationState.EASE_IN
- // Add offset to avoid repetitive noise.
- turbulenceNoiseView.playEaseIn(
- offsetX = random.nextFloat(),
- offsetY = random.nextFloat(),
- this::playMainAnimation
- )
+ turbulenceNoiseView.playEaseIn(this::playMainAnimation)
}
private fun playMainAnimation() {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index c59bc106ca91..5e72e3bd1e39 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -109,7 +109,7 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
/** Plays the turbulence noise with linear ease-in. */
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- fun playEaseIn(offsetX: Float = 0f, offsetY: Float = 0f, onAnimationEnd: Runnable? = null) {
+ fun playEaseIn(onAnimationEnd: Runnable? = null) {
if (noiseConfig == null) {
return
}
@@ -129,8 +129,8 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
val progress = updateListener.animatedValue as Float
shader.setNoiseMove(
- offsetX + initialX + timeInSec * config.noiseMoveSpeedX,
- offsetY + initialY + timeInSec * config.noiseMoveSpeedY,
+ initialX + timeInSec * config.noiseMoveSpeedX,
+ initialY + timeInSec * config.noiseMoveSpeedY,
initialZ + timeInSec * config.noiseMoveSpeedZ
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 8bd0d45920f4..97d5b41000de 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -35,7 +35,6 @@ import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
-import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
@@ -55,7 +54,6 @@ constructor(
private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
- private val alphaViewModel: AodAlphaViewModel,
) {
/**
* Renders a single lockscreen shortcut.
@@ -104,7 +102,6 @@ constructor(
content {
IndicationArea(
indicationAreaViewModel = indicationAreaViewModel,
- alphaViewModel = alphaViewModel,
indicationController = indicationController,
)
}
@@ -183,7 +180,6 @@ constructor(
@Composable
private fun IndicationArea(
indicationAreaViewModel: KeyguardIndicationAreaViewModel,
- alphaViewModel: AodAlphaViewModel,
indicationController: KeyguardIndicationController,
modifier: Modifier = Modifier,
) {
@@ -196,7 +192,6 @@ constructor(
KeyguardIndicationAreaBinder.bind(
view = view,
viewModel = indicationAreaViewModel,
- aodAlphaViewModel = alphaViewModel,
indicationController = indicationController,
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 4156d833b0de..ce96d75da666 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -18,7 +18,9 @@
package com.android.systemui.communal.domain.interactor
import android.app.smartspace.SmartspaceTarget
+import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -51,6 +53,8 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
@@ -96,6 +100,7 @@ class CommunalInteractorTest : SysuiTestCase() {
private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var userTracker: FakeUserTracker
private lateinit var underTest: CommunalInteractor
@@ -113,6 +118,7 @@ class CommunalInteractorTest : SysuiTestCase() {
editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
sceneInteractor = kosmos.sceneInteractor
+ userTracker = kosmos.fakeUserTracker
whenever(mainUser.isMain).thenReturn(true)
whenever(secondaryUser.isMain).thenReturn(false)
@@ -207,25 +213,19 @@ class CommunalInteractorTest : SysuiTestCase() {
keyguardRepository.setKeyguardOccluded(false)
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- // Widgets are available.
- val widgets =
- listOf(
- CommunalWidgetContentModel(
- appWidgetId = 0,
- priority = 30,
- providerInfo = mock(),
- ),
- CommunalWidgetContentModel(
- appWidgetId = 1,
- priority = 20,
- providerInfo = mock(),
- ),
- CommunalWidgetContentModel(
- appWidgetId = 2,
- priority = 10,
- providerInfo = mock(),
- ),
- )
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ // Widgets available.
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
widgetRepository.setCommunalWidgets(widgets)
val widgetContent by collectLastValue(underTest.widgetContent)
@@ -752,6 +752,38 @@ class CommunalInteractorTest : SysuiTestCase() {
verify(editWidgetsActivityStarter).startActivity(widgetKey)
}
+ @Test
+ fun filterWidgets_whenUserProfileRemoved() =
+ testScope.runTest {
+ // Keyguard showing, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Only main user exists.
+ val userInfos = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+ // Given three widgets, and one of them is associated with pre-existing work profile.
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
+ widgetRepository.setCommunalWidgets(widgets)
+
+ // One widget is filtered out and the remaining two link to main user id.
+ assertThat(checkNotNull(widgetContent).size).isEqualTo(2)
+ widgetContent!!.forEachIndexed { _, model ->
+ assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id)
+ }
+ }
+
private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
val timer = mock(SmartspaceTarget::class.java)
whenever(timer.smartspaceTargetId).thenReturn(id)
@@ -760,4 +792,17 @@ class CommunalInteractorTest : SysuiTestCase() {
whenever(timer.creationTimeMillis).thenReturn(timestamp)
return timer
}
+
+ private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
+ mock<CommunalWidgetContentModel> {
+ whenever(this.appWidgetId).thenReturn(appWidgetId)
+ val providerInfo = mock<AppWidgetProviderInfo>()
+ whenever(providerInfo.profile).thenReturn(UserHandle(userId))
+ whenever(this.providerInfo).thenReturn(providerInfo)
+ }
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 352bacc56ca5..5ee88cb92fa0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -17,6 +17,9 @@
package com.android.systemui.communal.view.viewmodel
import android.app.smartspace.SmartspaceTarget
+import android.appwidget.AppWidgetProviderInfo
+import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -39,6 +42,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
@@ -59,6 +63,7 @@ import org.mockito.MockitoAnnotations
class CommunalEditModeViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var providerInfo: AppWidgetProviderInfo
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -78,6 +83,11 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
+ kosmos.fakeUserTracker.set(
+ userInfos = listOf(MAIN_USER_INFO),
+ selectedUserIndex = 0,
+ )
+ whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
underTest =
CommunalEditModeViewModel(
@@ -100,12 +110,12 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
CommunalWidgetContentModel(
appWidgetId = 0,
priority = 30,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
CommunalWidgetContentModel(
appWidgetId = 1,
priority = 20,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
)
widgetRepository.setCommunalWidgets(widgets)
@@ -156,12 +166,12 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
CommunalWidgetContentModel(
appWidgetId = 0,
priority = 30,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
CommunalWidgetContentModel(
appWidgetId = 1,
priority = 20,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
)
widgetRepository.setCommunalWidgets(widgets)
@@ -205,4 +215,8 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
underTest.onReorderWidgetCancel()
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index cc322d085acd..1e523dd2a9cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -17,7 +17,9 @@
package com.android.systemui.communal.view.viewmodel
import android.app.smartspace.SmartspaceTarget
+import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -45,13 +47,13 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -71,6 +73,7 @@ import org.mockito.MockitoAnnotations
class CommunalViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var user: UserInfo
+ @Mock private lateinit var providerInfo: AppWidgetProviderInfo
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -98,6 +101,12 @@ class CommunalViewModelTest : SysuiTestCase() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+ kosmos.fakeUserTracker.set(
+ userInfos = listOf(MAIN_USER_INFO),
+ selectedUserIndex = 0,
+ )
+ whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
+
underTest =
CommunalViewModel(
testScope,
@@ -147,12 +156,12 @@ class CommunalViewModelTest : SysuiTestCase() {
CommunalWidgetContentModel(
appWidgetId = 0,
priority = 30,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
CommunalWidgetContentModel(
appWidgetId = 1,
priority = 20,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
)
widgetRepository.setCommunalWidgets(widgets)
@@ -225,4 +234,8 @@ class CommunalViewModelTest : SysuiTestCase() {
userRepository.setUserInfos(listOf(user))
userRepository.setSelectedUserInfo(user)
}
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index 8488843905f7..2c9d72c423bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -16,7 +16,9 @@
package com.android.systemui.communal.widgets
+import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
+import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
@@ -32,6 +34,7 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
@@ -65,7 +68,7 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO, USER_INFO_WORK))
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
@@ -76,6 +79,7 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
CommunalAppWidgetHostStartable(
appWidgetHost,
kosmos.communalInteractor,
+ kosmos.fakeUserTracker,
kosmos.applicationCoroutineScope,
kosmos.testDispatcher,
)
@@ -170,6 +174,46 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
}
}
+ @Test
+ fun removeWidgetsForDeletedProfile_whenCommunalIsAvailable() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is available and work profile is configured.
+ setCommunalAvailable(true)
+ kosmos.fakeUserTracker.set(
+ userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK),
+ selectedUserIndex = 0,
+ )
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
+ fakeCommunalWidgetRepository.setCommunalWidgets(widgets)
+
+ underTest.start()
+ runCurrent()
+
+ val communalWidgets by
+ collectLastValue(fakeCommunalWidgetRepository.communalWidgets)
+ assertThat(communalWidgets).containsExactly(widget1, widget2, widget3)
+
+ // Unlock the device and remove work profile.
+ fakeKeyguardRepository.setKeyguardShowing(false)
+ kosmos.fakeUserTracker.set(
+ userInfos = listOf(MAIN_USER_INFO),
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ // Communal becomes available.
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // Widget created for work profile is removed.
+ assertThat(communalWidgets).containsExactly(widget2, widget3)
+ }
+ }
+
private suspend fun setCommunalAvailable(available: Boolean) =
with(kosmos) {
fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
@@ -179,7 +223,16 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, settingsValue, MAIN_USER_INFO.id)
}
+ private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
+ mock<CommunalWidgetContentModel> {
+ whenever(this.appWidgetId).thenReturn(appWidgetId)
+ val providerInfo = mock<AppWidgetProviderInfo>()
+ whenever(providerInfo.profile).thenReturn(UserHandle(userId))
+ whenever(this.providerInfo).thenReturn(providerInfo)
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
new file mode 100644
index 000000000000..e188f5bfc1c8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.app.NotificationManager
+import android.media.AudioManager
+import android.provider.Settings.Global
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.statusbar.notification.data.model.ZenMode
+import com.android.settingslib.statusbar.notification.data.repository.updateNotificationPolicy
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationsSoundPolicyInteractorTest : SysuiTestCase() {
+
+ @JvmField @Rule val expect = Expect.create()
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: NotificationsSoundPolicyInteractor
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ underTest = NotificationsSoundPolicyInteractor(notificationsSoundPolicyRepository)
+ }
+ }
+
+ @Test
+ fun onlyAlarmsCategory_areAlarmsAllowed_isTrue() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_OFF))
+ val expectedByCategory =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.associateWith {
+ it == NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
+ }
+ expectedByCategory.forEach { entry ->
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = entry.key
+ )
+
+ val areAlarmsAllowed by collectLastValue(underTest.areAlarmsAllowed)
+ runCurrent()
+
+ expect.that(areAlarmsAllowed).isEqualTo(entry.value)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun onlyMediaCategory_areAlarmsAllowed_isTrue() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_OFF))
+ val expectedByCategory =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.associateWith {
+ it == NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA
+ }
+ expectedByCategory.forEach { entry ->
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = entry.key
+ )
+
+ val isMediaAllowed by collectLastValue(underTest.isMediaAllowed)
+ runCurrent()
+
+ expect.that(isMediaAllowed).isEqualTo(entry.value)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun atLeastOneCategoryAllowed_isRingerAllowed_isTrue() {
+ with(kosmos) {
+ testScope.runTest {
+ for (category in NotificationManager.Policy.ALL_PRIORITY_CATEGORIES) {
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = category,
+ state = NotificationManager.Policy.STATE_UNSET,
+ )
+
+ val isRingerAllowed by collectLastValue(underTest.isRingerAllowed)
+ runCurrent()
+
+ expect.that(isRingerAllowed).isTrue()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun allCategoriesAllowed_isRingerAllowed_isTrue() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.reduce { acc, value ->
+ acc or value
+ },
+ state = NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED,
+ )
+
+ val isRingerAllowed by collectLastValue(underTest.isRingerAllowed)
+ runCurrent()
+
+ assertThat(isRingerAllowed).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun noCategoriesAndBlocked_isRingerAllowed_isFalse() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = 0,
+ state = NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED,
+ )
+
+ val isRingerAllowed by collectLastValue(underTest.isRingerAllowed)
+ runCurrent()
+
+ assertThat(isRingerAllowed).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun zenModeNoInterruptions_allStreams_muted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_NO_INTERRUPTIONS)
+ )
+
+ for (stream in AudioStream.supportedStreamTypes) {
+ val isZenMuted by collectLastValue(underTest.isZenMuted(AudioStream(stream)))
+ runCurrent()
+
+ expect.that(isZenMuted).isTrue()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun zenModeOff_allStreams_notMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_OFF))
+
+ for (stream in AudioStream.supportedStreamTypes) {
+ val isZenMuted by collectLastValue(underTest.isZenMuted(AudioStream(stream)))
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun zenModeAlarms_ringAndNotifications_muted() {
+ with(kosmos) {
+ val expectedToBeMuted =
+ setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_ALARMS))
+
+ for (stream in AudioStream.supportedStreamTypes) {
+ val isZenMuted by collectLastValue(underTest.isZenMuted(AudioStream(stream)))
+ runCurrent()
+
+ expect.that(isZenMuted).isEqualTo(stream in expectedToBeMuted)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun alarms_allowed_notMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ )
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
+ )
+
+ val isZenMuted by
+ collectLastValue(underTest.isZenMuted(AudioStream(AudioManager.STREAM_ALARM)))
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun media_allowed_notMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ )
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA
+ )
+
+ val isZenMuted by
+ collectLastValue(underTest.isZenMuted(AudioStream(AudioManager.STREAM_MUSIC)))
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun ringer_allowed_notificationsNotMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ )
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.reduce { acc, value ->
+ acc or value
+ },
+ state = NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED,
+ )
+
+ val isZenMuted by
+ collectLastValue(
+ underTest.isZenMuted(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun ringer_allowed_ringNotMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ )
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.reduce { acc, value ->
+ acc or value
+ },
+ state = NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED,
+ )
+
+ val isZenMuted by
+ collectLastValue(underTest.isZenMuted(AudioStream(AudioManager.STREAM_RING)))
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
new file mode 100644
index 000000000000..a2f3ccb8c416
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.volume.domain.interactor
+
+import android.media.AudioManager
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.statusbar.notification.data.model.ZenMode
+import com.android.settingslib.statusbar.notification.data.repository.updateNotificationPolicy
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.audioRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioVolumeInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: AudioVolumeInteractor
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ underTest = AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor)
+
+ audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_NORMAL))
+
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Settings.Global.ZEN_MODE_OFF))
+ }
+ }
+
+ @Test
+ fun setMuted_mutesStream() {
+ with(kosmos) {
+ testScope.runTest {
+ val model by collectLastValue(underTest.getAudioStream(audioStream))
+
+ underTest.setMuted(audioStream, false)
+ runCurrent()
+ assertThat(model!!.isMuted).isFalse()
+
+ underTest.setMuted(audioStream, true)
+ runCurrent()
+ assertThat(model!!.isMuted).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun setVolume_changesVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ val model by collectLastValue(underTest.getAudioStream(audioStream))
+
+ underTest.setVolume(audioStream, 10)
+ runCurrent()
+ assertThat(model!!.volume).isEqualTo(10)
+
+ underTest.setVolume(audioStream, 20)
+ runCurrent()
+ assertThat(model!!.volume).isEqualTo(20)
+ }
+ }
+ }
+
+ @Test
+ fun ringMuted_notificationVolume_cantChange() {
+ with(kosmos) {
+ testScope.runTest {
+ val canChangeVolume by
+ collectLastValue(
+ underTest.canChangeVolume(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+
+ underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+ runCurrent()
+
+ assertThat(canChangeVolume).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun streamIsMuted_getStream_volumeZero() {
+ with(kosmos) {
+ testScope.runTest {
+ val model by collectLastValue(underTest.getAudioStream(audioStream))
+
+ underTest.setMuted(audioStream, true)
+ runCurrent()
+
+ assertThat(model!!.volume).isEqualTo(0)
+ }
+ }
+ }
+
+ @Test
+ fun streamIsZenMuted_getStream_lastAudibleVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setLastAudibleVolume(audioStream, 30)
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+ )
+
+ val model by collectLastValue(underTest.getAudioStream(audioStream))
+ runCurrent()
+
+ assertThat(model!!.volume).isEqualTo(30)
+ }
+ }
+ }
+
+ @Test
+ fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsZero() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
+ underTest.setMuted(AudioStream(AudioManager.STREAM_NOTIFICATION), true)
+
+ val model by
+ collectLastValue(
+ underTest.getAudioStream(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+ runCurrent()
+
+ assertThat(model!!.volume).isEqualTo(0)
+ }
+ }
+ }
+
+ @Test
+ fun ringerModeVibrate_getRingerStream_volumeIsZero() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
+
+ val model by
+ collectLastValue(
+ underTest.getAudioStream(AudioStream(AudioManager.STREAM_RING))
+ )
+ runCurrent()
+
+ assertThat(model!!.volume).isEqualTo(0)
+ }
+ }
+ }
+
+ private companion object {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
new file mode 100644
index 000000000000..a1e4fcafd3a4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.volume.panel.component.volume.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class VolumeSliderInteractorTest : SysuiTestCase() {
+
+ private val underTest = VolumeSliderInteractor()
+
+ @Test
+ fun translateValueToVolume() {
+ assertThat(underTest.translateValueToVolume(30f, volumeRange)).isEqualTo(3)
+ }
+
+ @Test
+ fun processVolumeToValue_muted_zero() {
+ assertThat(underTest.processVolumeToValue(3, volumeRange, null, true)).isEqualTo(0)
+ }
+
+ @Test
+ fun processVolumeToValue_currentValue_currentValue() {
+ assertThat(underTest.processVolumeToValue(3, volumeRange, 30f, false)).isEqualTo(30f)
+ }
+
+ @Test
+ fun processVolumeToValue_currentValueDiffersVolume_returnsTranslatedVolume() {
+ assertThat(underTest.processVolumeToValue(1, volumeRange, 60f, false)).isEqualTo(10f)
+ }
+
+ @Test
+ fun processVolumeToValue_currentValueDiffersNotEnoughVolume_returnsTranslatedVolume() {
+ assertThat(underTest.processVolumeToValue(1, volumeRange, 12f, false)).isEqualTo(12f)
+ }
+
+ private companion object {
+ val volumeRange = 0..10
+ }
+}
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index 37964158a4aa..2616e8ae25e8 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -67,12 +67,12 @@
android:gravity="start"/>
<!-- Buttons -->
- <LinearLayout
+ <com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="@dimen/screenrecord_buttons_margin_top">
- <TextView
+ <Button
android:id="@android:id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -83,13 +83,13 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
- <TextView
+ <Button
android:id="@android:id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/screenrecord_continue"
style="@style/Widget.Dialog.Button" />
- </LinearLayout>
+ </com.android.internal.widget.ButtonBarLayout>
</LinearLayout>
</ScrollView> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 495f20f27996..53ad344189d8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3129,6 +3129,8 @@
<!-- [CHAR LIMIT=25] Long label used by Note Task Shortcut -->
<string name="note_task_shortcut_long_label">Note-taking, <xliff:g id="note_taking_app" example="Note-taking App">%1$s</xliff:g></string>
+ <!-- [CHAR LIMIT=NONE] Output switch chip text during broadcasting -->
+ <string name="audio_sharing_description">Sharing audio</string>
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
<string name="broadcasting_description_is_broadcasting">Broadcasting</string>
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, title -->
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 00bbb20ed4f9..6af0fa069dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -40,6 +40,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.MediaOutputConstants;
import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.controls.util.MediaDataUtils;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.res.R;
@@ -74,7 +75,7 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate {
private final SystemUIDialog.Factory mSystemUIDialogFactory;
private final String mCurrentBroadcastApp;
private final String mOutputPackageName;
- private final Executor mExecutor;
+ private final Executor mBgExecutor;
private boolean mShouldLaunchLeBroadcastDialog;
private Button mSwitchBroadcast;
@@ -159,7 +160,7 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate {
MediaOutputDialogFactory mediaOutputDialogFactory,
@Nullable LocalBluetoothManager localBluetoothManager,
UiEventLogger uiEventLogger,
- Executor executor,
+ @Background Executor bgExecutor,
BroadcastSender broadcastSender,
SystemUIDialog.Factory systemUIDialogFactory,
@Assisted(CURRENT_BROADCAST_APP) String currentBroadcastApp,
@@ -171,7 +172,7 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate {
mCurrentBroadcastApp = currentBroadcastApp;
mOutputPackageName = outputPkgName;
mUiEventLogger = uiEventLogger;
- mExecutor = executor;
+ mBgExecutor = bgExecutor;
mBroadcastSender = broadcastSender;
if (DEBUG) {
@@ -187,7 +188,7 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate {
@Override
public void onStart(SystemUIDialog dialog) {
mDialogs.add(dialog);
- registerBroadcastCallBack(mExecutor, mBroadcastCallback);
+ registerBroadcastCallBack(mBgExecutor, mBroadcastCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 2af49cfbf1b1..b2699673f7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -143,7 +143,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
int availableHeight = mTextPreview.getHeight()
- (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
- mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+ mTextPreview.setMaxLines(Math.max(availableHeight / mTextPreview.getLineHeight(), 1));
return true;
});
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index d0044a4c029e..5397837423ff 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -29,6 +29,7 @@ import com.android.systemui.communal.shared.model.CommunalContentSize.FULL
import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
@@ -45,6 +46,7 @@ import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
@@ -59,6 +61,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
@@ -82,6 +85,7 @@ constructor(
communalSettingsInteractor: CommunalSettingsInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
+ private val userTracker: UserTracker,
sceneInteractor: SceneInteractor,
sceneContainerFlags: SceneContainerFlags,
@CommunalLog logBuffer: LogBuffer,
@@ -262,10 +266,16 @@ constructor(
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
+ /** All widgets present in db. */
+ val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+ isCommunalAvailable.flatMapLatest { available ->
+ if (!available) emptyFlow() else widgetRepository.communalWidgets
+ }
+
/** A list of widget content to be displayed in the communal hub. */
val widgetContent: Flow<List<CommunalContentModel.Widget>> =
widgetRepository.communalWidgets.map { widgets ->
- widgets.map Widget@{ widget ->
+ filterWidgetsByExistingUsers(widgets).map Widget@{ widget ->
return@Widget CommunalContentModel.Widget(
appWidgetId = widget.appWidgetId,
providerInfo = widget.providerInfo,
@@ -345,6 +355,19 @@ constructor(
return@combine ongoingContent
}
+ /**
+ * Filter and retain widgets associated with an existing user, safeguarding against displaying
+ * stale data following user deletion.
+ */
+ private fun filterWidgetsByExistingUsers(
+ list: List<CommunalWidgetContentModel>,
+ ): List<CommunalWidgetContentModel> {
+ val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
+ return list.filter { widget ->
+ currentUserIds.contains(widget.providerInfo.profile?.identifier)
+ }
+ }
+
companion object {
/**
* The user activity timeout which should be used when the communal hub is opened. A value
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 4ddd7681dd98..8390d62b23db 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -18,11 +18,14 @@ package com.android.systemui.communal.widgets
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -37,6 +40,7 @@ class CommunalAppWidgetHostStartable
constructor(
private val appWidgetHost: CommunalAppWidgetHost,
private val communalInteractor: CommunalInteractor,
+ private val userTracker: UserTracker,
@Background private val bgScope: CoroutineScope,
@Main private val uiDispatcher: CoroutineDispatcher
) : CoreStartable {
@@ -47,6 +51,14 @@ constructor(
.pairwise(false)
.filter { (previous, new) -> previous != new }
.onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
+ .sample(communalInteractor.communalWidgets, ::Pair)
+ .onEach { (withPrev, widgets) ->
+ val (_, isActive) = withPrev
+ // The validation is performed once the hub becomes active.
+ if (isActive) {
+ validateWidgetsAndDeleteOrphaned(widgets)
+ }
+ }
.launchIn(bgScope)
appWidgetHost.appWidgetIdToRemove
@@ -63,4 +75,15 @@ constructor(
appWidgetHost.stopListening()
}
}
+
+ /**
+ * Ensure the existence of all associated users for widgets, and remove widgets belonging to
+ * users who have been deleted.
+ */
+ private fun validateWidgetsAndDeleteOrphaned(widgets: List<CommunalWidgetContentModel>) {
+ val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
+ widgets
+ .filter { widget -> !currentUserIds.contains(widget.providerInfo.profile?.identifier) }
+ .onEach { widget -> communalInteractor.deleteWidget(id = widget.appWidgetId) }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 0f038e10dd4e..bc07b95c5d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -36,6 +36,7 @@ import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
@@ -47,16 +48,16 @@ import javax.inject.Inject
@SysUISingleton
class ControlActionCoordinatorImpl @Inject constructor(
- private val context: Context,
- private val bgExecutor: DelayableExecutor,
- @Main private val uiExecutor: DelayableExecutor,
- private val activityStarter: ActivityStarter,
- private val broadcastSender: BroadcastSender,
- private val keyguardStateController: KeyguardStateController,
- private val taskViewFactory: Optional<TaskViewFactory>,
- private val controlsMetricsLogger: ControlsMetricsLogger,
- private val vibrator: VibratorHelper,
- private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val context: Context,
+ @Background private val bgExecutor: DelayableExecutor,
+ @Main private val uiExecutor: DelayableExecutor,
+ private val activityStarter: ActivityStarter,
+ private val broadcastSender: BroadcastSender,
+ private val keyguardStateController: KeyguardStateController,
+ private val taskViewFactory: Optional<TaskViewFactory>,
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val vibrator: VibratorHelper,
+ private val controlsSettingsRepository: ControlsSettingsRepository,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index e8931770b15e..1157d97f2f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -126,6 +126,7 @@ import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.PolicyModule;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
@@ -358,6 +359,7 @@ public abstract class SystemUIModule {
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
@@ -376,6 +378,7 @@ public abstract class SystemUIModule {
visualInterruptionDecisionProvider,
zenModeController,
notifUserManager,
+ sensitiveNotificationProtectionController,
notifCollection,
notifPipeline,
sysUiState,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index e35c5a636bde..301942f6242b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -170,7 +170,6 @@ constructor(
KeyguardIndicationAreaBinder.bind(
notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
- aodAlphaViewModel,
indicationController,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 7c1368af652c..841f52d7aa64 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -23,7 +23,7 @@ import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
@@ -51,7 +51,6 @@ object KeyguardIndicationAreaBinder {
fun bind(
view: ViewGroup,
viewModel: KeyguardIndicationAreaViewModel,
- aodAlphaViewModel: AodAlphaViewModel,
indicationController: KeyguardIndicationController,
): DisposableHandle {
indicationController.setIndicationArea(view)
@@ -68,30 +67,10 @@ object KeyguardIndicationAreaBinder {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- if (keyguardBottomAreaRefactor()) {
- aodAlphaViewModel.alpha.collect { alpha ->
- view.apply {
- this.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
- this.alpha = alpha
- }
- }
- } else {
- viewModel.alpha.collect { alpha ->
- view.apply {
- this.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
- this.alpha = alpha
- }
- }
+ // Do not independently apply alpha, as [KeyguardRootViewModel] should work
+ // for this and all its children
+ if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) {
+ viewModel.alpha.collect { alpha -> view.alpha = alpha }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
index 78099d9f5d90..a53c6d77d328 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
@@ -50,6 +50,15 @@ class KeyguardIndicationArea(
)
}
+ override fun setAlpha(alpha: Float) {
+ super.setAlpha(alpha)
+
+ if (alpha == 0f) {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ }
private fun indicationTopRow(): KeyguardIndicationTextView {
return KeyguardIndicationTextView(context, attrs).apply {
id = R.id.keyguard_indication_text
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index ea05c1d878b8..3361343423a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -25,7 +25,6 @@ import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
-import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -37,7 +36,6 @@ class DefaultIndicationAreaSection
constructor(
private val context: Context,
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
- private val aodAlphaViewModel: AodAlphaViewModel,
private val indicationController: KeyguardIndicationController,
) : KeyguardSection() {
private val indicationAreaViewId = R.id.keyguard_indication_area
@@ -56,7 +54,6 @@ constructor(
KeyguardIndicationAreaBinder.bind(
constraintLayout.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
- aodAlphaViewModel,
indicationController,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 42d68bab49f8..f4d70a5e78c9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -30,6 +30,8 @@ import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.flags.Flags.enableLeAudioSharing
+import com.android.settingslib.flags.Flags.legacyLeAudioSharing
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.PhoneMediaDevice
@@ -332,14 +334,28 @@ constructor(
@WorkerThread
private fun updateCurrent() {
if (isLeAudioBroadcastEnabled()) {
- current =
- MediaDeviceData(
- /* enabled */ true,
- /* icon */ context.getDrawable(R.drawable.settings_input_antenna),
- /* name */ broadcastDescription,
- /* intent */ null,
- /* showBroadcastButton */ showBroadcastButton = true
- )
+ if (enableLeAudioSharing()) {
+ current =
+ MediaDeviceData(
+ enabled = false,
+ icon =
+ context.getDrawable(
+ com.android.settingslib.R.drawable.ic_bt_le_audio_sharing
+ ),
+ name = context.getString(R.string.audio_sharing_description),
+ intent = null,
+ showBroadcastButton = false
+ )
+ } else {
+ current =
+ MediaDeviceData(
+ /* enabled */ true,
+ /* icon */ context.getDrawable(R.drawable.settings_input_antenna),
+ /* name */ broadcastDescription,
+ /* intent */ null,
+ /* showBroadcastButton */ showBroadcastButton = true
+ )
+ }
} else {
val aboutToConnect = aboutToConnectDeviceOverride
if (
@@ -420,6 +436,7 @@ constructor(
@WorkerThread
private fun isLeAudioBroadcastEnabled(): Boolean {
+ if (!enableLeAudioSharing() && !legacyLeAudioSharing()) return false
val localBluetoothManager = localBluetoothManager.get()
if (localBluetoothManager != null) {
val profileManager = localBluetoothManager.profileManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
index 9206af28eeff..ba7d41008a01 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
@@ -300,10 +300,17 @@ constructor(
private fun setVisibility(view: ViewGroup?, newVisibility: Int) {
val currentMediaContainer = view ?: return
- val previousVisibility = currentMediaContainer.visibility
- currentMediaContainer.visibility = newVisibility
- if (previousVisibility != newVisibility && currentMediaContainer is MediaContainerView) {
- visibilityChangedListener?.invoke(newVisibility == View.VISIBLE)
+ val isVisible = newVisibility == View.VISIBLE
+
+ if (currentMediaContainer is MediaContainerView) {
+ val previousVisibility = currentMediaContainer.visibility
+
+ currentMediaContainer.setKeyguardVisibility(isVisible)
+ if (previousVisibility != newVisibility) {
+ visibilityChangedListener?.invoke(isVisible)
+ }
+ } else {
+ currentMediaContainer.visibility = newVisibility
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index e8ad4d325591..4e940f1f84da 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -260,7 +260,6 @@ public class MediaControlPanel {
private TurbulenceNoiseController mTurbulenceNoiseController;
private LoadingEffect mLoadingEffect;
private final GlobalSettings mGlobalSettings;
- private final Random mRandom = new Random();
private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig;
private boolean mWasPlaying = false;
private boolean mButtonClicked = false;
@@ -1294,13 +1293,14 @@ public class MediaControlPanel {
mMediaViewHolder.getTurbulenceNoiseView();
int width = targetView.getWidth();
int height = targetView.getHeight();
+ Random random = new Random();
return new TurbulenceNoiseAnimationConfig(
/* gridCount= */ 2.14f,
TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
- /* noiseOffsetX= */ mRandom.nextFloat(),
- /* noiseOffsetY= */ mRandom.nextFloat(),
- /* noiseOffsetZ= */ mRandom.nextFloat(),
+ /* noiseOffsetX= */ random.nextFloat(),
+ /* noiseOffsetY= */ random.nextFloat(),
+ /* noiseOffsetZ= */ random.nextFloat(),
/* noiseMoveSpeedX= */ 0.42f,
/* noiseMoveSpeedY= */ 0f,
TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 416eae1b2d0c..4f062afc2af7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -72,7 +72,7 @@ open class MediaTttChipControllerReceiver @Inject constructor(
context: Context,
logger: MediaTttReceiverLogger,
windowManager: WindowManager,
- mainExecutor: DelayableExecutor,
+ @Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
dumpManager: DumpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 72a5c468ea94..c1b20374dbac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -84,6 +84,10 @@ interface QSSceneAdapter {
*/
val qsHeight: Int
+ /** Compatibility for use by LockscreenShadeTransitionController. Matches default from [QS] */
+ val isQsFullyCollapsed: Boolean
+ get() = true
+
sealed interface State {
val isVisible: Boolean
@@ -165,6 +169,10 @@ constructor(
override val qsHeight: Int
get() = qsImpl.value?.qsHeight ?: 0
+ // If value is null, there's no QS and therefore it's fully collapsed.
+ override val isQsFullyCollapsed: Boolean
+ get() = qsImpl.value?.isFullyCollapsed ?: true
+
// Same config changes as in FragmentHostManager
private val interestingChanges =
InterestingConfigChanges(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index bee315261f89..fb5339df7212 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -31,10 +31,13 @@ import android.view.WindowManager
import android.view.WindowManagerGlobal
import com.android.app.tracing.coroutines.launch
import com.android.internal.infra.ServiceConnector
+import com.android.systemui.Flags.screenshotActionDismissSystemWindows
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.statusbar.phone.CentralSurfaces
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
@@ -46,9 +49,11 @@ class ActionIntentExecutor
@Inject
constructor(
private val context: Context,
+ private val activityManagerWrapper: ActivityManagerWrapper,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val displayTracker: DisplayTracker,
+ private val keyguardController: ScreenshotKeyguardController,
) {
/**
* Execute the given intent with startActivity while performing operations for screenshot action
@@ -74,7 +79,14 @@ constructor(
user: UserHandle,
overrideTransition: Boolean,
) {
- dismissKeyguard()
+ if (screenshotActionDismissSystemWindows()) {
+ keyguardController.dismiss()
+ activityManagerWrapper.closeSystemWindows(
+ CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT
+ )
+ } else {
+ dismissKeyguard()
+ }
if (user == myUserHandle()) {
withContext(mainDispatcher) { context.startActivity(intent, options) }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt
new file mode 100644
index 000000000000..7696bbe3763e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.screenshot
+
+import android.content.Context
+import android.content.Intent
+import com.android.internal.infra.ServiceConnector
+import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
+
+open class ScreenshotKeyguardController @Inject constructor(context: Context) {
+ private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+ ServiceConnector.Impl(
+ context,
+ Intent(context, ScreenshotProxyService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ context.userId,
+ IScreenshotProxy.Stub::asInterface
+ )
+
+ suspend fun dismiss() {
+ val completion = CompletableDeferred<Unit>()
+ val onDoneBinder =
+ object : IOnDoneCallback.Stub() {
+ override fun onDone(success: Boolean) {
+ completion.complete(Unit)
+ }
+ }
+ proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
+ completion.await()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 86f652389b42..d5ab3066bfde 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -17,15 +17,16 @@ package com.android.systemui.screenshot
import android.content.Intent
import android.os.IBinder
+import android.os.RemoteException
import android.util.Log
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shade.ShadeExpansionStateManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import com.android.app.tracing.coroutines.launch
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
@@ -56,7 +57,13 @@ constructor(
private suspend fun executeAfterDismissing(callback: IOnDoneCallback) =
withContext(mMainDispatcher) {
activityStarter.executeRunnableDismissingKeyguard(
- Runnable { callback.onDone(true) },
+ {
+ try {
+ callback.onDone(true)
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Failed to complete callback transaction", e)
+ }
+ },
null,
true /* dismissShade */,
true /* afterKeyguardGone */,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index f2fa0ef3f30f..125f7fc0619b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -121,7 +121,6 @@ open class UserTrackerImpl internal constructor(
@GuardedBy("callbacks")
private val callbacks: MutableList<DataItem> = ArrayList()
- private var beforeUserSwitchingJob: Job? = null
private var userSwitchingJob: Job? = null
private var afterUserSwitchingJob: Job? = null
@@ -194,14 +193,7 @@ open class UserTrackerImpl internal constructor(
private fun registerUserSwitchObserver() {
iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
override fun onBeforeUserSwitching(newUserId: Int) {
- if (isBackgroundUserSwitchEnabled) {
- beforeUserSwitchingJob?.cancel()
- beforeUserSwitchingJob = appScope.launch(backgroundContext) {
- handleBeforeUserSwitching(newUserId)
- }
- } else {
- handleBeforeUserSwitching(newUserId)
- }
+ handleBeforeUserSwitching(newUserId)
}
override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
@@ -233,15 +225,24 @@ open class UserTrackerImpl internal constructor(
@WorkerThread
protected open fun handleBeforeUserSwitching(newUserId: Int) {
- Assert.isNotMainThread()
setUserIdInternal(newUserId)
val list = synchronized(callbacks) {
callbacks.toList()
}
+ val latch = CountDownLatch(list.size)
list.forEach {
- it.callback.get()?.onBeforeUserSwitching(newUserId)
+ val callback = it.callback.get()
+ if (callback != null) {
+ it.executor.execute {
+ callback.onBeforeUserSwitching(newUserId)
+ latch.countDown()
+ }
+ } else {
+ latch.countDown()
+ }
}
+ latch.await()
}
@WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt
index 4d0552e7cb31..adca3f2d25d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt
@@ -21,9 +21,9 @@ import android.util.IndentingPrintWriter
import android.util.MathUtils
import androidx.annotation.FloatRange
import androidx.annotation.Px
-import com.android.systemui.res.R
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import dagger.assisted.Assisted
@@ -38,7 +38,7 @@ constructor(
context: Context,
configurationController: ConfigurationController,
dumpManager: DumpManager,
- @Assisted private val qsProvider: () -> QS,
+ @Assisted private val qsProvider: () -> QS?,
splitShadeStateController: SplitShadeStateController
) :
AbstractLockscreenShadeTransitionController(
@@ -48,7 +48,7 @@ constructor(
splitShadeStateController
) {
- private val qs: QS
+ private val qs: QS?
get() = qsProvider()
/**
@@ -135,7 +135,7 @@ constructor(
/* amount= */ MathUtils.saturate(qsDragDownAmount / qsSquishTransitionDistance)
)
isTransitioningToFullShade = dragDownAmount > 0.0f
- qs.setTransitionToFullShadeProgress(
+ qs?.setTransitionToFullShadeProgress(
isTransitioningToFullShade,
qsTransitionFraction,
qsSquishTransitionFraction
@@ -163,6 +163,6 @@ constructor(
@AssistedFactory
fun interface Factory {
- fun create(qsProvider: () -> QS): LockscreenShadeQsTransitionController
+ fun create(qsProvider: () -> QS?): LockscreenShadeQsTransitionController
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index a59d753971f6..4ee83497b368 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -31,6 +31,7 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeLockscreenInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
@@ -84,6 +85,7 @@ constructor(
private val splitShadeStateController: SplitShadeStateController,
private val shadeLockscreenInteractorLazy: Lazy<ShadeLockscreenInteractor>,
naturalScrollingSettingObserver: NaturalScrollingSettingObserver,
+ private val lazyQSSceneAdapter: Lazy<QSSceneAdapter>,
) : Dumpable {
private var pulseHeight: Float = 0f
@@ -93,7 +95,11 @@ constructor(
private var useSplitShade: Boolean = false
private lateinit var nsslController: NotificationStackScrollLayoutController
lateinit var centralSurfaces: CentralSurfaces
- lateinit var qS: QS
+
+ // When in scene container mode, this will be null. In that case, we use the adapter if needed
+ var qS: QS? = null
+ private val isQsFullyCollapsed: Boolean
+ get() = qS?.isFullyCollapsed ?: lazyQSSceneAdapter.get().isQsFullyCollapsed
/** A handler that handles the next keyguard dismiss animation. */
private var animationHandlerOnKeyguardDismiss: ((Long) -> Unit)? = null
@@ -286,7 +292,8 @@ constructor(
/** @return true if the interaction is accepted, false if it should be cancelled */
internal fun canDragDown(): Boolean {
return (statusBarStateController.state == StatusBarState.KEYGUARD ||
- nsslController.isInLockedDownShade()) && (qS.isFullyCollapsed || useSplitShade)
+ nsslController.isInLockedDownShade()) &&
+ (isQsFullyCollapsed || useSplitShade)
}
/** Called by the touch helper when when a gesture has completed all the way and released. */
@@ -410,7 +417,7 @@ constructor(
get() =
(statusBarStateController.getState() == StatusBarState.KEYGUARD &&
!keyguardBypassController.bypassEnabled &&
- (qS.isFullyCollapsed || useSplitShade))
+ (isQsFullyCollapsed || useSplitShade))
/** The amount in pixels that the user has dragged down. */
internal var dragDownAmount = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
index e47c914341a6..612a365dbe8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -27,7 +27,7 @@ constructor(
private val context: Context,
private val scrimController: ScrimController,
private val statusBarStateController: SysuiStatusBarStateController,
- @Assisted private val qSProvider: () -> QS,
+ @Assisted private val qSProvider: () -> QS?,
@Assisted private val nsslControllerProvider: () -> NotificationStackScrollLayoutController
) : LockScreenShadeOverScroller {
@@ -37,7 +37,7 @@ constructor(
private var maxOverScrollAmount = 0
private var previousOverscrollAmount = 0
- private val qS: QS
+ private val qS: QS?
get() = qSProvider()
private val nsslController: NotificationStackScrollLayoutController
@@ -90,7 +90,7 @@ constructor(
}
private fun applyOverscroll(overscrollAmount: Int) {
- qS.setOverScrollAmount(overscrollAmount)
+ qS?.setOverScrollAmount(overscrollAmount)
scrimController.setNotificationsOverScrollAmount(overscrollAmount)
nsslController.setOverScrollAmount(overscrollAmount)
}
@@ -109,7 +109,7 @@ constructor(
val animator = ValueAnimator.ofInt(previousOverscrollAmount, 0)
animator.addUpdateListener {
val overScrollAmount = it.animatedValue as Int
- qS.setOverScrollAmount(overScrollAmount)
+ qS?.setOverScrollAmount(overScrollAmount)
scrimController.setNotificationsOverScrollAmount(overScrollAmount)
nsslController.setOverScrollAmount(overScrollAmount)
}
@@ -143,7 +143,7 @@ constructor(
@AssistedFactory
fun interface Factory {
fun create(
- qSProvider: () -> QS,
+ qSProvider: () -> QS?,
nsslControllerProvider: () -> NotificationStackScrollLayoutController
): SplitShadeLockScreenOverScroller
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 8189fe03b2ed..dfe6cd5f25b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -25,6 +25,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -92,7 +93,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
@Inject
public VisualStabilityCoordinator(
- DelayableExecutor delayableExecutor,
+ @Background DelayableExecutor delayableExecutor,
DumpManager dumpManager,
HeadsUpManager headsUpManager,
ShadeAnimationInteractor shadeAnimationInteractor,
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 2a1ec3e9c64f..6548967c7462 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
@@ -16,12 +16,18 @@
package com.android.systemui.statusbar.notification.dagger;
+import android.app.NotificationManager;
import android.content.Context;
import android.service.notification.NotificationListenerService;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository;
+import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepositoryImpl;
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor;
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.res.R;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -79,13 +85,15 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import javax.inject.Provider;
+
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
-
-import javax.inject.Provider;
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.CoroutineScope;
/**
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
@@ -259,4 +267,22 @@ public interface NotificationsModule {
@ClassKey(VisualInterruptionDecisionProvider.class)
CoreStartable startVisualInterruptionDecisionProvider(
VisualInterruptionDecisionProvider provider);
+
+ @Provides
+ @SysUISingleton
+ public static NotificationsSoundPolicyRepository provideNotificationsSoundPolicyRepository(
+ Context context,
+ NotificationManager notificationManager,
+ @Application CoroutineScope coroutineScope,
+ @Background CoroutineContext coroutineContext) {
+ return new NotificationsSoundPolicyRepositoryImpl(context, notificationManager,
+ coroutineScope, coroutineContext);
+ }
+
+ @Provides
+ @SysUISingleton
+ public static NotificationsSoundPolicyInteractor provideNotificationsSoundPolicyInteractror(
+ NotificationsSoundPolicyRepository repository) {
+ return new NotificationsSoundPolicyInteractor(repository);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 76e5fd3bd4f2..a5f42bb99e10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -45,10 +45,12 @@ import javax.inject.Inject
* icons and keeping the icon assets themselves up to date as notifications change.
*
* TODO: Much of this code was copied whole-sale in order to get it out of NotificationEntry.
- * Long-term, it should probably live somewhere in the content inflation pipeline.
+ * Long-term, it should probably live somewhere in the content inflation pipeline.
*/
@SysUISingleton
-class IconManager @Inject constructor(
+class IconManager
+@Inject
+constructor(
private val notifCollection: CommonNotifCollection,
private val launcherApps: LauncherApps,
private val iconBuilder: IconBuilder
@@ -59,30 +61,30 @@ class IconManager @Inject constructor(
notifCollection.addCollectionListener(entryListener)
}
- private val entryListener = object : NotifCollectionListener {
- override fun onEntryInit(entry: NotificationEntry) {
- entry.addOnSensitivityChangedListener(sensitivityListener)
- }
+ private val entryListener =
+ object : NotifCollectionListener {
+ override fun onEntryInit(entry: NotificationEntry) {
+ entry.addOnSensitivityChangedListener(sensitivityListener)
+ }
- override fun onEntryCleanUp(entry: NotificationEntry) {
- entry.removeOnSensitivityChangedListener(sensitivityListener)
- }
+ override fun onEntryCleanUp(entry: NotificationEntry) {
+ entry.removeOnSensitivityChangedListener(sensitivityListener)
+ }
- override fun onRankingApplied() {
- // rankings affect whether a conversation is important, which can change the icons
- recalculateForImportantConversationChange()
+ override fun onRankingApplied() {
+ // rankings affect whether a conversation is important, which can change the icons
+ recalculateForImportantConversationChange()
+ }
}
- }
- private val sensitivityListener = NotificationEntry.OnSensitivityChangedListener {
- entry -> updateIconsSafe(entry)
- }
+ private val sensitivityListener =
+ NotificationEntry.OnSensitivityChangedListener { entry -> updateIconsSafe(entry) }
private fun recalculateForImportantConversationChange() {
for (entry in notifCollection.allNotifs) {
val isImportant = isImportantConversation(entry)
- if (entry.icons.areIconsAvailable &&
- isImportant != entry.icons.isImportantConversation
+ if (
+ entry.icons.areIconsAvailable && isImportant != entry.icons.isImportantConversation
) {
updateIconsSafe(entry)
}
@@ -97,34 +99,35 @@ class IconManager @Inject constructor(
* @throws InflationException Exception if required icons are not valid or specified
*/
@Throws(InflationException::class)
- fun createIcons(entry: NotificationEntry) = traceSection("IconManager.createIcons") {
- // Construct the status bar icon view.
- val sbIcon = iconBuilder.createIconView(entry)
- sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
-
- // Construct the shelf icon view.
- val shelfIcon = iconBuilder.createIconView(entry)
- shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
- shelfIcon.visibility = View.INVISIBLE
-
- // Construct the aod icon view.
- val aodIcon = iconBuilder.createIconView(entry)
- aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
- aodIcon.setIncreasedSize(true)
-
- // Set the icon views' icons
- val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
-
- try {
- setIcon(entry, normalIconDescriptor, sbIcon)
- setIcon(entry, sensitiveIconDescriptor, shelfIcon)
- setIcon(entry, sensitiveIconDescriptor, aodIcon)
- entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
- } catch (e: InflationException) {
- entry.icons = IconPack.buildEmptyPack(entry.icons)
- throw e
+ fun createIcons(entry: NotificationEntry) =
+ traceSection("IconManager.createIcons") {
+ // Construct the status bar icon view.
+ val sbIcon = iconBuilder.createIconView(entry)
+ sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+
+ // Construct the shelf icon view.
+ val shelfIcon = iconBuilder.createIconView(entry)
+ shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+ shelfIcon.visibility = View.INVISIBLE
+
+ // Construct the aod icon view.
+ val aodIcon = iconBuilder.createIconView(entry)
+ aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+ aodIcon.setIncreasedSize(true)
+
+ // Set the icon views' icons
+ val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+
+ try {
+ setIcon(entry, normalIconDescriptor, sbIcon)
+ setIcon(entry, sensitiveIconDescriptor, shelfIcon)
+ setIcon(entry, sensitiveIconDescriptor, aodIcon)
+ entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
+ } catch (e: InflationException) {
+ entry.icons = IconPack.buildEmptyPack(entry.icons)
+ throw e
+ }
}
- }
/**
* Update the notification icons.
@@ -133,33 +136,33 @@ class IconManager @Inject constructor(
* @throws InflationException Exception if required icons are not valid or specified
*/
@Throws(InflationException::class)
- fun updateIcons(entry: NotificationEntry) = traceSection("IconManager.updateIcons") {
- if (!entry.icons.areIconsAvailable) {
- return@traceSection
- }
- entry.icons.smallIconDescriptor = null
- entry.icons.peopleAvatarDescriptor = null
+ fun updateIcons(entry: NotificationEntry) =
+ traceSection("IconManager.updateIcons") {
+ if (!entry.icons.areIconsAvailable) {
+ return@traceSection
+ }
+ entry.icons.smallIconDescriptor = null
+ entry.icons.peopleAvatarDescriptor = null
- val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
- val notificationContentDescription = entry.sbn.notification?.let {
- iconBuilder.getIconContentDescription(it)
- }
+ val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+ val notificationContentDescription =
+ entry.sbn.notification?.let { iconBuilder.getIconContentDescription(it) }
- entry.icons.statusBarIcon?.let {
- it.setNotification(entry.sbn, notificationContentDescription)
- setIcon(entry, normalIconDescriptor, it)
- }
+ entry.icons.statusBarIcon?.let {
+ it.setNotification(entry.sbn, notificationContentDescription)
+ setIcon(entry, normalIconDescriptor, it)
+ }
- entry.icons.shelfIcon?.let {
- it.setNotification(entry.sbn, notificationContentDescription)
- setIcon(entry, normalIconDescriptor, it)
- }
+ entry.icons.shelfIcon?.let {
+ it.setNotification(entry.sbn, notificationContentDescription)
+ setIcon(entry, sensitiveIconDescriptor, it)
+ }
- entry.icons.aodIcon?.let {
- it.setNotification(entry.sbn, notificationContentDescription)
- setIcon(entry, sensitiveIconDescriptor, it)
+ entry.icons.aodIcon?.let {
+ it.setNotification(entry.sbn, notificationContentDescription)
+ setIcon(entry, sensitiveIconDescriptor, it)
+ }
}
- }
private fun updateIconsSafe(entry: NotificationEntry) {
try {
@@ -173,11 +176,12 @@ class IconManager @Inject constructor(
@Throws(InflationException::class)
private fun getIconDescriptors(entry: NotificationEntry): Pair<StatusBarIcon, StatusBarIcon> {
val iconDescriptor = getIconDescriptor(entry, redact = false)
- val sensitiveDescriptor = if (entry.isSensitive) {
- getIconDescriptor(entry, redact = true)
- } else {
- iconDescriptor
- }
+ val sensitiveDescriptor =
+ if (entry.isSensitive) {
+ getIconDescriptor(entry, redact = true)
+ } else {
+ iconDescriptor
+ }
return Pair(iconDescriptor, sensitiveDescriptor)
}
@@ -197,14 +201,15 @@ class IconManager @Inject constructor(
}
val icon =
- (if (showPeopleAvatar) {
- createPeopleAvatar(entry)
- } else {
- n.smallIcon
- }) ?: throw InflationException(
- "No icon in notification from " + entry.sbn.packageName)
-
- val ic = StatusBarIcon(
+ (if (showPeopleAvatar) {
+ createPeopleAvatar(entry)
+ } else {
+ n.smallIcon
+ })
+ ?: throw InflationException("No icon in notification from " + entry.sbn.packageName)
+
+ val ic =
+ StatusBarIcon(
entry.sbn.user,
entry.sbn.packageName,
icon,
@@ -282,8 +287,8 @@ class IconManager @Inject constructor(
/**
* Determines if this icon shows a conversation based on the sensitivity of the icon, its
- * context and the user's indicated sensitivity preference. If we're using a fall back icon
- * of the small icon, we don't consider this to be showing a conversation
+ * context and the user's indicated sensitivity preference. If we're using a fall back icon of
+ * the small icon, we don't consider this to be showing a conversation
*
* @param iconView The icon that shows the conversation.
*/
@@ -293,19 +298,20 @@ class IconManager @Inject constructor(
iconDescriptor: StatusBarIcon
): Boolean {
val usedInSensitiveContext =
- iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon
+ iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon
val isSmallIcon = iconDescriptor.icon.equals(entry.sbn.notification.smallIcon)
- return isImportantConversation(entry) && !isSmallIcon &&
- (!usedInSensitiveContext || !entry.isSensitive)
+ return isImportantConversation(entry) &&
+ !isSmallIcon &&
+ (!usedInSensitiveContext || !entry.isSensitive)
}
private fun isImportantConversation(entry: NotificationEntry): Boolean {
// Also verify that the Notification is MessagingStyle, since we're going to access
// MessagingStyle-specific data (EXTRA_MESSAGES, EXTRA_MESSAGING_PERSON).
return entry.ranking.channel != null &&
- entry.ranking.channel.isImportantConversation &&
- entry.sbn.notification.isStyle(MessagingStyle::class.java) &&
- entry.key !in unimportantConversationKeys
+ entry.ranking.channel.isImportantConversation &&
+ entry.sbn.notification.isStyle(MessagingStyle::class.java) &&
+ entry.key !in unimportantConversationKeys
}
override fun setUnimportantConversations(keys: Collection<String>) {
@@ -323,8 +329,8 @@ private const val TAG = "IconManager"
interface ConversationIconManager {
/**
* Sets the complete current set of notification keys which should (for the purposes of icon
- * presentation) be considered unimportant. This tells the icon manager to remove the avatar
- * of a group from which the priority notification has been removed.
+ * presentation) be considered unimportant. This tells the icon manager to remove the avatar of
+ * a group from which the priority notification has been removed.
*/
fun setUnimportantConversations(keys: Collection<String>)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index bae5baaf91ed..5551ab46262c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -22,6 +22,8 @@ import android.graphics.Canvas
import android.graphics.Path
import android.graphics.RectF
import android.util.AttributeSet
+import android.util.Log
+import com.android.systemui.Flags
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -87,4 +89,63 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie
) {
// No animation, it doesn't need it, this would be local
}
+
+ override fun setVisibility(visibility: Int) {
+ if (Flags.bindKeyguardMediaVisibility()) {
+ if (isVisibilityValid(visibility)) {
+ super.setVisibility(visibility)
+ }
+ } else {
+ super.setVisibility(visibility)
+ }
+
+ assertMediaContainerVisibility(visibility)
+ }
+
+ /**
+ * visibility should be aligned with MediaContainerView visibility on the keyguard.
+ */
+ private fun isVisibilityValid(visibility: Int): Boolean {
+ val currentViewState = viewState as? MediaContainerViewState ?: return true
+ val shouldBeGone = !currentViewState.shouldBeVisible
+ return if (shouldBeGone) visibility == GONE else visibility != GONE
+ }
+
+ /**
+ * b/298213983
+ * MediaContainerView's visibility is changed to VISIBLE when it should be GONE.
+ * This method check this state and logs.
+ */
+ private fun assertMediaContainerVisibility(visibility: Int) {
+ val currentViewState = viewState
+
+ if (currentViewState is MediaContainerViewState) {
+ if (!currentViewState.shouldBeVisible && visibility == VISIBLE) {
+ Log.wtf("MediaContainerView", "MediaContainerView should be GONE " +
+ "but its visibility changed to VISIBLE")
+ }
+ }
+ }
+
+ fun setKeyguardVisibility(isVisible: Boolean) {
+ val currentViewState = viewState
+ if (currentViewState is MediaContainerViewState) {
+ currentViewState.shouldBeVisible = isVisible
+ }
+
+ visibility = if (isVisible) VISIBLE else GONE
+ }
+
+ override fun createExpandableViewState(): ExpandableViewState = MediaContainerViewState()
+
+ class MediaContainerViewState : ExpandableViewState() {
+ var shouldBeVisible: Boolean = false
+
+ override fun copyFrom(viewState: ViewState) {
+ super.copyFrom(viewState)
+ if (viewState is MediaContainerViewState) {
+ shouldBeVisible = viewState.shouldBeVisible
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7925a1ce97ee..e397a70ea1f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -573,7 +573,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* Do notifications dismiss with normal transitioning
*/
private boolean mDismissUsingRowTranslationX = true;
- private NotificationEntry mTopHeadsUpEntry;
+ private ExpandableNotificationRow mTopHeadsUpRow;
private long mNumHeadsUp;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
@@ -1688,10 +1688,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* is mainly used when dragging down from a heads up notification.
*/
private int getTopHeadsUpPinnedHeight() {
- if (mTopHeadsUpEntry == null) {
+ if (mTopHeadsUpRow == null) {
return 0;
}
- ExpandableNotificationRow row = mTopHeadsUpEntry.getRow();
+ ExpandableNotificationRow row = mTopHeadsUpRow;
if (row.isChildInGroup()) {
final NotificationEntry groupSummary =
mGroupMembershipManager.getGroupSummary(row.getEntry());
@@ -1872,8 +1872,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (slidingChild instanceof ExpandableNotificationRow row) {
NotificationEntry entry = row.getEntry();
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
- && mTopHeadsUpEntry.getRow() != row
- && mGroupMembershipManager.getGroupSummary(mTopHeadsUpEntry) != entry) {
+ && mTopHeadsUpRow != row
+ && mGroupMembershipManager.getGroupSummary(mTopHeadsUpRow.getEntry())
+ != entry) {
continue;
}
return row.getViewAtPosition(touchY - childTop);
@@ -5724,8 +5725,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mShelf.updateAppearance();
}
- void setTopHeadsUpEntry(NotificationEntry topEntry) {
- mTopHeadsUpEntry = topEntry;
+ void setTopHeadsUpRow(ExpandableNotificationRow topHeadsUpRow) {
+ mTopHeadsUpRow = topHeadsUpRow;
}
void setNumHeadsUp(long numHeadsUp) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 8dfac8617ff1..7c138776d5a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -692,7 +692,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
long numEntries = mHeadsUpManager.getAllEntries().count();
NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
mView.setNumHeadsUp(numEntries);
- mView.setTopHeadsUpEntry(topEntry);
+ mView.setTopHeadsUpRow(topEntry != null ? topEntry.getRow() : null);
generateHeadsUpAnimation(entry, isHeadsUp);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 634de7a17ef7..1ef9a8f3d7ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -853,7 +853,7 @@ public class StackScrollAlgorithm {
}
}
if (row.isHeadsUpAnimatingAway()) {
- if (NotificationsImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) {
if (shouldHunAppearFromBottom(ambientState, childState)) {
// move to the bottom of the screen
childState.setYTranslation(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b772158b0825..db15144340e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1375,7 +1375,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|| !mKeyguardStateController.canDismissLockScreen()
|| mKeyguardViewMediator.isAnySimPinSecure()
|| (mQsController.getExpanded() && trackingTouch)
- || mShadeSurface.getBarState() == StatusBarState.SHADE_LOCKED) {
+ || mShadeSurface.getBarState() == StatusBarState.SHADE_LOCKED
+ // This last one causes a race condition when the shade resets. Don't send a 0
+ // and let StatusBarStateController process a keyguard state change instead
+ || 1f - fraction == 0f) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4fd33ba458d8..5610ed926f70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -55,6 +55,7 @@ import com.android.systemui.EventLogTags;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
@@ -138,7 +139,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
Context context,
@DisplayId int displayId,
Handler mainThreadHandler,
- Executor uiBgExecutor,
+ @Background Executor uiBgExecutor,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManager headsUpManager,
ActivityStarter activityStarter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index f73d089c36b9..3e3ea855ccf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -315,8 +315,8 @@ constructor(
// TTL for satellite polling is one hour
const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 60
- // Let the system boot up (5s) and stabilize before we check for system support
- const val MIN_UPTIME: Long = 1000 * 5
+ // Let the system boot up and stabilize before we check for system support
+ const val MIN_UPTIME: Long = 1000 * 60
private const val TAG = "DeviceBasedSatelliteRepo"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
index a078dd5cf28c..2ad4d361df1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
@@ -23,6 +23,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
@@ -34,7 +35,7 @@ import javax.inject.Inject
class BatteryStateNotifier @Inject constructor(
val controller: BatteryController,
val noMan: NotificationManager,
- val delayableExecutor: DelayableExecutor,
+ @Background val delayableExecutor: DelayableExecutor,
val context: Context
) : BatteryController.BatteryStateChangeCallback {
var stateUnknown = false
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 6124f6383fff..2cad8442e3ba 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -124,15 +124,6 @@ public abstract class SysUIConcurrencyModule {
}
/**
- * Provide a Background-Thread Executor by default.
- */
- @Provides
- @SysUISingleton
- public static Executor provideExecutor(@Background Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
* Provide a BroadcastRunning Executor (for sending and receiving broadcasts).
*/
@Provides
@@ -174,15 +165,6 @@ public abstract class SysUIConcurrencyModule {
}
/**
- * Provide a Background-Thread Executor by default.
- */
- @Provides
- @SysUISingleton
- public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
* Provide a Background-Thread Executor.
*/
@Provides
@@ -193,15 +175,6 @@ public abstract class SysUIConcurrencyModule {
}
/**
- * Provide a Background-Thread Executor by default.
- */
- @Provides
- @SysUISingleton
- public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) {
- return new RepeatableExecutorImpl(exec);
- }
-
- /**
* Provide a Background-Thread Executor.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 9b72eb710588..5979f3e60cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -28,6 +28,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
@@ -94,10 +95,12 @@ public class PersistentConnectionManager<T> implements Dumpable {
}
};
+ // TODO: b/326449074 - Ensure the DelayableExecutor is on the correct thread, and update the
+ // qualifier (to @Main) or name (to bgExecutor) to be consistent with that.
@Inject
public PersistentConnectionManager(
SystemClock clock,
- DelayableExecutor mainExecutor,
+ @Background DelayableExecutor mainExecutor,
DumpManager dumpManager,
@Named(DUMPSYS_NAME) String dumpsysName,
@Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index f6fd519ed723..8431fbcd8bad 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,17 +16,16 @@
package com.android.systemui.volume.dagger
-import android.app.NotificationManager
import android.content.Context
import android.media.AudioManager
import com.android.settingslib.media.data.repository.SpatializerRepository
import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
-import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository
-import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepositoryImpl
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.settingslib.volume.shared.AudioManagerEventsReceiverImpl
import com.android.systemui.dagger.qualifiers.Application
@@ -62,6 +61,13 @@ interface AudioModule {
AudioModeInteractor(repository)
@Provides
+ fun provideAudioVolumeInteractor(
+ audioRepository: AudioRepository,
+ notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
+ ): AudioVolumeInteractor =
+ AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor)
+
+ @Provides
fun provdieSpatializerRepository(
audioManager: AudioManager,
@Background backgroundContext: CoroutineContext,
@@ -71,19 +77,5 @@ interface AudioModule {
@Provides
fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
SpatializerInteractor(repository)
-
- @Provides
- fun provideNotificationsSoundPolicyRepository(
- context: Context,
- notificationManager: NotificationManager,
- @Background coroutineContext: CoroutineContext,
- @Application coroutineScope: CoroutineScope,
- ): NotificationsSoundPolicyRepository =
- NotificationsSoundPolicyRepositoryImpl(
- context,
- notificationManager,
- coroutineScope,
- coroutineContext,
- )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index bf9963d13959..d134e60ef72f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -18,8 +18,10 @@ package com.android.systemui.volume.dagger
import android.media.session.MediaSessionManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
+import com.android.settingslib.volume.domain.interactor.LocalMediaInteractor
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -44,6 +46,19 @@ interface MediaDevicesModule {
@Provides
@SysUISingleton
+ fun provideLocalMediaRepository(
+ factory: LocalMediaRepositoryFactory
+ ): LocalMediaRepository = factory.create(null)
+
+ @Provides
+ @SysUISingleton
+ fun provideLocalMediaInteractor(
+ repository: LocalMediaRepository,
+ @Application scope: CoroutineScope,
+ ): LocalMediaInteractor = LocalMediaInteractor(repository, scope)
+
+ @Provides
+ @SysUISingleton
fun provideMediaDeviceSessionRepository(
intentsReceiver: AudioManagerEventsReceiver,
mediaSessionManager: MediaSessionManager,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt
new file mode 100644
index 000000000000..6b62074e023d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.volume.panel.component.volume.domain.interactor
+
+import com.android.settingslib.volume.domain.interactor.LocalMediaInteractor
+import com.android.settingslib.volume.domain.model.RoutingSession
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides a remote media casting state. */
+@VolumePanelScope
+class CastVolumeInteractor
+@Inject
+constructor(
+ @VolumePanelScope private val coroutineScope: CoroutineScope,
+ private val localMediaInteractor: LocalMediaInteractor,
+) {
+
+ /** Returns a list of [RoutingSession] to show in the UI. */
+ val remoteRoutingSessions: StateFlow<List<RoutingSession>> =
+ localMediaInteractor.remoteRoutingSessions
+ .map { it.filter { routingSession -> routingSession.isVolumeSeekBarEnabled } }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, emptyList())
+
+ /** Sets [routingSession] volume to [volume]. */
+ suspend fun setVolume(routingSession: RoutingSession, volume: Int) {
+ localMediaInteractor.adjustSessionVolume(routingSession.routingSessionInfo.id, volume)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
new file mode 100644
index 000000000000..52736c6cb08b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.volume.panel.component.volume.domain.interactor
+
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+
+/** Converts from slider value to volume and back. */
+@VolumePanelScope
+class VolumeSliderInteractor @Inject constructor() {
+
+ /** mimic percentage volume setting */
+ private val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
+
+ /**
+ * Translates [volume], that belongs to [volumeRange] to the value that belongs to
+ * [displayValueRange].
+ *
+ * [currentValue] is the raw value received from the slider. Returns [currentValue] when it
+ * translates to the same volume as [volume] parameter. This ensures smooth slider experience
+ * (avoids snapping when the user stops dragging).
+ */
+ fun processVolumeToValue(
+ volume: Int,
+ volumeRange: ClosedRange<Int>,
+ currentValue: Float?,
+ isMuted: Boolean,
+ ): Float {
+ if (isMuted) {
+ return 0f
+ }
+ val changedVolume: Int? = currentValue?.let { translateValueToVolume(it, volumeRange) }
+ return if (volume != volumeRange.start && volume == changedVolume) {
+ currentValue
+ } else {
+ translateToRange(
+ currentValue = volume.toFloat(),
+ currentRangeStart = volumeRange.start.toFloat(),
+ currentRangeEnd = volumeRange.endInclusive.toFloat(),
+ targetRangeStart = displayValueRange.start,
+ targetRangeEnd = displayValueRange.endInclusive,
+ )
+ }
+ }
+
+ /** Translates [value] from [displayValueRange] to volume that has [volumeRange]. */
+ fun translateValueToVolume(
+ value: Float,
+ volumeRange: ClosedRange<Int>,
+ ): Int {
+ return translateToRange(
+ currentValue = value,
+ currentRangeStart = displayValueRange.start,
+ currentRangeEnd = displayValueRange.endInclusive,
+ targetRangeStart = volumeRange.start.toFloat(),
+ targetRangeEnd = volumeRange.endInclusive.toFloat(),
+ )
+ .toInt()
+ }
+
+ /**
+ * Translates a value from one range to another.
+ *
+ * ```
+ * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
+ * Result: 37.5
+ * ```
+ */
+ private fun translateToRange(
+ currentValue: Float,
+ currentRangeStart: Float,
+ currentRangeEnd: Float,
+ targetRangeStart: Float,
+ targetRangeEnd: Float,
+ ): Float {
+ val currentRangeLength: Float = (currentRangeEnd - currentRangeStart)
+ val targetRangeLength: Float = targetRangeEnd - targetRangeStart
+ if (currentRangeLength == 0f || targetRangeLength == 0f) {
+ return 0f
+ }
+ val volumeFraction: Float = (currentValue - currentRangeStart) / currentRangeLength
+ return targetRangeStart + volumeFraction * targetRangeLength
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
new file mode 100644
index 000000000000..b97123b29b68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.volume.panel.component.volume.domain.model
+
+import com.android.settingslib.volume.shared.model.AudioStream
+
+/** The type of volume slider that can be shown at the UI. */
+sealed interface SliderType {
+
+ /** The slider represents one of the device volume streams. */
+ data class Stream(val stream: AudioStream) : SliderType
+
+ /** The represents media device casting volume. */
+ data object MediaDeviceCast : SliderType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 139d190ae63c..65dede83f3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -25,6 +25,7 @@ import static android.service.notification.NotificationListenerService.REASON_GR
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -69,6 +70,7 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleEntry;
@@ -102,6 +104,7 @@ public class BubblesManager {
private final NotificationVisibilityProvider mVisibilityProvider;
private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider;
private final NotificationLockscreenUserManager mNotifUserManager;
+ private final SensitiveNotificationProtectionController mSensitiveNotifProtectionController;
private final CommonNotifCollection mCommonNotifCollection;
private final NotifPipeline mNotifPipeline;
private final NotifPipelineFlags mNotifPipelineFlags;
@@ -111,6 +114,7 @@ public class BubblesManager {
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
private final List<NotifCallback> mCallbacks = new ArrayList<>();
private final StatusBarWindowCallback mStatusBarWindowCallback;
+ private final Runnable mSensitiveStateChangedListener;
private boolean mPanelExpanded;
/**
@@ -130,6 +134,7 @@ public class BubblesManager {
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
@@ -149,6 +154,7 @@ public class BubblesManager {
visualInterruptionDecisionProvider,
zenModeController,
notifUserManager,
+ sensitiveNotificationProtectionController,
notifCollection,
notifPipeline,
sysUiState,
@@ -173,6 +179,7 @@ public class BubblesManager {
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
@@ -188,6 +195,7 @@ public class BubblesManager {
mVisibilityProvider = visibilityProvider;
mVisualInterruptionDecisionProvider = visualInterruptionDecisionProvider;
mNotifUserManager = notifUserManager;
+ mSensitiveNotifProtectionController = sensitiveNotificationProtectionController;
mCommonNotifCollection = notifCollection;
mNotifPipeline = notifPipeline;
mNotifPipelineFlags = notifPipelineFlags;
@@ -251,6 +259,22 @@ public class BubblesManager {
};
notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
+ mSensitiveStateChangedListener = new Runnable() {
+ @Override
+ public void run() {
+ if (!screenshareNotificationHiding()) {
+ return;
+ }
+ bubbles.onSensitiveNotificationProtectionStateChanged(
+ mSensitiveNotifProtectionController.isSensitiveStateActive());
+ }
+ };
+
+ if (screenshareNotificationHiding()) {
+ mSensitiveNotifProtectionController
+ .registerSensitiveStateListener(mSensitiveStateChangedListener);
+ }
+
mSysuiProxy = new Bubbles.SysuiProxy() {
@Override
public void isNotificationPanelExpand(Consumer<Boolean> callback) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
index 1205dceb49e9..711f90f043ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -40,7 +39,6 @@ import org.mockito.MockitoAnnotations
class DefaultIndicationAreaSectionTest : SysuiTestCase() {
@Mock private lateinit var keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel
- @Mock private lateinit var aodAlphaViewModel: AodAlphaViewModel
@Mock private lateinit var indicationController: KeyguardIndicationController
private lateinit var underTest: DefaultIndicationAreaSection
@@ -52,7 +50,6 @@ class DefaultIndicationAreaSectionTest : SysuiTestCase() {
DefaultIndicationAreaSection(
context,
keyguardIndicationAreaViewModel,
- aodAlphaViewModel,
indicationController,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 14fe18289401..7f3d79f7e288 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -27,12 +27,16 @@ import android.media.RoutingSessionInfo
import android.media.session.MediaController
import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.settingslib.flags.Flags
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.PhoneMediaDevice
@@ -83,6 +87,7 @@ private const val NORMAL_APP_NAME = "NORMAL_APP_NAME"
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
public class MediaDeviceManagerTest : SysuiTestCase() {
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
private lateinit var manager: MediaDeviceManager
@Mock private lateinit var controllerFactory: MediaControllerFactory
@@ -668,7 +673,28 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
}
@Test
- fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting() {
+ fun onBroadcastStarted_flagOff_currentMediaDeviceDataIsBroadcasting() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(BROADCAST_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isFalse()
+ assertThat(data.enabled).isTrue()
+ assertThat(data.name).isEqualTo(DEVICE_NAME)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_legacy_currentMediaDeviceDataIsBroadcasting() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(true)
setupBroadcastPackage(BROADCAST_APP_NAME)
@@ -686,7 +712,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
}
@Test
- fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(true)
setupBroadcastPackage(NORMAL_APP_NAME)
@@ -703,6 +731,62 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStopped_legacy_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGone() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(false)
+ broadcastCallback.onBroadcastStopped(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(BROADCAST_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isFalse()
+ assertThat(data.enabled).isFalse()
+ assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(NORMAL_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isFalse()
+ assertThat(data.enabled).isFalse()
+ assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
fun onBroadcastStopped_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGone() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(false)
@@ -714,6 +798,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
val data = captureDeviceData(KEY)
assertThat(data.showBroadcastButton).isFalse()
+ assertThat(data.name?.equals(context.getString(R.string.audio_sharing_description)))
+ .isFalse()
}
private fun captureCallback(): LocalMediaManager.DeviceCallback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
new file mode 100644
index 000000000000..0c324706857f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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.screenshot
+
+import android.content.Intent
+import android.os.Process.myUserHandle
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+class ActionIntentExecutorTest : SysuiTestCase() {
+
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = StandardTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
+ private val testableContext = TestableContext(mContext)
+
+ private val activityManagerWrapper = mock<ActivityManagerWrapper>()
+ private val displayTracker = mock<DisplayTracker>()
+ private val keyguardController = mock<ScreenshotKeyguardController>()
+
+ private val actionIntentExecutor =
+ ActionIntentExecutor(
+ testableContext,
+ activityManagerWrapper,
+ testScope,
+ mainDispatcher,
+ displayTracker,
+ keyguardController,
+ )
+
+ @Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS)
+ fun launchIntent_callsCloseSystemWindows() =
+ testScope.runTest {
+ val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
+ val userHandle = myUserHandle()
+
+ actionIntentExecutor.launchIntent(intent, null, userHandle, false)
+ scheduler.advanceUntilIdle()
+
+ verify(activityManagerWrapper)
+ .closeSystemWindows(CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index 032ec7440923..774aa517672e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -371,7 +371,6 @@ class UserTrackerImplTest : SysuiTestCase() {
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
captor.value.onUserSwitching(newID, userSwitchingReply)
assertThat(callback.calledOnUserChanging).isEqualTo(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
index 0b4de345e2d7..402d9aab66bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
@@ -18,12 +18,13 @@ package com.android.systemui.statusbar
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -43,13 +44,15 @@ class LockscreenShadeQsTransitionControllerTest : SysuiTestCase() {
@get:Rule val expect: Expect = Expect.create()
@Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var qS: QS
+ private var qS: QS? = null
private lateinit var controller: LockscreenShadeQsTransitionController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ qS = mock()
+
setTransitionDistance(TRANSITION_DISTANCE)
setTransitionDelay(TRANSITION_DELAY)
setSquishTransitionDistance(SQUISH_TRANSITION_DISTANCE)
@@ -220,7 +223,7 @@ class LockscreenShadeQsTransitionControllerTest : SysuiTestCase() {
controller.dragDownAmount = rawDragAmount
- verify(qS)
+ verify(qS!!)
.setTransitionToFullShadeProgress(
/* isTransitioningToFullShade= */ true,
/* transitionFraction= */ controller.qsTransitionFraction,
@@ -228,6 +231,15 @@ class LockscreenShadeQsTransitionControllerTest : SysuiTestCase() {
)
}
+ @Test
+ fun nullQS_onDragAmountChanged_doesNotCrash() {
+ qS = null
+
+ val rawDragAmount = 200f
+
+ controller.dragDownAmount = rawDragAmount
+ }
+
private fun setTransitionDistance(value: Int) {
overrideResource(R.dimen.lockscreen_shade_qs_transition_distance, value)
configurationController.notifyConfigurationChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 91701b17b5e6..86116a073d9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -18,6 +18,7 @@ import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingOb
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeLockscreenInteractor
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -82,6 +83,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
private val testScope
get() = testComponent.testScope
+ private val qsSceneAdapter = FakeQSSceneAdapter({ mock() })
+
lateinit var row: ExpandableNotificationRow
@Mock lateinit var centralSurfaces: CentralSurfaces
@@ -189,6 +192,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
splitShadeStateController = ResourcesSplitShadeStateController(),
shadeLockscreenInteractorLazy = {shadeLockscreenInteractor},
naturalScrollingSettingObserver = naturalScrollingSettingObserver,
+ lazyQSSceneAdapter = { qsSceneAdapter }
)
transitionController.addCallback(transitionControllerCallback)
@@ -567,6 +571,16 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(-1f)
}
+ @Test
+ fun nullQs_canDragDownFromAdapter() {
+ transitionController.qS = null
+
+ qsSceneAdapter.isQsFullyCollapsed = true
+ assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
+ qsSceneAdapter.isQsFullyCollapsed = false
+ assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
+ }
+
private fun enableSplitShade() {
setSplitShadeEnabled(true)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
index 81d5c4d52b74..700fb1ec332c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
@@ -9,6 +9,7 @@ import com.android.systemui.plugins.qs.QS
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -31,7 +32,7 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock private lateinit var qS: QS
+ private var qS: QS? = null
@Mock private lateinit var nsslController: NotificationStackScrollLayoutController
@Mock private lateinit var dumpManager: DumpManager
@@ -40,6 +41,7 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ qS = mock()
whenever(nsslController.height).thenReturn(1800)
@@ -92,7 +94,7 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
setDragAmount(1000f)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
setDragAmount(999f)
- reset(qS, scrimController, nsslController)
+ reset(qS!!, scrimController, nsslController)
setDragAmount(998f)
setDragAmount(997f)
@@ -100,8 +102,15 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
verifyNoMoreOverScrollChanges()
}
+ @Test
+ fun qsNull_applyOverscroll_doesNotCrash() {
+ qS = null
+
+ setDragAmount(100f)
+ }
+
private fun verifyOverScrollPerformed() {
- verify(qS).setOverScrollAmount(intThat { it > 0 })
+ verify(qS!!).setOverScrollAmount(intThat { it > 0 })
verify(scrimController).setNotificationsOverScrollAmount(intThat { it > 0 })
verify(nsslController).setOverScrollAmount(intThat { it > 0 })
}
@@ -109,7 +118,7 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
private fun verifyOverScrollResetToZero() {
// Might be more than once as the animator might have multiple values close to zero that
// round down to zero.
- verify(qS, atLeast(1)).setOverScrollAmount(0)
+ verify(qS!!, atLeast(1)).setOverScrollAmount(0)
verify(scrimController, atLeast(1)).setNotificationsOverScrollAmount(0)
verify(nsslController, atLeast(1)).setOverScrollAmount(0)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index 8b99811e3d5f..a12806b9cc99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -167,6 +167,20 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
+ fun testUpdateIcons_sensitiveImportantConversation() {
+ val entry =
+ notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
+ entry?.setSensitive(true, true)
+ entry?.channel?.isImportantConversation = true
+ entry?.let { iconManager.createIcons(it) }
+ // Updating the icons after creation shouldn't break anything
+ entry?.let { iconManager.updateIcons(it) }
+ assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
+ assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
+ assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
+ }
+
+ @Test
fun testUpdateIcons_sensitivityChange() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 91a9da399ee8..995da8192f7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -222,6 +222,26 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAwayWhileDozing_yTranslationIsInset() {
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ ambientState.isDozing = true
+
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAwayWhileDozing_hasStackMargin_changesHunYTranslation() {
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ ambientState.isDozing = true
+
+ resetViewStates_stackMargin_changesHunYTranslation()
+ }
+
+ @Test
fun resetViewStates_hunsOverlapping_bottomHunClipped() {
val topHun = mockExpandableNotificationRow()
val bottomHun = mockExpandableNotificationRow()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
index 203096affd5c..08b49f026523 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
@@ -166,4 +166,28 @@ class TurbulenceNoiseControllerTest : SysuiTestCase() {
assertThat(config.color).isEqualTo(expectedColor)
}
}
+
+ @Test
+ fun play_initializesShader() {
+ val expectedNoiseOffset = floatArrayOf(0.1f, 0.2f, 0.3f)
+ val config =
+ TurbulenceNoiseAnimationConfig(
+ noiseOffsetX = expectedNoiseOffset[0],
+ noiseOffsetY = expectedNoiseOffset[1],
+ noiseOffsetZ = expectedNoiseOffset[2]
+ )
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+ val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ fakeExecutor.execute {
+ turbulenceNoiseController.play(SIMPLEX_NOISE, config)
+
+ assertThat(turbulenceNoiseView.noiseConfig).isNotNull()
+ val shader = turbulenceNoiseView.turbulenceNoiseShader!!
+ assertThat(shader).isNotNull()
+ assertThat(shader.noiseOffsetX).isEqualTo(expectedNoiseOffset[0])
+ assertThat(shader.noiseOffsetY).isEqualTo(expectedNoiseOffset[1])
+ assertThat(shader.noiseOffsetZ).isEqualTo(expectedNoiseOffset[2])
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 373d209426d4..d2e03861b022 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -801,8 +801,9 @@ public class VolumeDialogImplTest extends SysuiTestCase {
Log.d(TAG, "teardown: entered");
setOrientation(mOriginalOrientation);
Log.d(TAG, "teardown: after setOrientation");
- mAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration);
- Log.d(TAG, "teardown: after advanceTimeBy");
+ // Unclear why we used to do this, and it seems to be a source of flakes
+ // mAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration);
+ Log.d(TAG, "teardown: skipped advanceTimeBy");
mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration);
Log.d(TAG, "teardown: after moveTimeForward");
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index b25ac24093c5..a9308601a314 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -26,6 +26,7 @@ import static android.service.notification.NotificationListenerService.REASON_AP
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.google.common.truth.Truth.assertThat;
@@ -46,6 +47,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
@@ -73,6 +75,8 @@ import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
@@ -161,6 +165,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
@@ -257,6 +262,8 @@ public class BubblesTest extends SysuiTestCase {
private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock
private AuthController mAuthController;
+ @Mock
+ private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController;
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
@@ -272,6 +279,8 @@ public class BubblesTest extends SysuiTestCase {
private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
@Captor
private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
+ @Captor
+ private ArgumentCaptor<Runnable> mSensitiveStateChangedListener;
private BubblesManager mBubblesManager;
private TestableBubbleController mBubbleController;
@@ -594,6 +603,7 @@ public class BubblesTest extends SysuiTestCase {
interruptionDecisionProvider,
mZenModeController,
mLockscreenUserManager,
+ mSensitiveNotificationProtectionController,
mCommonNotifCollection,
mNotifPipeline,
mSysUiState,
@@ -2203,6 +2213,33 @@ public class BubblesTest extends SysuiTestCase {
assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
}
+ @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Test
+ public void doesNotRegisterSensitiveStateListener() {
+ verifyZeroInteractions(mSensitiveNotificationProtectionController);
+ }
+
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Test
+ public void registerSensitiveStateListener() {
+ verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any());
+ }
+
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Test
+ public void onSensitiveNotificationProtectionStateChanged() {
+ verify(mSensitiveNotificationProtectionController, atLeastOnce())
+ .registerSensitiveStateListener(mSensitiveStateChangedListener.capture());
+
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+ mSensitiveStateChangedListener.getValue().run();
+ verify(mBubbleController).onSensitiveNotificationProtectionStateChanged(true);
+
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+ mSensitiveStateChangedListener.getValue().run();
+ verify(mBubbleController).onSensitiveNotificationProtectionStateChanged(false);
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 6af08d3df554..f74cf71f9e7b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -31,6 +31,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.settings.userTracker
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
@@ -46,6 +47,7 @@ val Kosmos.communalInteractor by Fixture {
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
+ userTracker = userTracker,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
communalSettingsInteractor = communalSettingsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index b1581d1771fd..4d902fa35204 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -41,6 +41,8 @@ class FakeQSSceneAdapter(
private val _navBarPadding = MutableStateFlow<Int>(0)
val navBarPadding = _navBarPadding.asStateFlow()
+ override var isQsFullyCollapsed: Boolean = true
+
override suspend fun inflate(context: Context) {
_view.value = inflateDelegate(context)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterKosmos.kt
new file mode 100644
index 000000000000..00ab0b57fab7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.ui.adapter
+
+import android.view.View
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.fakeQSSceneAdapter by Kosmos.Fixture { FakeQSSceneAdapter({ mock<View>() }) }
+
+val Kosmos.qsSceneAdapter: QSSceneAdapter by Kosmos.Fixture { fakeQSSceneAdapter }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
index e5072f1c9f1c..e4a3896378f6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
@@ -26,6 +26,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.media.controls.ui.controller.mediaHierarchyManager
import com.android.systemui.plugins.activityStarter
+import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
@@ -61,5 +62,6 @@ val Kosmos.lockscreenShadeTransitionController by Fixture {
splitShadeStateController = splitShadeStateController,
shadeLockscreenInteractorLazy = { shadeLockscreenInteractor },
naturalScrollingSettingObserver = naturalScrollingSettingObserver,
+ lazyQSSceneAdapter = { qsSceneAdapter }
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt
new file mode 100644
index 000000000000..0614309a3910
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.settingslib.statusbar.notification.data.repository.FakeNotificationsSoundPolicyRepository
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.notificationsSoundPolicyRepository by
+ Kosmos.Fixture { FakeNotificationsSoundPolicyRepository() }
+
+val Kosmos.notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor by
+ Kosmos.Fixture { NotificationsSoundPolicyInteractor(notificationsSoundPolicyRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index fed3e171862d..a3ad2b87d5f5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -42,6 +42,7 @@ class FakeAudioRepository : AudioRepository {
get() = mutableCommunicationDevice.asStateFlow()
private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
+ private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf()
private fun getAudioStreamModelState(
audioStream: AudioStream
@@ -59,12 +60,9 @@ class FakeAudioRepository : AudioRepository {
)
}
- override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+ override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
getAudioStreamModelState(audioStream).asStateFlow()
- override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel =
- getAudioStreamModelState(audioStream).value
-
override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
}
@@ -73,6 +71,9 @@ class FakeAudioRepository : AudioRepository {
getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
}
+ override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int =
+ lastAudibleVolumes.getOrDefault(audioStream, 0)
+
fun setMode(newMode: Int) {
mutableMode.value = newMode
}
@@ -88,4 +89,8 @@ class FakeAudioRepository : AudioRepository {
fun setAudioStreamModel(model: AudioStreamModel) {
getAudioStreamModelState(model.audioStream).update { model }
}
+
+ fun setLastAudibleVolume(audioStream: AudioStream, volume: Int) {
+ lastAudibleVolumes[audioStream] = volume
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
index 7835fc89ea52..284bd55f15d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
@@ -27,20 +27,19 @@ class FakeLocalMediaRepository : LocalMediaRepository {
private val volumeBySession: MutableMap<String?, Int> = mutableMapOf()
- private val mutableMediaDevices = MutableStateFlow<Collection<MediaDevice>>(emptyList())
- override val mediaDevices: StateFlow<Collection<MediaDevice>>
+ private val mutableMediaDevices = MutableStateFlow<List<MediaDevice>>(emptyList())
+ override val mediaDevices: StateFlow<List<MediaDevice>>
get() = mutableMediaDevices.asStateFlow()
private val mutableCurrentConnectedDevice = MutableStateFlow<MediaDevice?>(null)
override val currentConnectedDevice: StateFlow<MediaDevice?>
get() = mutableCurrentConnectedDevice.asStateFlow()
- private val mutableRemoteRoutingSessions =
- MutableStateFlow<Collection<RoutingSession>>(emptyList())
- override val remoteRoutingSessions: StateFlow<Collection<RoutingSession>>
+ private val mutableRemoteRoutingSessions = MutableStateFlow<List<RoutingSession>>(emptyList())
+ override val remoteRoutingSessions: StateFlow<List<RoutingSession>>
get() = mutableRemoteRoutingSessions.asStateFlow()
- fun updateMediaDevices(devices: Collection<MediaDevice>) {
+ fun updateMediaDevices(devices: List<MediaDevice>) {
mutableMediaDevices.value = devices
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 4a4c29030f3c..eb3c55cb4ff6 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -255,6 +255,7 @@ android.view.Display
android.view.Display$HdrCapabilities
android.view.Display$Mode
android.view.DisplayInfo
+android.view.inputmethod.InputBinding
android.hardware.SerialManager
android.hardware.SerialManagerInternal
diff --git a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
index 9b27dd347caf..40b6ff01965e 100644
--- a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
@@ -232,9 +232,63 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
}
/** Returns true if this descriptor includes usages for the Braille display usage page 0x41. */
- private static boolean isBrailleDisplay(byte[] descriptor) {
- // TODO: b/316036493 - Check that descriptor includes 0x41 reports.
- return true;
+ @VisibleForTesting
+ static boolean isBrailleDisplay(byte[] descriptor) {
+ boolean foundMatch = false;
+ for (int i = 0; i < descriptor.length; i++) {
+ // HID Spec "6.2.2.2 Short Items" defines that the report descriptor is a collection of
+ // items: each item is a collection of bytes where the first byte defines info about
+ // the type of item and the following 0, 1, 2, or 4 bytes are data bytes for that item.
+ // All items in the HID descriptor are expected to be Short Items.
+ final byte itemInfo = descriptor[i];
+ if (!isHidItemShort(itemInfo)) {
+ Slog.w(LOG_TAG, "Item " + itemInfo + " declares unsupported long type");
+ return false;
+ }
+ final int dataSize = getHidItemDataSize(itemInfo);
+ if (i + dataSize >= descriptor.length) {
+ Slog.w(LOG_TAG, "Item " + itemInfo + " specifies size past the remaining bytes");
+ return false;
+ }
+ // The item we're looking for (usage page declaration) should have size 1.
+ if (dataSize == 1) {
+ final byte itemData = descriptor[i + 1];
+ if (isHidItemBrailleDisplayUsagePage(itemInfo, itemData)) {
+ foundMatch = true;
+ }
+ }
+ // Move to the next item by skipping past all data bytes in this item.
+ i += dataSize;
+ }
+ return foundMatch;
+ }
+
+ private static boolean isHidItemShort(byte itemInfo) {
+ // Info bits 7-4 describe the item type, and HID Spec "6.2.2.3 Long Items" says that long
+ // items always have type bits 1111. Otherwise, the item is a short item.
+ return (itemInfo & 0b1111_0000) != 0b1111_0000;
+ }
+
+ private static int getHidItemDataSize(byte itemInfo) {
+ // HID Spec "6.2.2.2 Short Items" says that info bits 0-1 specify the optional data size:
+ // 0, 1, 2, or 4 bytes.
+ return switch (itemInfo & 0b0000_0011) {
+ case 0b00 -> 0;
+ case 0b01 -> 1;
+ case 0b10 -> 2;
+ default -> 4;
+ };
+ }
+
+ private static boolean isHidItemBrailleDisplayUsagePage(byte itemInfo, byte itemData) {
+ // From HID Spec "6.2.2.7 Global Items"
+ final byte usagePageType = 0b0000_0100;
+ // From HID Usage Tables version 1.2.
+ final byte brailleDisplayUsagePage = 0x41;
+ // HID Spec "6.2.2.2 Short Items" says item info bits 2-7 describe the type and
+ // function of the item.
+ final byte itemType = (byte) (itemInfo & 0b1111_1100);
+ return itemType == usagePageType && itemData == brailleDisplayUsagePage;
}
/**
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 71f2b9e8e10b..e9f959f4b9eb 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -35,6 +35,15 @@ flag {
}
flag {
+ name: "enable_v_to_u_restore_for_system_components_in_allowlist"
+ namespace: "onboarding"
+ description: "Enables system components to opt in to support restore in V to U downgrade "
+ "scenario without opting in for restoreAnyVersion."
+ bug: "324233962"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_increase_datatypes_for_agent_logging"
namespace: "onboarding"
description: "Increase the number of a supported datatypes that an agent can define for its "
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 9f0deea503cf..6e98e68601f6 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -177,6 +177,10 @@ public class PackageManagerBackupAgent extends BackupAgent {
return mHasMetadata;
}
+ public int getSourceSdk() {
+ return mStoredSdkVersion;
+ }
+
public Metadata getRestoredMetadata(String packageName) {
if (mRestoredSignatures == null) {
Slog.w(TAG, "getRestoredMetadata() before metadata read!");
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index d85dd879e21d..e666442af9c9 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -44,6 +44,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
+import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -51,6 +52,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
@@ -82,6 +84,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -158,6 +161,12 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// When finished call listener
private final OnTaskFinishedListener mListener;
+ // List of packages that support V-> U downgrade but do not have RestoreAnyVersion set to true.
+ private List<String> mVToUAllowlist;
+
+ // List of packages that have RestoreAnyVersion set to true but do not support V-> U downgrade.
+ private List<String> mVToUDenylist;
+
// Key/value: bookkeeping about staged data and files for agent access
private File mBackupDataName;
private File mStageName;
@@ -172,7 +181,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
@VisibleForTesting
PerformUnifiedRestoreTask(
UserBackupManagerService backupManagerService,
- TransportConnection transportConnection) {
+ TransportConnection transportConnection,
+ String vToUAllowlist, String vToUDenyList) {
mListener = null;
mAgentTimeoutParameters = null;
mOperationStorage = null;
@@ -183,6 +193,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
mBackupEligibilityRules = null;
this.backupManagerService = backupManagerService;
mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(/* monitor= */ null);
+ mVToUAllowlist = createVToUList(vToUAllowlist);
+ mVToUDenylist = createVToUList(vToUDenyList);
}
// This task can assume that the wakelock is properly held for it and doesn't have to worry
@@ -223,6 +235,18 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
mBackupEligibilityRules = backupEligibilityRules;
+ mVToUAllowlist =
+ createVToUList(
+ Settings.Secure.getStringForUser(
+ backupManagerService.getContext().getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
+ mUserId));
+ mVToUDenylist =
+ createVToUList(
+ Settings.Secure.getStringForUser(
+ backupManagerService.getContext().getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_DENYLIST,
+ mUserId));
if (targetPackage != null) {
// Single package restore
@@ -636,60 +660,29 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Data is from a "newer" version of the app than we have currently
// installed. If the app has not declared that it is prepared to
// handle this case, we do not attempt the restore.
- if ((mCurrentPackage.applicationInfo.flags
- & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
- == 0) {
- String message =
- "Source version "
- + metaInfo.versionCode
- + " > installed version "
- + mCurrentPackage.getLongVersionCode();
- Slog.w(TAG, "Package " + pkgName + ": " + message);
- Bundle monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- null,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
- metaInfo.versionCode);
- monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- monitoringExtras,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
- false);
- monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
- mBackupManagerMonitorEventSender.monitorEvent(
- BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
- mCurrentPackage,
- BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
- monitoringExtras);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName, message);
- nextState = UnifiedRestoreState.RUNNING_QUEUE;
- return;
+ if (mIsSystemRestore
+ && isVToUDowngrade(mPmAgent.getSourceSdk(), android.os.Build.VERSION.SDK_INT)) {
+ if (isPackageEligibleForVToURestore(mCurrentPackage)) {
+ Slog.i(TAG, "Package " + pkgName
+ + " is eligible for V to U downgrade scenario");
+ } else {
+ String message = "Package not eligible for V to U downgrade scenario";
+ Slog.i(TAG, pkgName + " : " + message);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName, message);
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
+ }
} else {
- if (DEBUG) {
- Slog.v(
- TAG,
- "Source version "
- + metaInfo.versionCode
- + " > installed version "
- + mCurrentPackage.getLongVersionCode()
- + " but restoreAnyVersion");
+ if ((mCurrentPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
+ == 0) {
+ // Downgrade scenario with RestoreAnyVersion flag off
+ logDowngradeScenario(/* isRestoreAnyVersion */ false, metaInfo);
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
+ } else {
+ logDowngradeScenario(/* isRestoreAnyVersion */ true, metaInfo);
}
- Bundle monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- null,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
- metaInfo.versionCode);
- monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- monitoringExtras,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
- true);
- monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
- mBackupManagerMonitorEventSender.monitorEvent(
- BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
- mCurrentPackage,
- BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
- monitoringExtras);
}
}
@@ -1673,4 +1666,86 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
return mBackupManagerMonitorEventSender.putMonitoringExtra(
extras, BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, RESTORE);
}
+
+ // checks the sdk of the target/source device for a B&R operation.
+ // system components can opt in/out of V->U restore via allowlists. All other apps are
+ // not impacted
+ @SuppressWarnings("AndroidFrameworkCompatChange")
+ @VisibleForTesting
+ protected boolean isVToUDowngrade(int sourceSdk, int targetSdk) {
+ // We assume that if the source sdk is greater than U then the source is V.
+ return Flags.enableVToURestoreForSystemComponentsInAllowlist()
+ && (sourceSdk > Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ && (targetSdk == Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+ }
+
+ @VisibleForTesting
+ protected List<String> createVToUList(@Nullable String listString) {
+ // The allowlist/denylist is stored as a comma-separated list of package names
+ List<String> list = new ArrayList<>();
+ if (listString != null) {
+ list = Arrays.asList(listString.split(","));
+ }
+ return list;
+ }
+
+ @VisibleForTesting
+ protected boolean isPackageEligibleForVToURestore(PackageInfo mCurrentPackage) {
+ // A package is eligible for V to U downgrade restore if either:
+ // - The package has restoreAnyVersion set to false and is part of the V to U allowlist
+ // (and not in the denylist)
+ // - The package has restoreAnyVersion set to true and is not part of the denylist
+ if (mVToUDenylist.contains(mCurrentPackage.packageName)){
+ return false;
+ } else if ((mCurrentPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
+ == 0) {
+ // package has restoreAnyVersion set to false
+ return mVToUAllowlist.contains(mCurrentPackage.packageName);
+ } else {
+ // package has restoreAnyVersion set to true and is nor in denylist
+ return true;
+ }
+ }
+
+ private void logDowngradeScenario(boolean isRestoreAnyVersion, Metadata metaInfo) {
+ Bundle monitoringExtras =
+ mBackupManagerMonitorEventSender.putMonitoringExtra(
+ null,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
+ metaInfo.versionCode);
+ String message;
+ if (isRestoreAnyVersion) {
+ monitoringExtras =
+ mBackupManagerMonitorEventSender.putMonitoringExtra(
+ monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
+ true);
+ message = "Source version "
+ + metaInfo.versionCode
+ + " > installed version "
+ + mCurrentPackage.getLongVersionCode()
+ + " but restoreAnyVersion";
+ } else {
+ monitoringExtras =
+ mBackupManagerMonitorEventSender.putMonitoringExtra(
+ monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
+ false);
+ message = "Source version "
+ + metaInfo.versionCode
+ + " > installed version "
+ + mCurrentPackage.getLongVersionCode();
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, mCurrentPackage.packageName,
+ message);
+ }
+ Slog.i(TAG, "Package " + mCurrentPackage.packageName + ": " + message);
+ monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
+ mBackupManagerMonitorEventSender.monitorEvent(
+ BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+ }
+
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 767f54d6a8c7..966fe5bbeecc 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -120,6 +120,7 @@ public class SettingsToPropertiesMapper {
static final String[] sDeviceConfigAconfigScopes = new String[] {
"accessibility",
"android_core_networking",
+ "android_stylus",
"aoc",
"app_widgets",
"arc_next",
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index bba5ba35dbc7..631e7518b746 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -37,9 +37,11 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.utils.DebugUtils;
import com.android.server.display.utils.DeviceConfigParsingUtils;
+import com.android.server.display.utils.SensorUtils;
import java.io.PrintWriter;
import java.util.HashMap;
@@ -79,7 +81,7 @@ class BrightnessThrottler {
// Maps the throttling ID to the data. Sourced from DisplayDeviceConfig.
@NonNull
- private HashMap<String, ThermalBrightnessThrottlingData> mDdcThermalThrottlingDataMap;
+ private Map<String, ThermalBrightnessThrottlingData> mDdcThermalThrottlingDataMap;
// Current throttling data being used.
// Null if we do not support throttling.
@@ -97,6 +99,10 @@ class BrightnessThrottler {
// The brightness throttling configuration that should be used.
private String mThermalBrightnessThrottlingDataId;
+ // Temperature Sensor to be monitored for throttling.
+ @NonNull
+ private SensorData mTempSensor;
+
// This is a collection of brightness throttling data that has been written as overrides from
// the DeviceConfig. This will always take priority over the display device config data.
// We need to store the data for every display device, so we do not need to update this each
@@ -121,17 +127,19 @@ class BrightnessThrottler {
BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
String throttlingDataId,
- @NonNull HashMap<String, ThermalBrightnessThrottlingData>
- thermalBrightnessThrottlingDataMap) {
- this(new Injector(), handler, handler, throttlingChangeCallback,
- uniqueDisplayId, throttlingDataId, thermalBrightnessThrottlingDataMap);
+ @NonNull DisplayDeviceConfig displayDeviceConfig) {
+ this(new Injector(), handler, handler, throttlingChangeCallback, uniqueDisplayId,
+ throttlingDataId,
+ displayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ displayDeviceConfig.getTempSensor());
}
@VisibleForTesting
BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler,
Runnable throttlingChangeCallback, String uniqueDisplayId, String throttlingDataId,
- @NonNull HashMap<String, ThermalBrightnessThrottlingData>
- thermalBrightnessThrottlingDataMap) {
+ @NonNull Map<String, ThermalBrightnessThrottlingData>
+ thermalBrightnessThrottlingDataMap,
+ @NonNull SensorData tempSensor) {
mInjector = injector;
mHandler = handler;
@@ -147,7 +155,7 @@ class BrightnessThrottler {
mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
loadThermalBrightnessThrottlingDataFromDeviceConfig();
loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(mDdcThermalThrottlingDataMap,
- mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
+ tempSensor, mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
}
boolean deviceSupportsThrottling() {
@@ -180,12 +188,14 @@ class BrightnessThrottler {
}
void loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
- HashMap<String, ThermalBrightnessThrottlingData> ddcThrottlingDataMap,
+ Map<String, ThermalBrightnessThrottlingData> ddcThrottlingDataMap,
+ SensorData tempSensor,
String brightnessThrottlingDataId,
String uniqueDisplayId) {
mDdcThermalThrottlingDataMap = ddcThrottlingDataMap;
mThermalBrightnessThrottlingDataId = brightnessThrottlingDataId;
mUniqueDisplayId = uniqueDisplayId;
+ mTempSensor = tempSensor;
resetThermalThrottlingData();
}
@@ -310,7 +320,7 @@ class BrightnessThrottler {
}
if (deviceSupportsThrottling()) {
- mSkinThermalStatusObserver.startObserving();
+ mSkinThermalStatusObserver.startObserving(mTempSensor);
}
}
@@ -357,6 +367,7 @@ class BrightnessThrottler {
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
private final Injector mInjector;
private final Handler mHandler;
+ private SensorData mObserverTempSensor;
private IThermalService mThermalService;
private boolean mStarted;
@@ -371,28 +382,51 @@ class BrightnessThrottler {
if (DEBUG) {
Slog.d(TAG, "New thermal throttling status = " + temp.getStatus());
}
+
+ if (mObserverTempSensor.name != null
+ && !mObserverTempSensor.name.equals(temp.getName())) {
+ Slog.i(TAG, "Skipping thermal throttling notification as monitored sensor: "
+ + mObserverTempSensor.name
+ + " != notified sensor: "
+ + temp.getName());
+ return;
+ }
mHandler.post(() -> {
final @Temperature.ThrottlingStatus int status = temp.getStatus();
thermalStatusChanged(status);
});
}
- void startObserving() {
- if (mStarted) {
+ void startObserving(SensorData tempSensor) {
+ if (!mStarted || mObserverTempSensor == null) {
+ mObserverTempSensor = tempSensor;
+ registerThermalListener();
+ return;
+ }
+
+ String curType = mObserverTempSensor.type;
+ mObserverTempSensor = tempSensor;
+ if (curType.equals(tempSensor.type)) {
if (DEBUG) {
Slog.d(TAG, "Thermal status observer already started");
}
return;
}
+ stopObserving();
+ registerThermalListener();
+ }
+
+ void registerThermalListener() {
mThermalService = mInjector.getThermalService();
if (mThermalService == null) {
Slog.e(TAG, "Could not observe thermal status. Service not available");
return;
}
+ int temperatureType = SensorUtils.getSensorTemperatureType(mObserverTempSensor);
try {
// We get a callback immediately upon registering so there's no need to query
// for the current value.
- mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+ mThermalService.registerThermalEventListenerWithType(this, temperatureType);
mStarted = true;
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register thermal status listener", e);
@@ -418,6 +452,7 @@ class BrightnessThrottler {
void dump(PrintWriter writer) {
writer.println(" SkinThermalStatusObserver:");
writer.println(" mStarted: " + mStarted);
+ writer.println(" mObserverTempSensor: " + mObserverTempSensor);
if (mThermalService != null) {
writer.println(" ThermalService available");
} else {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d1374a5ab9dc..9b2dcc53f456 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -384,6 +384,10 @@ import javax.xml.datatype.DatatypeConfigurationException;
* </point>
* </supportedModes>
* </proxSensor>
+ * <tempSensor>
+ * <type>DISPLAY</type>
+ * <name>VIRTUAL-SKIN-DISPLAY</name>
+ * </tempSensor>
*
* <ambientLightHorizonLong>10001</ambientLightHorizonLong>
* <ambientLightHorizonShort>2001</ambientLightHorizonShort>
@@ -625,6 +629,12 @@ public class DisplayDeviceConfig {
@Nullable
private SensorData mProximitySensor;
+ // The details of the temperature sensor associated with this display.
+ // Throttling will be based on thermal status of this sensor.
+ // For empty values default back to sensor of TYPE_SKIN.
+ @NonNull
+ private SensorData mTempSensor;
+
private final List<RefreshRateLimitation> mRefreshRateLimitations =
new ArrayList<>(2 /*initialCapacity*/);
@@ -821,10 +831,10 @@ public class DisplayDeviceConfig {
private String mLowBlockingZoneThermalMapId = null;
private String mHighBlockingZoneThermalMapId = null;
- private final HashMap<String, ThermalBrightnessThrottlingData>
+ private final Map<String, ThermalBrightnessThrottlingData>
mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>();
- private final HashMap<String, PowerThrottlingData>
+ private final Map<String, PowerThrottlingData>
mPowerThrottlingDataMapByThrottlingId = new HashMap<>();
private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>>
@@ -1489,6 +1499,13 @@ public class DisplayDeviceConfig {
return mProximitySensor;
}
+ /**
+ * @return temperature sensor data associated with the display.
+ */
+ public SensorData getTempSensor() {
+ return mTempSensor;
+ }
+
boolean isAutoBrightnessAvailable() {
return mAutoBrightnessAvailable;
}
@@ -1539,7 +1556,7 @@ public class DisplayDeviceConfig {
/**
* @return brightness throttling configuration data for this display, for each throttling id.
*/
- public HashMap<String, ThermalBrightnessThrottlingData>
+ public Map<String, ThermalBrightnessThrottlingData>
getThermalBrightnessThrottlingDataMapByThrottlingId() {
return mThermalBrightnessThrottlingDataMapByThrottlingId;
}
@@ -1558,7 +1575,7 @@ public class DisplayDeviceConfig {
/**
* @return power throttling configuration data for this display, for each throttling id.
**/
- public HashMap<String, PowerThrottlingData>
+ public Map<String, PowerThrottlingData>
getPowerThrottlingDataMapByThrottlingId() {
return mPowerThrottlingDataMapByThrottlingId;
}
@@ -1871,6 +1888,7 @@ public class DisplayDeviceConfig {
+ "mAmbientLightSensor=" + mAmbientLightSensor
+ ", mScreenOffBrightnessSensor=" + mScreenOffBrightnessSensor
+ ", mProximitySensor=" + mProximitySensor
+ + ", mTempSensor=" + mTempSensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
+ ", mDensityMapping= " + mDensityMapping
+ ", mAutoBrightnessBrighteningLightDebounce= "
@@ -1972,6 +1990,7 @@ public class DisplayDeviceConfig {
mContext.getResources());
mScreenOffBrightnessSensor = SensorData.loadScreenOffBrightnessSensorConfig(config);
mProximitySensor = SensorData.loadProxSensorConfig(config);
+ mTempSensor = SensorData.loadTempSensorConfig(mFlags, config);
loadAmbientHorizonFromDdc(config);
loadBrightnessChangeThresholds(config);
loadAutoBrightnessConfigValues(config);
@@ -1999,6 +2018,7 @@ public class DisplayDeviceConfig {
loadBrightnessRampsFromConfigXml();
mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources());
mProximitySensor = SensorData.loadSensorUnspecifiedConfig();
+ mTempSensor = SensorData.loadTempSensorUnspecifiedConfig();
loadBrightnessChangeThresholdsFromXml();
loadAutoBrightnessConfigsFromConfigXml();
loadAutoBrightnessAvailableFromConfigXml();
@@ -2026,6 +2046,7 @@ public class DisplayDeviceConfig {
setSimpleMappingStrategyValues();
mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources());
mProximitySensor = SensorData.loadSensorUnspecifiedConfig();
+ mTempSensor = SensorData.loadTempSensorUnspecifiedConfig();
loadAutoBrightnessAvailableFromConfigXml();
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d5863a73a0c3..3965d55b0c28 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -861,6 +861,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
config.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ config.getTempSensor(),
mThermalBrightnessThrottlingDataId,
mUniqueDisplayId);
}
@@ -923,6 +924,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ mDisplayDeviceConfig.getTempSensor(),
mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
}
@@ -1996,7 +1998,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
postBrightnessChangeRunnable();
}, mUniqueDisplayId,
mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId,
- ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
+ ddConfig);
}
private void blockScreenOn() {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index bc5fcb449c95..18e8fab54e3e 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -38,6 +38,7 @@ import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -336,5 +337,10 @@ public class BrightnessClamperController {
public float getBrightnessWearBedtimeModeCap() {
return mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode();
}
+
+ @NonNull
+ public SensorData getTempSensor() {
+ return mDisplayDeviceConfig.getTempSensor();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
index 944a8a65693b..449825831182 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
@@ -35,8 +35,10 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.utils.DeviceConfigParsingUtils;
+import com.android.server.display.utils.SensorUtils;
import java.io.PrintWriter;
import java.util.List;
@@ -49,9 +51,8 @@ class BrightnessThermalClamper extends
BrightnessClamper<BrightnessThermalClamper.ThermalData> {
private static final String TAG = "BrightnessThermalClamper";
-
- @Nullable
- private final IThermalService mThermalService;
+ @NonNull
+ private final ThermalStatusObserver mThermalStatusObserver;
@NonNull
private final DeviceConfigParameterProvider mConfigParameterProvider;
// data from DeviceConfig, for all displays, for all dataSets
@@ -66,7 +67,6 @@ class BrightnessThermalClamper extends
// otherwise mDataFromDeviceConfig
@Nullable
private ThermalBrightnessThrottlingData mThermalThrottlingDataActive = null;
- private boolean mStarted = false;
@Nullable
private String mUniqueDisplayId = null;
@Nullable
@@ -74,14 +74,6 @@ class BrightnessThermalClamper extends
@Temperature.ThrottlingStatus
private int mThrottlingStatus = Temperature.THROTTLING_NONE;
- private final IThermalEventListener mThermalEventListener = new IThermalEventListener.Stub() {
- @Override
- public void notifyThrottling(Temperature temperature) {
- @Temperature.ThrottlingStatus int status = temperature.getStatus();
- mHandler.post(() -> thermalStatusChanged(status));
- }
- };
-
private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
try {
int status = DeviceConfigParsingUtils.parseThermalStatus(key);
@@ -105,12 +97,11 @@ class BrightnessThermalClamper extends
BrightnessThermalClamper(Injector injector, Handler handler,
ClamperChangeListener listener, ThermalData thermalData) {
super(handler, listener);
- mThermalService = injector.getThermalService();
mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+ mThermalStatusObserver = new ThermalStatusObserver(injector, handler);
mHandler.post(() -> {
setDisplayData(thermalData);
loadOverrideData();
- start();
});
}
@@ -139,32 +130,19 @@ class BrightnessThermalClamper extends
@Override
void stop() {
- if (!mStarted) {
- return;
- }
- try {
- mThermalService.unregisterThermalEventListener(mThermalEventListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to unregister thermal status listener", e);
- }
- mStarted = false;
+ mThermalStatusObserver.stopObserving();
}
@Override
void dump(PrintWriter writer) {
writer.println("BrightnessThermalClamper:");
- writer.println(" mStarted: " + mStarted);
- if (mThermalService != null) {
- writer.println(" ThermalService available");
- } else {
- writer.println(" ThermalService not available");
- }
writer.println(" mThrottlingStatus: " + mThrottlingStatus);
writer.println(" mUniqueDisplayId: " + mUniqueDisplayId);
writer.println(" mDataId: " + mDataId);
writer.println(" mDataOverride: " + mThermalThrottlingDataOverride);
writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig);
writer.println(" mDataActive: " + mThermalThrottlingDataActive);
+ mThermalStatusObserver.dump(writer);
super.dump(writer);
}
@@ -193,6 +171,7 @@ class BrightnessThermalClamper extends
Slog.wtf(TAG,
"Thermal throttling data is missing for thermalThrottlingDataId=" + mDataId);
}
+ mThermalStatusObserver.registerSensor(data.getTempSensor());
}
private void recalculateBrightnessCap() {
@@ -226,19 +205,91 @@ class BrightnessThermalClamper extends
}
}
- private void start() {
- if (mThermalService == null) {
- Slog.e(TAG, "Could not observe thermal status. Service not available");
- return;
+
+ private final class ThermalStatusObserver extends IThermalEventListener.Stub {
+ private final Injector mInjector;
+ private final Handler mHandler;
+ private IThermalService mThermalService;
+ private boolean mStarted;
+ private SensorData mObserverTempSensor;
+
+ ThermalStatusObserver(Injector injector, Handler handler) {
+ mInjector = injector;
+ mHandler = handler;
+ mStarted = false;
}
- try {
- // We get a callback immediately upon registering so there's no need to query
- // for the current value.
- mThermalService.registerThermalEventListenerWithType(mThermalEventListener,
- Temperature.TYPE_SKIN);
- mStarted = true;
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register thermal status listener", e);
+
+ void registerSensor(SensorData tempSensor) {
+ if (!mStarted || mObserverTempSensor == null) {
+ mObserverTempSensor = tempSensor;
+ registerThermalListener();
+ return;
+ }
+
+ String curType = mObserverTempSensor.type;
+ mObserverTempSensor = tempSensor;
+ if (curType.equals(tempSensor.type)) {
+ Slog.d(TAG, "Thermal status observer already started");
+ return;
+ }
+ stopObserving();
+ registerThermalListener();
+ }
+
+ void registerThermalListener() {
+ mThermalService = mInjector.getThermalService();
+ if (mThermalService == null) {
+ Slog.e(TAG, "Could not observe thermal status. Service not available");
+ return;
+ }
+ int temperatureType = SensorUtils.getSensorTemperatureType(mObserverTempSensor);
+ try {
+ // We get a callback immediately upon registering so there's no need to query
+ // for the current value.
+ mThermalService.registerThermalEventListenerWithType(this, temperatureType);
+ mStarted = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register thermal status listener", e);
+ }
+ }
+
+ @Override
+ public void notifyThrottling(Temperature temp) {
+ Slog.d(TAG, "New thermal throttling status = " + temp.getStatus());
+ if (mObserverTempSensor.name != null
+ && !mObserverTempSensor.name.equals(temp.getName())) {
+ Slog.i(TAG, "Skipping thermal throttling notification as monitored sensor: "
+ + mObserverTempSensor.name
+ + " != notified sensor: "
+ + temp.getName());
+ return;
+ }
+ @Temperature.ThrottlingStatus int status = temp.getStatus();
+ mHandler.post(() -> thermalStatusChanged(status));
+ }
+
+ void stopObserving() {
+ if (!mStarted) {
+ return;
+ }
+ try {
+ mThermalService.unregisterThermalEventListener(this);
+ mStarted = false;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to unregister thermal status listener", e);
+ }
+ mThermalService = null;
+ }
+
+ void dump(PrintWriter writer) {
+ writer.println(" ThermalStatusObserver:");
+ writer.println(" mStarted: " + mStarted);
+ writer.println(" mObserverTempSensor: " + mObserverTempSensor);
+ if (mThermalService != null) {
+ writer.println(" ThermalService available");
+ } else {
+ writer.println(" ThermalService not available");
+ }
}
}
@@ -251,6 +302,9 @@ class BrightnessThermalClamper extends
@Nullable
ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData();
+
+ @NonNull
+ SensorData getTempSensor();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java
index 3bb35bf7c49f..8e716f8380b6 100644
--- a/services/core/java/com/android/server/display/config/SensorData.java
+++ b/services/core/java/com/android/server/display/config/SensorData.java
@@ -22,6 +22,7 @@ import android.content.res.Resources;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
import java.util.ArrayList;
import java.util.Collections;
@@ -32,6 +33,9 @@ import java.util.List;
*/
public class SensorData {
+ public static final String TEMPERATURE_TYPE_DISPLAY = "DISPLAY";
+ public static final String TEMPERATURE_TYPE_SKIN = "SKIN";
+
@Nullable
public final String type;
@Nullable
@@ -143,6 +147,32 @@ public class SensorData {
}
/**
+ * Loads temperature sensor data for no config case. (Type: SKIN, Name: null)
+ */
+ public static SensorData loadTempSensorUnspecifiedConfig() {
+ return new SensorData(TEMPERATURE_TYPE_SKIN, null);
+ }
+
+ /**
+ * Loads temperature sensor data from given display config.
+ * If empty or null config given default to (Type: SKIN, Name: null)
+ */
+ public static SensorData loadTempSensorConfig(DisplayManagerFlags flags,
+ DisplayConfiguration config) {
+ SensorDetails sensorDetails = config.getTempSensor();
+ if (!flags.isSensorBasedBrightnessThrottlingEnabled() || sensorDetails == null) {
+ return new SensorData(TEMPERATURE_TYPE_SKIN, null);
+ }
+ String name = sensorDetails.getName();
+ String type = sensorDetails.getType();
+ if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
+ type = TEMPERATURE_TYPE_SKIN;
+ name = null;
+ }
+ return new SensorData(type, name);
+ }
+
+ /**
* Loads sensor unspecified config, this means system should use default sensor.
* See also {@link com.android.server.display.utils.SensorUtils}
*/
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 1ae255933f66..516d4b1d4125 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -121,6 +121,11 @@ public class DisplayManagerFlags {
Flags::refreshRateVotingTelemetry
);
+ private final FlagState mSensorBasedBrightnessThrottling = new FlagState(
+ Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING,
+ Flags::sensorBasedBrightnessThrottling
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -247,6 +252,10 @@ public class DisplayManagerFlags {
return mRefreshRateVotingTelemetry.isEnabled();
}
+ public boolean isSensorBasedBrightnessThrottlingEnabled() {
+ return mSensorBasedBrightnessThrottling.isEnabled();
+ }
+
/**
* dumps all flagstates
* @param pw printWriter
@@ -270,6 +279,7 @@ public class DisplayManagerFlags {
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mRefreshRateVotingTelemetry);
+ pw.println(" " + mSensorBasedBrightnessThrottling);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index c2f52b5ad8a0..63ab3a95822f 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -184,3 +184,11 @@ flag {
bug: "310029108"
is_fixed_read_only: true
}
+
+flag {
+ name: "sensor_based_brightness_throttling"
+ namespace: "display_manager"
+ description: "Feature flag for enabling brightness throttling using sensor from config."
+ bug: "294900859"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 8b9fe1083187..c63473a4b3d7 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -16,9 +16,11 @@
package com.android.server.display.utils;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.Sensor;
import android.hardware.SensorManager;
+import android.os.Temperature;
import android.text.TextUtils;
import com.android.server.display.config.SensorData;
@@ -70,4 +72,17 @@ public class SensorUtils {
return null;
}
+ /**
+ * Convert string temperature type to its corresponding integer value.
+ */
+ public static int getSensorTemperatureType(@NonNull SensorData tempSensor) {
+ if (tempSensor.type.equalsIgnoreCase(SensorData.TEMPERATURE_TYPE_DISPLAY)) {
+ return Temperature.TYPE_DISPLAY;
+ } else if (tempSensor.type.equalsIgnoreCase(SensorData.TEMPERATURE_TYPE_SKIN)) {
+ return Temperature.TYPE_SKIN;
+ }
+ throw new IllegalArgumentException(
+ "tempSensor doesn't support type: " + tempSensor.type);
+ }
+
}
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index 3ffd2e1dec71..b30f5ecb2dd5 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -313,7 +313,7 @@ public class FocusEventDebugView extends RelativeLayout {
case KeyEvent.KEYCODE_FORWARD_DEL:
return "\u2326";
case KeyEvent.KEYCODE_ESCAPE:
- return "ESC";
+ return "esc";
case KeyEvent.KEYCODE_DPAD_UP:
return "\u2191";
case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -330,6 +330,14 @@ public class FocusEventDebugView extends RelativeLayout {
return "\u2198";
case KeyEvent.KEYCODE_DPAD_DOWN_LEFT:
return "\u2199";
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ return "\u23ef";
+ case KeyEvent.KEYCODE_HOME:
+ return "\u25ef";
+ case KeyEvent.KEYCODE_BACK:
+ return "\u25c1";
+ case KeyEvent.KEYCODE_RECENT_APPS:
+ return "\u25a1";
default:
break;
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
index 84a59b4d28e4..7251ac42c582 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
@@ -43,6 +43,9 @@ import com.android.internal.inputmethod.InputBindResult;
* the given {@link Handler} thread if {@link IInputMethodClient} is not a proxy object. Be careful
* about its call ordering characteristics.</p>
*/
+// TODO(b/322895594) Mark this class to be host side test compatible once enabling fw/services in
+// Ravenwood (mark this class with @RavenwoodKeepWholeClass and #create with @RavenwoodReplace,
+// so Ravenwood can properly swap create method during test execution).
final class IInputMethodClientInvoker {
private static final String TAG = InputMethodManagerService.TAG;
private static final boolean DEBUG = InputMethodManagerService.DEBUG;
@@ -64,6 +67,16 @@ final class IInputMethodClientInvoker {
return new IInputMethodClientInvoker(inputMethodClient, isProxy, isProxy ? null : handler);
}
+ @AnyThread
+ @Nullable
+ static IInputMethodClientInvoker create$ravenwood(
+ @Nullable IInputMethodClient inputMethodClient, @NonNull Handler handler) {
+ if (inputMethodClient == null) {
+ return null;
+ }
+ return new IInputMethodClientInvoker(inputMethodClient, true, null);
+ }
+
private IInputMethodClientInvoker(@NonNull IInputMethodClient target,
boolean isProxy, @Nullable Handler handler) {
mTarget = target;
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index db70ce281eb5..a110e5637f82 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -40,6 +40,7 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Object mLock = new Object();
+ private final int mUniqueId;
@GuardedBy("mLock")
private final Session2Token mSessionToken;
@GuardedBy("mLock")
@@ -63,11 +64,13 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
MediaSessionService service,
Looper handlerLooper,
int pid,
- int policies) {
+ int policies,
+ int uniqueId) {
// The lock is required to prevent `Controller2Callback` from using partially initialized
// `MediaSession2Record.this`.
synchronized (mLock) {
mSessionToken = sessionToken;
+ mUniqueId = uniqueId;
mService = service;
mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
mController = new MediaController2.Builder(service.getContext(), sessionToken)
@@ -98,6 +101,13 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
}
@Override
+ public int getUniqueId() {
+ synchronized (mLock) {
+ return mUniqueId;
+ }
+ }
+
+ @Override
public String getPackageName() {
return mSessionToken.getPackageName();
}
@@ -200,6 +210,7 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
@Override
public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "uniqueId=" + mUniqueId);
pw.println(prefix + "token=" + mSessionToken);
pw.println(prefix + "controller=" + mController);
@@ -209,8 +220,7 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
@Override
public String toString() {
- // TODO(jaewan): Also add getId().
- return getPackageName() + " (userId=" + getUserId() + ")";
+ return getPackageName() + "/" + mUniqueId + " (userId=" + getUserId() + ")";
}
private class Controller2Callback extends MediaController2.ControllerCallback {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 53f780e4d19e..15527041d8eb 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -173,6 +173,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
private final int mUserId;
private final String mPackageName;
private final String mTag;
+ private final int mUniqueId;
private final Bundle mSessionInfo;
private final ControllerStub mController;
private final MediaSession.Token mSessionToken;
@@ -223,15 +224,25 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
private int mPolicies;
- public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
- ISessionCallback cb, String tag, Bundle sessionInfo,
- MediaSessionService service, Looper handlerLooper, int policies)
+ public MediaSessionRecord(
+ int ownerPid,
+ int ownerUid,
+ int userId,
+ String ownerPackageName,
+ ISessionCallback cb,
+ String tag,
+ int uniqueId,
+ Bundle sessionInfo,
+ MediaSessionService service,
+ Looper handlerLooper,
+ int policies)
throws RemoteException {
mOwnerPid = ownerPid;
mOwnerUid = ownerUid;
mUserId = userId;
mPackageName = ownerPackageName;
mTag = tag;
+ mUniqueId = uniqueId;
mSessionInfo = sessionInfo;
mController = new ControllerStub();
mSessionToken = new MediaSession.Token(ownerUid, mController);
@@ -292,6 +303,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
}
/**
+ * Get the unique id of this session record.
+ *
+ * @return a unique id of this session record.
+ */
+ @Override
+ public int getUniqueId() {
+ return mUniqueId;
+ }
+
+ /**
* Get the info for this session.
*
* @return Info that identifies this session.
@@ -703,7 +724,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
@Override
public String toString() {
- return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
+ return mPackageName + "/" + mTag + "/" + mUniqueId + " (userId=" + mUserId + ")";
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 99c8ea93936e..e53a2dbe8101 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -32,6 +32,13 @@ import java.io.PrintWriter;
public interface MediaSessionRecordImpl extends AutoCloseable {
/**
+ * Get the unique id of this session record.
+ *
+ * @return a unique id of this session record.
+ */
+ int getUniqueId();
+
+ /**
* Get the info for this session.
*
* @return Info that identifies this session.
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 757b26c45ab1..9e98a5809650 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -106,6 +106,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* System implementation of MediaSessionManager
@@ -155,6 +156,8 @@ public class MediaSessionService extends SystemService implements Monitor {
/* Maps uid with all user engaging session tokens associated to it */
private final SparseArray<Set<MediaSession.Token>> mUserEngagingSessions = new SparseArray<>();
+ private final AtomicInteger mNextMediaSessionRecordId = new AtomicInteger(1);
+
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
private FullUserRecord mCurrentFullUserRecord;
@@ -193,7 +196,8 @@ public class MediaSessionService extends SystemService implements Monitor {
MediaSessionService.this,
mRecordThread.getLooper(),
pid,
- /* policies= */ 0);
+ /* policies= */ 0,
+ /* uniqueId= */ mNextMediaSessionRecordId.getAndIncrement());
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user != null) {
@@ -794,9 +798,19 @@ public class MediaSessionService extends SystemService implements Monitor {
final MediaSessionRecord session;
try {
- session = new MediaSessionRecord(callerPid, callerUid, userId,
- callerPackageName, cb, tag, sessionInfo, this,
- mRecordThread.getLooper(), policies);
+ session =
+ new MediaSessionRecord(
+ callerPid,
+ callerUid,
+ userId,
+ callerPackageName,
+ cb,
+ tag,
+ /* uniqueId= */ mNextMediaSessionRecordId.getAndIncrement(),
+ sessionInfo,
+ this,
+ mRecordThread.getLooper(),
+ policies);
} catch (RemoteException e) {
throw new RuntimeException("Media Session owner died prematurely.", e);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fc7b87317344..3a7ac0bd659d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8206,7 +8206,7 @@ public class NotificationManagerService extends SystemService {
try {
return mTelecomManager.isInManagedCall()
|| mTelecomManager.isInSelfManagedCall(pkg,
- UserHandle.getUserHandleForUid(uid), /* hasCrossUserAccess */ true);
+ /* hasCrossUserAccess */ true);
} catch (IllegalStateException ise) {
// Telecom is not ready (this is likely early boot), so there are no calls.
return false;
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 9b347d572459..bf06c2aba405 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -15430,17 +15430,18 @@ public class BatteryStatsImpl extends BatteryStats {
mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState;
}
- final boolean compatibleConfig;
if (supportedStandardBuckets != null) {
final EnergyConsumerStats.Config config = new EnergyConsumerStats.Config(
supportedStandardBuckets, customBucketNames,
SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS,
getBatteryConsumerProcessStateNames());
- if (mEnergyConsumerStatsConfig == null) {
- compatibleConfig = true;
- } else {
- compatibleConfig = mEnergyConsumerStatsConfig.isCompatible(config);
+ if (mEnergyConsumerStatsConfig != null
+ && !mEnergyConsumerStatsConfig.isCompatible(config)) {
+ // Supported power buckets changed since last boot.
+ // Existing data is no longer reliable.
+ resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
+ RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE);
}
mEnergyConsumerStatsConfig = config;
@@ -15456,18 +15457,14 @@ public class BatteryStatsImpl extends BatteryStats {
mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
}
} else {
- compatibleConfig = (mEnergyConsumerStatsConfig == null);
- // EnergyConsumer no longer supported, wipe out the existing data.
+ if (mEnergyConsumerStatsConfig != null) {
+ // EnergyConsumer no longer supported, wipe out the existing data.
+ resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
+ RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE);
+ }
mEnergyConsumerStatsConfig = null;
mGlobalEnergyConsumerStats = null;
}
-
- if (!compatibleConfig) {
- // Supported power buckets changed since last boot.
- // Existing data is no longer reliable.
- resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
- RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE);
- }
}
@GuardedBy("this")
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java
index 7091c47b8e83..ecfc040ae29c 100644
--- a/services/core/java/com/android/server/search/SearchManagerService.java
+++ b/services/core/java/com/android/server/search/SearchManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.search;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.ISearchManager;
import android.app.SearchManager;
import android.app.SearchableInfo;
@@ -24,6 +25,7 @@ import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.os.Binder;
@@ -32,6 +34,7 @@ import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -47,6 +50,7 @@ import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -71,11 +75,6 @@ public class SearchManagerService extends ISearchManager.Stub {
}
@Override
- public void onUserUnlocking(@NonNull TargetUser user) {
- mService.mHandler.post(() -> mService.onUnlockUser(user.getUserIdentifier()));
- }
-
- @Override
public void onUserStopped(@NonNull TargetUser user) {
mService.onCleanupUser(user.getUserIdentifier());
}
@@ -102,10 +101,6 @@ public class SearchManagerService extends ISearchManager.Stub {
}
private Searchables getSearchables(int userId) {
- return getSearchables(userId, false);
- }
-
- private Searchables getSearchables(int userId, boolean forceUpdate) {
final long token = Binder.clearCallingIdentity();
try {
final UserManager um = mContext.getSystemService(UserManager.class);
@@ -122,21 +117,11 @@ public class SearchManagerService extends ISearchManager.Stub {
Searchables searchables = mSearchables.get(userId);
if (searchables == null) {
searchables = new Searchables(mContext, userId);
- searchables.updateSearchableList();
- mSearchables.append(userId, searchables);
- } else if (forceUpdate) {
- searchables.updateSearchableList();
+ mSearchables.put(userId, searchables);
}
- return searchables;
- }
- }
- private void onUnlockUser(int userId) {
- try {
- getSearchables(userId, true);
- } catch (IllegalStateException ignored) {
- // We're just trying to warm a cache, so we don't mind if the user
- // was stopped or destroyed before we got here.
+ searchables.updateSearchableListIfNeeded();
+ return searchables;
}
}
@@ -150,28 +135,110 @@ public class SearchManagerService extends ISearchManager.Stub {
* Refreshes the "searchables" list when packages are added/removed.
*/
class MyPackageMonitor extends PackageMonitor {
+ /**
+ * Packages that are appeared, disappeared, or modified for whatever reason.
+ */
+ private final ArrayList<String> mChangedPackages = new ArrayList<>();
+
+ /**
+ * {@code true} if one or more packages that contain {@link SearchableInfo} appeared.
+ */
+ private boolean mSearchablePackageAppeared = false;
+
+ @Override
+ public void onBeginPackageChanges() {
+ clearPackageChangeState();
+ }
+
+ @Override
+ public void onPackageAppeared(String packageName, int reason) {
+ if (!mSearchablePackageAppeared) {
+ // Check if the new appeared package contains SearchableInfo.
+ mSearchablePackageAppeared =
+ hasSearchableForPackage(packageName, getChangingUserId());
+ }
+ mChangedPackages.add(packageName);
+ }
@Override
- public void onSomePackagesChanged() {
- updateSearchables();
+ public void onPackageDisappeared(String packageName, int reason) {
+ mChangedPackages.add(packageName);
}
@Override
- public void onPackageModified(String pkg) {
- updateSearchables();
+ public void onPackageModified(String packageName) {
+ mChangedPackages.add(packageName);
}
- private void updateSearchables() {
+ @Override
+ public void onFinishPackageChanges() {
+ onFinishPackageChangesInternal();
+ clearPackageChangeState();
+ }
+
+ private void clearPackageChangeState() {
+ mChangedPackages.clear();
+ mSearchablePackageAppeared = false;
+ }
+
+ private boolean hasSearchableForPackage(String packageName, int userId) {
+ final List<ResolveInfo> searchList = querySearchableActivities(mContext,
+ new Intent(Intent.ACTION_SEARCH).setPackage(packageName), userId);
+ if (!searchList.isEmpty()) {
+ return true;
+ }
+
+ final List<ResolveInfo> webSearchList = querySearchableActivities(mContext,
+ new Intent(Intent.ACTION_WEB_SEARCH).setPackage(packageName), userId);
+ if (!webSearchList.isEmpty()) {
+ return true;
+ }
+
+ final List<ResolveInfo> globalSearchList = querySearchableActivities(mContext,
+ new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH).setPackage(packageName),
+ userId);
+ return !globalSearchList.isEmpty();
+ }
+
+ private boolean shouldRebuildSearchableList(@UserIdInt int changingUserId) {
+ // This method is guaranteed to be called only on getRegisteredHandler()
+ if (mSearchablePackageAppeared) {
+ return true;
+ }
+
+ ArraySet<String> knownSearchablePackageNames = new ArraySet<>();
+ synchronized (mSearchables) {
+ Searchables searchables = mSearchables.get(changingUserId);
+ if (searchables != null) {
+ knownSearchablePackageNames = searchables.getKnownSearchablePackageNames();
+ }
+ }
+
+ final int numOfPackages = mChangedPackages.size();
+ for (int i = 0; i < numOfPackages; i++) {
+ final String packageName = mChangedPackages.get(i);
+ if (knownSearchablePackageNames.contains(packageName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void onFinishPackageChangesInternal() {
final int changingUserId = getChangingUserId();
+ if (!shouldRebuildSearchableList(changingUserId)) {
+ return;
+ }
+
synchronized (mSearchables) {
- // Update list of searchable activities
- for (int i = 0; i < mSearchables.size(); i++) {
- if (changingUserId == mSearchables.keyAt(i)) {
- mSearchables.valueAt(i).updateSearchableList();
- break;
- }
+ // Invalidate the searchable list.
+ Searchables searchables = mSearchables.get(changingUserId);
+ if (searchables != null) {
+ searchables.invalidateSearchableList();
}
}
+
// Inform all listeners that the list of searchables has been updated.
Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
@@ -180,6 +247,17 @@ public class SearchManagerService extends ISearchManager.Stub {
}
}
+ @NonNull
+ static List<ResolveInfo> querySearchableActivities(Context context, Intent searchIntent,
+ @UserIdInt int userId) {
+ final List<ResolveInfo> activities = context.getPackageManager()
+ .queryIntentActivitiesAsUser(searchIntent, PackageManager.GET_META_DATA
+ | PackageManager.MATCH_INSTANT
+ | PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
+ return activities;
+ }
+
+
class GlobalSearchProviderObserver extends ContentObserver {
private final ContentResolver mResolver;
@@ -196,7 +274,7 @@ public class SearchManagerService extends ISearchManager.Stub {
public void onChange(boolean selfChange) {
synchronized (mSearchables) {
for (int i = 0; i < mSearchables.size(); i++) {
- mSearchables.valueAt(i).updateSearchableList();
+ mSearchables.valueAt(i).invalidateSearchableList();
}
}
Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
diff --git a/services/core/java/com/android/server/search/Searchables.java b/services/core/java/com/android/server/search/Searchables.java
index 7b397755173d..dc6733941357 100644
--- a/services/core/java/com/android/server/search/Searchables.java
+++ b/services/core/java/com/android/server/search/Searchables.java
@@ -35,8 +35,10 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import java.io.FileDescriptor;
@@ -62,7 +64,6 @@ public class Searchables {
private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
private Context mContext;
-
private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
private ArrayList<SearchableInfo> mSearchablesList = null;
private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
@@ -81,6 +82,12 @@ public class Searchables {
final private IPackageManager mPm;
// User for which this Searchables caches information
private int mUserId;
+ @GuardedBy("this")
+ private boolean mRebuildSearchables = true;
+
+ // Package names that are known to contain {@link SearchableInfo}
+ @GuardedBy("this")
+ private ArraySet<String> mKnownSearchablePackageNames = new ArraySet<>();
/**
*
@@ -224,7 +231,14 @@ public class Searchables {
*
* TODO: sort the list somehow? UI choice.
*/
- public void updateSearchableList() {
+ public void updateSearchableListIfNeeded() {
+ synchronized (this) {
+ if (!mRebuildSearchables) {
+ // The searchable list is valid, no need to rebuild.
+ return;
+ }
+ }
+
// These will become the new values at the end of the method
HashMap<ComponentName, SearchableInfo> newSearchablesMap
= new HashMap<ComponentName, SearchableInfo>();
@@ -232,6 +246,7 @@ public class Searchables {
= new ArrayList<SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
= new ArrayList<SearchableInfo>();
+ ArraySet<String> newKnownSearchablePackageNames = new ArraySet<>();
// Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
List<ResolveInfo> searchList;
@@ -264,6 +279,7 @@ public class Searchables {
mUserId);
if (searchable != null) {
newSearchablesList.add(searchable);
+ newKnownSearchablePackageNames.add(ai.packageName);
newSearchablesMap.put(searchable.getSearchActivity(), searchable);
if (searchable.shouldIncludeInGlobalSearch()) {
newSearchablesInGlobalSearchList.add(searchable);
@@ -286,16 +302,41 @@ public class Searchables {
synchronized (this) {
mSearchablesMap = newSearchablesMap;
mSearchablesList = newSearchablesList;
+ mKnownSearchablePackageNames = newKnownSearchablePackageNames;
mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
mGlobalSearchActivities = newGlobalSearchActivities;
mCurrentGlobalSearchActivity = newGlobalSearchActivity;
mWebSearchActivity = newWebSearchActivity;
+ for (ResolveInfo globalSearchActivity: mGlobalSearchActivities) {
+ mKnownSearchablePackageNames.add(
+ globalSearchActivity.getComponentInfo().packageName);
+ }
+ if (mCurrentGlobalSearchActivity != null) {
+ mKnownSearchablePackageNames.add(
+ mCurrentGlobalSearchActivity.getPackageName());
+ }
+ if (mWebSearchActivity != null) {
+ mKnownSearchablePackageNames.add(mWebSearchActivity.getPackageName());
+ }
+
+ mRebuildSearchables = false;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+ synchronized ArraySet<String> getKnownSearchablePackageNames() {
+ return mKnownSearchablePackageNames;
+ }
+
+ synchronized void invalidateSearchableList() {
+ mRebuildSearchables = true;
+
+ // Don't rebuild the searchable list, it will be rebuilt
+ // when the next updateSearchableList gets called.
+ }
+
/**
* Returns a sorted list of installed search providers as per
* the following heuristics:
@@ -532,6 +573,8 @@ public class Searchables {
pw.print(" "); pw.println(info.getSuggestAuthority());
}
}
+
+ pw.println("mRebuildSearchables = " + mRebuildSearchables);
}
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 7163319f281a..5d17884c769b 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -19,7 +19,7 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
-import android.os.IExternalVibratorService;
+import android.os.ExternalVibrationScale;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -37,11 +37,13 @@ final class VibrationScaler {
// Scale levels. Each level, except MUTE, is defined as the delta between the current setting
// and the default intensity for that type of vibration (i.e. current - default).
- private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2
- private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1
- private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0
- private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1
- private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2
+ private static final int SCALE_VERY_LOW =
+ ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW; // -2
+ private static final int SCALE_LOW = ExternalVibrationScale.ScaleLevel.SCALE_LOW; // -1
+ private static final int SCALE_NONE = ExternalVibrationScale.ScaleLevel.SCALE_NONE; // 0
+ private static final int SCALE_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_HIGH; // 1
+ private static final int SCALE_VERY_HIGH =
+ ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH; // 2
// Scale factors for each level.
private static final float SCALE_FACTOR_VERY_LOW = 0.6f;
@@ -83,9 +85,9 @@ final class VibrationScaler {
* Calculates the scale to be applied to external vibration with given usage.
*
* @param usageHint one of VibrationAttributes.USAGE_*
- * @return one of IExternalVibratorService.SCALE_*
+ * @return one of ExternalVibrationScale.ScaleLevel.SCALE_*
*/
- public int getExternalVibrationScale(int usageHint) {
+ public int getExternalVibrationScaleLevel(int usageHint) {
int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
@@ -107,6 +109,22 @@ final class VibrationScaler {
}
/**
+ * Returns the adaptive haptics scale that should be applied to the vibrations with
+ * the given usage. When no adaptive scales are available for the usages, then returns 1
+ * indicating no scaling will be applied
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*
+ * @return The adaptive haptics scale.
+ */
+ public float getAdaptiveHapticsScale(int usageHint) {
+ if (shouldApplyAdaptiveHapticsScale(usageHint)) {
+ return mAdaptiveHapticsScales.get(usageHint);
+ }
+
+ return 1f; // no scaling
+ }
+
+ /**
* Scale a {@link VibrationEffect} based on the given usage hint for this vibration.
*
* @param effect the effect to be scaled
@@ -152,9 +170,7 @@ final class VibrationScaler {
}
// If adaptive haptics scaling is available for this usage, apply it to the segment.
- if (Flags.adaptiveHapticsEnabled()
- && mAdaptiveHapticsScales.size() > 0
- && mAdaptiveHapticsScales.contains(usageHint)) {
+ if (shouldApplyAdaptiveHapticsScale(usageHint)) {
float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
segment = segment.scaleLinearly(adaptiveScale);
}
@@ -224,6 +240,10 @@ final class VibrationScaler {
mAdaptiveHapticsScales.clear();
}
+ private boolean shouldApplyAdaptiveHapticsScale(int usageHint) {
+ return Flags.adaptiveHapticsEnabled() && mAdaptiveHapticsScales.contains(usageHint);
+ }
+
/** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
private static int intensityToEffectStrength(int intensity) {
switch (intensity) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index be5d15877e32..78e0ebbb53fa 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import static android.os.ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -35,6 +36,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.CombinedVibration;
import android.os.ExternalVibration;
+import android.os.ExternalVibrationScale;
import android.os.Handler;
import android.os.IBinder;
import android.os.IExternalVibratorService;
@@ -277,7 +279,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
- if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
+ if (injector.isServiceDeclared(VIBRATOR_CONTROL_SERVICE)) {
injector.addService(VIBRATOR_CONTROL_SERVICE, mVibratorControlService);
}
@@ -1427,6 +1429,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
VibratorControllerHolder createVibratorControllerHolder() {
return new VibratorControllerHolder();
}
+
+ boolean isServiceDeclared(String name) {
+ return ServiceManager.isDeclared(name);
+ }
}
/**
@@ -1594,7 +1600,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
IBinder.DeathRecipient {
public final ExternalVibration externalVibration;
- public int scale;
+ public ExternalVibrationScale scale = new ExternalVibrationScale();
private Vibration.Status mStatus;
@@ -1605,7 +1611,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// instead of using DEVICE_ID_INVALID here and relying on the UID checks.
Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null));
this.externalVibration = externalVibration;
- this.scale = IExternalVibratorService.SCALE_NONE;
mStatus = Vibration.Status.RUNNING;
}
@@ -1658,7 +1663,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
public Vibration.DebugInfo getDebugInfo() {
return new Vibration.DebugInfo(mStatus, stats, /* playedEffect= */ null,
- /* originalEffect= */ null, scale, callerInfo);
+ /* originalEffect= */ null, scale.scaleLevel, callerInfo);
}
public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
@@ -1988,11 +1993,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
@VisibleForTesting
final class ExternalVibratorService extends IExternalVibratorService.Stub {
+ private static final ExternalVibrationScale SCALE_MUTE = new ExternalVibrationScale();
+
+ static {
+ SCALE_MUTE.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
+ }
@Override
- public int onExternalVibrationStart(ExternalVibration vib) {
+ public ExternalVibrationScale onExternalVibrationStart(ExternalVibration vib) {
+
if (!hasExternalControlCapability()) {
- return IExternalVibratorService.SCALE_MUTE;
+ return SCALE_MUTE;
}
if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
vib.getUid(), -1 /*owningUid*/, true /*exported*/)
@@ -2000,7 +2011,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ " tried to play externally controlled vibration"
+ " without VIBRATE permission, ignoring.");
- return IExternalVibratorService.SCALE_MUTE;
+ return SCALE_MUTE;
}
// Create Vibration.Stats as close to the received request as possible, for tracking.
@@ -2033,7 +2044,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
if (vibrationEndInfo != null) {
- vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+ vibHolder.scale = SCALE_MUTE;
// Failed to start the vibration, end it and report metrics right away.
endVibrationAndWriteStatsLocked(vibHolder, vibrationEndInfo);
return vibHolder.scale;
@@ -2074,7 +2085,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
mCurrentExternalVibration = vibHolder;
vibHolder.linkToDeath();
- vibHolder.scale = mVibrationScaler.getExternalVibrationScale(attrs.getUsage());
+ vibHolder.scale.scaleLevel = mVibrationScaler.getExternalVibrationScaleLevel(
+ attrs.getUsage());
+ vibHolder.scale.adaptiveHapticsScale = mVibrationScaler.getAdaptiveHapticsScale(
+ attrs.getUsage());
}
if (waitForCompletion) {
@@ -2086,7 +2100,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
/* continueExternalControl= */ false);
}
- return IExternalVibratorService.SCALE_MUTE;
+ return SCALE_MUTE;
}
}
if (!alreadyUnderExternalControl) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index b3c8b0b3a47a..27c80c43cd94 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -107,15 +107,20 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
mContext = context;
mSystemInterface = systemInterface;
WebViewProviderInfo[] webviewProviders = getWebViewPackages();
+
+ WebViewProviderInfo defaultProvider = null;
for (WebViewProviderInfo provider : webviewProviders) {
if (provider.availableByDefault) {
- mDefaultProvider = provider;
+ defaultProvider = provider;
break;
}
}
- // This should be unreachable because the config parser enforces that there is at least one
- // availableByDefault provider.
- throw new AndroidRuntimeException("No available by default WebView Provider.");
+ if (defaultProvider == null) {
+ // This should be unreachable because the config parser enforces that there is at least
+ // one availableByDefault provider.
+ throw new AndroidRuntimeException("No available by default WebView Provider.");
+ }
+ mDefaultProvider = defaultProvider;
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 56024f75545c..bc6f93fd64ac 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1535,11 +1535,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order. Set the last reported MW state to the same as the PiP
// state since we haven't yet actually resized the task (these callbacks need to
- // precede the configuration change from the resize.
+ // precede the configuration change from the resize.)
mLastReportedPictureInPictureMode = inPictureInPictureMode;
mLastReportedMultiWindowMode = inPictureInPictureMode;
ensureActivityConfiguration(true /* ignoreVisibility */);
- if (inPictureInPictureMode && findMainWindow() == null) {
+ if (inPictureInPictureMode && findMainWindow() == null
+ && task.topRunningActivity() == this) {
// Prevent malicious app entering PiP without valid WindowState, which can in turn
// result a non-touchable PiP window since the InputConsumer for PiP requires it.
EventLog.writeEvent(0x534e4554, "265293293", -1, "");
@@ -3549,7 +3550,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
IBinder callerToken = new Binder();
if (android.security.Flags.contentUriPermissionApis()) {
try {
- resultTo.computeCallerInfo(callerToken, intent, this.getUid(),
+ resultTo.computeCallerInfo(callerToken, resultData, this.getUid(),
mAtmService.getPackageManager().getNameForUid(this.getUid()),
/* isShareIdentityEnabled */ false);
// Result callers cannot share their identity via
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 10cbc6633533..85d81c4db2ca 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -104,6 +104,7 @@ import android.window.TaskFragmentOrganizerToken;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ToBooleanFunction;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.window.flags.Flags;
@@ -3025,11 +3026,17 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return false;
}
- // boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
- return forAllWindows(
+ ToBooleanFunction<WindowState> getDimBehindWindow =
(w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null
&& w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested()
- || w.mActivityRecord.isVisible()), true);
+ || w.mActivityRecord.isVisible());
+ if (adjacentTf.forAllWindows(getDimBehindWindow, true)) {
+ // early return if the adjacent Tf has a dimming window.
+ return false;
+ }
+
+ // boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
+ return forAllWindows(getDimBehindWindow, true);
}
@Override
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index a46916553abc..b38a2f9558e9 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -117,6 +117,9 @@
<xs:element type="sensorDetails" name="proxSensor">
<xs:annotation name="final"/>
</xs:element>
+ <xs:element type="sensorDetails" name="tempSensor">
+ <xs:annotation name="final"/>
+ </xs:element>
<!-- Length of the ambient light horizon used to calculate the long & short term
estimates of ambient light in milliseconds.-->
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 79ea274e2fca..b329db4a2076 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -133,6 +133,7 @@ package com.android.server.display.config {
method public final java.math.BigDecimal getScreenBrightnessRampSlowIncreaseIdle();
method public final com.android.server.display.config.SensorDetails getScreenOffBrightnessSensor();
method public final com.android.server.display.config.IntegerArray getScreenOffBrightnessSensorValueToLux();
+ method public final com.android.server.display.config.SensorDetails getTempSensor();
method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling();
method public final com.android.server.display.config.UsiVersion getUsiVersion();
method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
@@ -167,6 +168,7 @@ package com.android.server.display.config {
method public final void setScreenBrightnessRampSlowIncreaseIdle(java.math.BigDecimal);
method public final void setScreenOffBrightnessSensor(com.android.server.display.config.SensorDetails);
method public final void setScreenOffBrightnessSensorValueToLux(com.android.server.display.config.IntegerArray);
+ method public final void setTempSensor(com.android.server.display.config.SensorDetails);
method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling);
method public final void setUsiVersion(com.android.server.display.config.UsiVersion);
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index ca23d62601bb..fa63bc899cb5 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -30,9 +30,7 @@ import android.credentials.selection.AuthenticationEntry;
import android.credentials.selection.Entry;
import android.credentials.selection.GetCredentialProviderData;
import android.credentials.selection.ProviderPendingIntentResponse;
-import android.os.Bundle;
import android.os.ICancellationSignal;
-import android.service.autofill.Flags;
import android.service.credentials.Action;
import android.service.credentials.BeginGetCredentialOption;
import android.service.credentials.BeginGetCredentialRequest;
@@ -44,7 +42,6 @@ import android.service.credentials.GetCredentialRequest;
import android.service.credentials.RemoteEntry;
import android.util.Pair;
import android.util.Slog;
-import android.view.autofill.AutofillId;
import java.util.ArrayList;
import java.util.HashMap;
@@ -77,10 +74,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
@NonNull
private final Map<String, CredentialOption> mBeginGetOptionToCredentialOptionMap;
- @NonNull
- private final Map<String, AutofillId> mCredentialEntryKeyToAutofilLIdMap;
-
-
/** The complete request to be used in the second round. */
private final android.credentials.GetCredentialRequest mCompleteRequest;
private final CallingAppInfo mCallingAppInfo;
@@ -249,7 +242,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
mBeginGetOptionToCredentialOptionMap = new HashMap<>(beginGetOptionToCredentialOptionMap);
mProviderResponseDataHandler = new ProviderResponseDataHandler(
ComponentName.unflattenFromString(hybridService));
- mCredentialEntryKeyToAutofilLIdMap = new HashMap<>();
}
/** Called when the provider response has been updated by an external source. */
@@ -303,7 +295,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
invokeCallbackOnInternalInvalidState();
return;
}
- onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
+ onCredentialEntrySelected(providerPendingIntentResponse);
break;
case ACTION_ENTRY_KEY:
Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
@@ -312,7 +304,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
invokeCallbackOnInternalInvalidState();
return;
}
- onActionEntrySelected(providerPendingIntentResponse, entryKey);
+ onActionEntrySelected(providerPendingIntentResponse);
break;
case AUTHENTICATION_ACTION_ENTRY_KEY:
Action authenticationEntry = mProviderResponseDataHandler
@@ -342,7 +334,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
break;
case REMOTE_ENTRY_KEY:
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
- onRemoteEntrySelected(providerPendingIntentResponse, entryKey);
+ onRemoteEntrySelected(providerPendingIntentResponse);
} else {
Slog.i(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
@@ -381,7 +373,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
return null;
}
- private Intent setUpFillInIntentWithFinalRequest(@NonNull String id, String entryKey) {
+ private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
// TODO: Determine if we should skip this entry if entry id is not set, or is set
// but does not resolve to a valid option. For now, not skipping it because
// it may be possible that the provider adds their own extras and expects to receive
@@ -392,13 +384,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option");
return intent;
}
- AutofillId autofillId = credentialOption
- .getCandidateQueryData()
- .getParcelable(CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId.class);
- if (autofillId != null && Flags.autofillCredmanIntegration()) {
- intent.putExtra(CredentialProviderService.EXTRA_AUTOFILL_ID, autofillId);
- mCredentialEntryKeyToAutofilLIdMap.put(entryKey, autofillId);
- }
return intent.putExtra(
CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
new GetCredentialRequest(
@@ -414,13 +399,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
}
private void onRemoteEntrySelected(
- ProviderPendingIntentResponse providerPendingIntentResponse, String entryKey) {
- onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ onCredentialEntrySelected(providerPendingIntentResponse);
}
private void onCredentialEntrySelected(
- ProviderPendingIntentResponse providerPendingIntentResponse,
- String entryKey) {
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
if (providerPendingIntentResponse == null) {
invokeCallbackOnInternalInvalidState();
return;
@@ -437,18 +421,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
.extractGetCredentialResponse(
providerPendingIntentResponse.getResultData());
- if (getCredentialResponse != null && getCredentialResponse.getCredential() != null) {
- Bundle credentialData = getCredentialResponse.getCredential().getData();
- AutofillId autofillId = mCredentialEntryKeyToAutofilLIdMap.get(entryKey);
- if (Flags.autofillCredmanIntegration()
- && entryKey != null && autofillId != null && credentialData != null
- ) {
- Slog.d(TAG, "Adding autofillId to credential response: " + autofillId);
- credentialData.putParcelable(
- CredentialProviderService.EXTRA_AUTOFILL_ID,
- mCredentialEntryKeyToAutofilLIdMap.get(entryKey)
- );
- }
+ if (getCredentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName,
getCredentialResponse);
return;
@@ -532,9 +505,9 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
/** Returns true if either an exception or a response is found. */
private void onActionEntrySelected(ProviderPendingIntentResponse
- providerPendingIntentResponse, String entryKey) {
+ providerPendingIntentResponse) {
Slog.i(TAG, "onActionEntrySelected");
- onCredentialEntrySelected(providerPendingIntentResponse, entryKey);
+ onCredentialEntrySelected(providerPendingIntentResponse);
}
@@ -632,7 +605,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
id, credentialEntry.getSlice(),
setUpFillInIntentWithFinalRequest(credentialEntry
- .getBeginGetCredentialOptionId(), id));
+ .getBeginGetCredentialOptionId()));
mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
mCredentialEntryTypes.add(credentialEntry.getType());
}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index afd6dbd7f6a7..3bce9b54e320 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -69,7 +69,7 @@ android_test {
}
android_ravenwood_test {
- name: "FrameworksInputMethodSystemServerTests_host",
+ name: "FrameworksInputMethodSystemServerTestsRavenwood",
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
@@ -85,7 +85,6 @@ android_ravenwood_test {
srcs: [
"src/com/android/server/inputmethod/**/ClientControllerTest.java",
],
- sdk_version: "test_current",
auto_gen_config: true,
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
index dc9631a8f2e2..9e3d9ec6b9b6 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -32,10 +32,8 @@ import android.content.pm.PackageManagerInternal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import android.view.Display;
-import android.view.inputmethod.InputBinding;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -53,7 +51,7 @@ import java.util.concurrent.TimeUnit;
public final class ClientControllerTest {
private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
private static final int ANY_CALLER_UID = 1;
- private static final int ANY_CALLER_PID = 1;
+ private static final int ANY_CALLER_PID = 2;
private static final String SOME_PACKAGE_NAME = "some.package";
@Rule
@@ -82,13 +80,16 @@ public final class ClientControllerTest {
mController = new ClientController(mMockPackageManagerInternal);
}
+ // TODO(b/322895594): No need to directly invoke create$ravenwood once b/322895594 is fixed.
+ private IInputMethodClientInvoker createInvoker(IInputMethodClient client, Handler handler) {
+ return RavenwoodRule.isOnRavenwood()
+ ? IInputMethodClientInvoker.create$ravenwood(client, handler) :
+ IInputMethodClientInvoker.create(client, handler);
+ }
+
@Test
- // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
- // inputmethod server classes.
- @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testAddClient_cannotAddTheSameClientTwice() {
- var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
-
+ final var invoker = createInvoker(mClient, mHandler);
synchronized (ImfLock.class) {
mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
ANY_CALLER_PID);
@@ -101,18 +102,17 @@ public final class ClientControllerTest {
}
});
assertThat(thrown.getMessage()).isEqualTo(
- "uid=1/pid=1/displayId=0 is already registered");
+ "uid=" + ANY_CALLER_UID + "/pid=" + ANY_CALLER_PID
+ + "/displayId=0 is already registered");
}
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
- // inputmethod server classes.
- @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testAddClient() throws Exception {
+ final var invoker = createInvoker(mClient, mHandler);
synchronized (ImfLock.class) {
- var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
- var added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+ final var added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
+ ANY_CALLER_UID,
ANY_CALLER_PID);
verify(invoker.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
@@ -121,16 +121,12 @@ public final class ClientControllerTest {
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
- // inputmethod server classes.
- @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testRemoveClient() {
- var callback = new TestClientControllerCallback();
+ final var invoker = createInvoker(mClient, mHandler);
+ final var callback = new TestClientControllerCallback();
ClientState added;
synchronized (ImfLock.class) {
mController.addClientControllerCallback(callback);
-
- var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
ANY_CALLER_PID);
assertThat(mController.getClient(invoker.asBinder())).isSameInstanceAs(added);
@@ -138,21 +134,17 @@ public final class ClientControllerTest {
}
// Test callback
- var removed = callback.waitForRemovedClient(5, TimeUnit.SECONDS);
+ final var removed = callback.waitForRemovedClient(5, TimeUnit.SECONDS);
assertThat(removed).isSameInstanceAs(added);
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
- // inputmethod server classes and updated to newer Mockito with static mock support (mock
- // InputMethodUtils#checkIfPackageBelongsToUid instead of PackageManagerInternal#isSameApp)
- @IgnoreUnderRavenwood(blockedBy = {InputMethodUtils.class})
public void testVerifyClientAndPackageMatch() {
+ final var invoker = createInvoker(mClient, mHandler);
when(mMockPackageManagerInternal.isSameApp(eq(SOME_PACKAGE_NAME), /* flags= */
anyLong(), eq(ANY_CALLER_UID), /* userId= */ anyInt())).thenReturn(true);
synchronized (ImfLock.class) {
- var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
ANY_CALLER_PID);
assertThat(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
index 8faaf5998d13..05c243fda2b8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -43,6 +43,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.server.display.BrightnessThrottler.Injector;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.config.SensorData;
import com.android.server.display.mode.DisplayModeDirectorTest;
import org.junit.Before;
@@ -56,6 +57,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -292,6 +294,53 @@ public class BrightnessThrottlerTest {
assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
}
+
+ @Test
+ public void testThermalThrottlingWithDisplaySensor() throws Exception {
+ final ThrottlingLevel level =
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f);
+ List<ThrottlingLevel> levels = new ArrayList<>(List.of(level));
+ final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+ final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
+ final BrightnessThrottler throttler =
+ createThrottlerSupportedWithTempSensor(data, tempSensor);
+ assertTrue(throttler.deviceSupportsThrottling());
+
+ verify(mThermalServiceMock)
+ .registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_DISPLAY));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Set VIRTUAL-SKIN-DISPLAY tatus too low to verify no throttling.
+ listener.notifyThrottling(getDisplayTempWithName(tempSensor.name, level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Verify when skin sensor throttled, no brightness throttling triggered.
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Verify when display sensor of another name throttled, no brightness throttling triggered.
+ listener.notifyThrottling(getDisplayTempWithName("ANOTHER-NAME", level.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Verify when display sensor of current name throttled, brightness throttling triggered.
+ listener.notifyThrottling(getDisplayTempWithName(tempSensor.name, level.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+ }
+
@Test public void testUpdateThermalThrottlingData() throws Exception {
// Initialise brightness throttling levels
// Ensure that they are overridden by setting the data through device config.
@@ -476,18 +525,30 @@ public class BrightnessThrottlerTest {
return new BrightnessThrottler(mInjectorMock, mHandler, mHandler,
/* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null,
/* thermalThrottlingDataId= */ null,
- /* thermalThrottlingDataMap= */ new HashMap<>(1));
+ /* thermalThrottlingDataMap= */ new HashMap<>(1),
+ /* tempSensor= */ null);
}
private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) {
+ SensorData tempSensor = SensorData.loadTempSensorUnspecifiedConfig();
+ return createThrottlerSupportedWithTempSensor(data, tempSensor);
+ }
+ private BrightnessThrottler createThrottlerSupportedWithTempSensor(
+ ThermalBrightnessThrottlingData data, SensorData tempSensor) {
assertNotNull(data);
- HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
+ Map<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
throttlingDataMap.put("default", data);
return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
- () -> {}, "123", "default", throttlingDataMap);
+ () -> {}, "123", "default", throttlingDataMap, tempSensor);
}
private Temperature getSkinTemp(@ThrottlingStatus int status) {
return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
}
+
+ private Temperature getDisplayTempWithName(
+ String sensorName, @ThrottlingStatus int status) {
+ assertNotNull(sensorName);
+ return new Temperature(30.0f, Temperature.TYPE_DISPLAY, sensorName, status);
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index b29fc8828f58..2867041511b5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -20,6 +20,7 @@ package com.android.server.display;
import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
+import static com.android.server.display.config.SensorData.TEMPERATURE_TYPE_SKIN;
import static com.android.server.display.config.SensorData.SupportedMode;
import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -106,6 +107,7 @@ public final class DisplayDeviceConfigTest {
MockitoAnnotations.initMocks(this);
when(mContext.getResources()).thenReturn(mResources);
when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mFlags.isSensorBasedBrightnessThrottlingEnabled()).thenReturn(true);
mockDeviceConfigs();
}
@@ -143,6 +145,8 @@ public final class DisplayDeviceConfigTest {
assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
assertNull(mDisplayDeviceConfig.getProximitySensor().type);
assertNull(mDisplayDeviceConfig.getProximitySensor().name);
+ assertEquals(TEMPERATURE_TYPE_SKIN, mDisplayDeviceConfig.getTempSensor().type);
+ assertNull(mDisplayDeviceConfig.getTempSensor().name);
assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
}
@@ -592,6 +596,13 @@ public final class DisplayDeviceConfigTest {
}
@Test
+ public void testTempSensorFromDisplayConfig() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+ assertEquals("DISPLAY", mDisplayDeviceConfig.getTempSensor().type);
+ assertEquals("VIRTUAL-SKIN-DISPLAY", mDisplayDeviceConfig.getTempSensor().name);
+ }
+
+ @Test
public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile();
@@ -1356,6 +1367,10 @@ public final class DisplayDeviceConfigTest {
+ "<name>Test Binned Brightness Sensor</name>\n"
+ "</screenOffBrightnessSensor>\n"
+ proxSensor
+ + "<tempSensor>\n"
+ + "<type>DISPLAY</type>\n"
+ + "<name>VIRTUAL-SKIN-DISPLAY</name>\n"
+ + "</tempSensor>\n"
+ "<ambientBrightnessChangeThresholds>\n"
+ "<brighteningThresholds>\n"
+ "<minimum>10</minimum>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
index 37d0f6250aaf..34f352e7bf54 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
@@ -37,10 +37,14 @@ import com.android.internal.annotations.Keep;
import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.testutils.FakeDeviceConfigInterface;
import com.android.server.testutils.TestHandler;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,9 +54,6 @@ import org.mockito.MockitoAnnotations;
import java.util.List;
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
@RunWith(JUnitParamsRunner.class)
public class BrightnessThermalClamperTest {
@@ -125,13 +126,13 @@ public class BrightnessThermalClamperTest {
public void testNotifyThrottlingAfterOnDisplayChange(List<ThrottlingLevel> throttlingLevels,
@Temperature.ThrottlingStatus int throttlingStatus,
boolean expectedActive, float expectedBrightness) throws RemoteException {
- IThermalEventListener thermalEventListener = captureThermalEventListener();
+ IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
mTestHandler.flush();
assertFalse(mClamper.isActive());
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+ thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus));
mTestHandler.flush();
assertEquals(expectedActive, mClamper.isActive());
assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
@@ -139,11 +140,11 @@ public class BrightnessThermalClamperTest {
@Test
@Parameters(method = "testThrottlingData")
- public void testOnDisplayChangeAfterNotifyThrottlng(List<ThrottlingLevel> throttlingLevels,
+ public void testOnDisplayChangeAfterNotifyThrottling(List<ThrottlingLevel> throttlingLevels,
@Temperature.ThrottlingStatus int throttlingStatus,
boolean expectedActive, float expectedBrightness) throws RemoteException {
- IThermalEventListener thermalEventListener = captureThermalEventListener();
- thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+ IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+ thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus));
mTestHandler.flush();
assertFalse(mClamper.isActive());
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
@@ -156,8 +157,8 @@ public class BrightnessThermalClamperTest {
@Test
public void testOverrideData() throws RemoteException {
- IThermalEventListener thermalEventListener = captureThermalEventListener();
- thermalEventListener.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
+ IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+ thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE));
mTestHandler.flush();
assertFalse(mClamper.isActive());
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
@@ -183,15 +184,60 @@ public class BrightnessThermalClamperTest {
assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
}
- private IThermalEventListener captureThermalEventListener() throws RemoteException {
+ @Test
+ public void testDisplaySensorBasedThrottling() throws RemoteException {
+ final int severity = PowerManager.THERMAL_STATUS_SEVERE;
+ IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+ // Update config to listen to display type sensor.
+ final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
+ final TestThermalData thermalData =
+ new TestThermalData(
+ DISPLAY_ID,
+ DisplayDeviceConfig.DEFAULT_ID,
+ List.of(new ThrottlingLevel(severity, 0.5f)),
+ tempSensor);
+ mClamper.onDisplayChanged(thermalData);
+ mTestHandler.flush();
+ verify(mMockThermalService).unregisterThermalEventListener(thermalEventListener);
+ thermalEventListener = captureThermalEventListener(Temperature.TYPE_DISPLAY);
+ assertFalse(mClamper.isActive());
+
+ // Verify no throttling triggered when any other sensor notification received.
+ thermalEventListener.notifyThrottling(createSkinTemperature(severity));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+
+ thermalEventListener.notifyThrottling(createDisplayTemperature("OTHER-SENSOR", severity));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ // Verify throttling triggered when display sensor of given name throttled.
+ thermalEventListener.notifyThrottling(createDisplayTemperature(tempSensor.name, severity));
+ mTestHandler.flush();
+ assertTrue(mClamper.isActive());
+ assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ private IThermalEventListener captureSkinThermalEventListener() throws RemoteException {
+ return captureThermalEventListener(Temperature.TYPE_SKIN);
+ }
+
+ private IThermalEventListener captureThermalEventListener(int type) throws RemoteException {
ArgumentCaptor<IThermalEventListener> captor = ArgumentCaptor.forClass(
IThermalEventListener.class);
verify(mMockThermalService).registerThermalEventListenerWithType(captor.capture(), eq(
- Temperature.TYPE_SKIN));
+ type));
return captor.getValue();
}
- private Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
+ private Temperature createDisplayTemperature(
+ @NonNull String sensorName, @Temperature.ThrottlingStatus int status) {
+ return new Temperature(100, Temperature.TYPE_DISPLAY, sensorName, status);
+ }
+
+ private Temperature createSkinTemperature(@Temperature.ThrottlingStatus int status) {
return new Temperature(100, Temperature.TYPE_SKIN, "test_temperature", status);
}
@@ -217,19 +263,26 @@ public class BrightnessThermalClamperTest {
private final String mUniqueDisplayId;
private final String mDataId;
private final ThermalBrightnessThrottlingData mData;
+ private final SensorData mTempSensor;
private TestThermalData() {
- this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null,
+ SensorData.loadTempSensorUnspecifiedConfig());
}
private TestThermalData(List<ThrottlingLevel> data) {
- this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data,
+ SensorData.loadTempSensorUnspecifiedConfig());
}
- private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+
+ private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data,
+ SensorData tempSensor) {
mUniqueDisplayId = uniqueDisplayId;
mDataId = dataId;
mData = ThermalBrightnessThrottlingData.create(data);
+ mTempSensor = tempSensor;
}
+
@NonNull
@Override
public String getUniqueDisplayId() {
@@ -247,5 +300,11 @@ public class BrightnessThermalClamperTest {
public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
return mData;
}
+
+ @NonNull
+ @Override
+ public SensorData getTempSensor() {
+ return mTempSensor;
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index a4761555384e..99752212fcbd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.alarm;
import static android.Manifest.permission.SCHEDULE_EXACT_ALARM;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED;
@@ -42,6 +44,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
@@ -135,6 +138,7 @@ import android.net.Uri;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -145,9 +149,11 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.flag.util.FlagSetException;
import android.provider.DeviceConfig;
-import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.ArraySet;
import android.util.Log;
@@ -215,6 +221,7 @@ public final class AlarmManagerServiceTest {
private AppStateTrackerImpl.Listener mListener;
private AlarmManagerService.UninstallReceiver mPackageChangesReceiver;
private AlarmManagerService.ChargingReceiver mChargingReceiver;
+ private ActivityManager.UidFrozenStateChangedCallback mUidFrozenStateCallback;
private IAppOpsCallback mIAppOpsCallback;
private IAlarmManager mBinder;
@Mock
@@ -240,6 +247,8 @@ public final class AlarmManagerServiceTest {
@Mock
private ActivityManagerInternal mActivityManagerInternal;
@Mock
+ private ActivityManager mActivityManager;
+ @Mock
private PackageManagerInternal mPackageManagerInternal;
@Mock
private AppStateTrackerImpl mAppStateTracker;
@@ -403,15 +412,31 @@ public final class AlarmManagerServiceTest {
.mockStatic(PermissionChecker.class)
.mockStatic(PermissionManagerService.class)
.mockStatic(ServiceManager.class)
- .mockStatic(Settings.Global.class)
.mockStatic(SystemProperties.class)
.spyStatic(UserHandle.class)
.afterSessionFinished(
() -> LocalServices.removeServiceForTest(AlarmManagerInternal.class))
.build();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT);
+
+ /**
+ * Have to do this to switch the {@link Flags} implementation to {@link FakeFeatureFlagsImpl}.
+ * All methods that need any flag enabled should use the
+ * {@link android.platform.test.annotations.EnableFlags} annotation, in which case disabling
+ * the flag will fail with an exception that we will swallow here.
+ */
+ private void disableFlagsNotSetByAnnotation() {
+ try {
+ mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS);
+ } catch (FlagSetException fse) {
+ // Expected if the test about to be run requires this enabled.
+ }
+ }
+
@Before
- public final void setUp() {
+ public void setUp() {
doReturn(mIActivityManager).when(ActivityManager::getService);
doReturn(mDeviceIdleInternal).when(
() -> LocalServices.getService(DeviceIdleInternal.class));
@@ -469,6 +494,7 @@ public final class AlarmManagerServiceTest {
when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
when(mMockContext.getSystemService(BatteryManager.class)).thenReturn(mBatteryManager);
+ when(mMockContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
registerAppIds(new String[]{TEST_CALLING_PACKAGE},
new Integer[]{UserHandle.getAppId(TEST_CALLING_UID)});
@@ -479,7 +505,18 @@ public final class AlarmManagerServiceTest {
mService = new AlarmManagerService(mMockContext, mInjector);
spyOn(mService);
+ disableFlagsNotSetByAnnotation();
+
mService.onStart();
+
+ if (Flags.useFrozenStateToDropListenerAlarms()) {
+ final ArgumentCaptor<ActivityManager.UidFrozenStateChangedCallback> frozenCaptor =
+ ArgumentCaptor.forClass(ActivityManager.UidFrozenStateChangedCallback.class);
+ verify(mActivityManager).registerUidFrozenStateChangedCallback(
+ any(HandlerExecutor.class), frozenCaptor.capture());
+ mUidFrozenStateCallback = frozenCaptor.getValue();
+ }
+
// Unable to mock mMockContext to return a mock stats manager.
// So just mocking the whole MetricsHelper instance.
mService.mMetricsHelper = mock(MetricsHelper.class);
@@ -3741,9 +3778,87 @@ public final class AlarmManagerServiceTest {
mListener.handleUidCachedChanged(TEST_CALLING_UID, true);
assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true);
assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
+ assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
+ }
+
+ private void executeUidFrozenStateCallback(int[] uids, int[] frozenStates) {
+ assertNotNull(mUidFrozenStateCallback);
+ mUidFrozenStateCallback.onUidFrozenStateChanged(uids, frozenStates);
+ }
+
+ @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
+ @Test
+ public void exactListenerAlarmsRemovedOnFrozen() {
+ mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
+
+ setTestAlarmWithListener(ELAPSED_REALTIME, 31, getNewListener(() -> {}), WINDOW_EXACT,
+ TEST_CALLING_UID);
+ setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
+ setTestAlarm(ELAPSED_REALTIME, 54, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
+ TEST_CALLING_UID, null);
+ setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
+
+ setTestAlarmWithListener(ELAPSED_REALTIME, 21, getNewListener(() -> {}), WINDOW_EXACT,
+ TEST_CALLING_UID_2);
+ setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
+ setTestAlarm(ELAPSED_REALTIME, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
+ TEST_CALLING_UID_2, null);
+ setTestAlarm(RTC, 549, 234, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null);
+
+ assertEquals(8, mService.mAlarmStore.size());
+
+ executeUidFrozenStateCallback(
+ new int[] {TEST_CALLING_UID, TEST_CALLING_UID_2},
+ new int[] {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_UNFROZEN});
+ assertEquals(7, mService.mAlarmStore.size());
+
+ executeUidFrozenStateCallback(
+ new int[] {TEST_CALLING_UID_2}, new int[] {UID_FROZEN_STATE_FROZEN});
+ assertEquals(6, mService.mAlarmStore.size());
+ }
+
+ @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
+ @Test
+ public void alarmCountOnListenerFrozen() {
+ mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
+
+ // Set some alarms for TEST_CALLING_UID.
+ final int numExactListenerUid1 = 17;
+ for (int i = 0; i < numExactListenerUid1; i++) {
+ setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
+ getNewListener(() -> {}));
+ }
+ setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
+ setTestAlarm(ELAPSED_REALTIME, 54, getNewMockPendingIntent());
+ setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
+
+ // Set some alarms for TEST_CALLING_UID_2.
+ final int numExactListenerUid2 = 11;
+ for (int i = 0; i < numExactListenerUid2; i++) {
+ setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
+ getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID_2);
+ }
+ setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
+ setTestAlarm(RTC_WAKEUP, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
+ TEST_CALLING_UID_2, null);
+
+ assertEquals(numExactListenerUid1 + 3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
+
+ executeUidFrozenStateCallback(
+ new int[] {TEST_CALLING_UID, TEST_CALLING_UID_2},
+ new int[] {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_UNFROZEN});
+ assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
+
+ executeUidFrozenStateCallback(
+ new int[] {TEST_CALLING_UID_2}, new int[] {UID_FROZEN_STATE_FROZEN});
+ assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 940469f89b16..414532b88e22 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -29,16 +29,20 @@ import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupTransport;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.os.Build;
import android.os.Message;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.testing.TestableDeviceConfig;
+import com.android.server.backup.Flags;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.BackupHandler;
import com.android.server.backup.transport.BackupTransportClient;
@@ -56,10 +60,12 @@ import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
@@ -74,10 +80,17 @@ public class PerformUnifiedRestoreTaskTest {
private static final String SYSTEM_PACKAGE_NAME = "android";
private static final String NON_SYSTEM_PACKAGE_NAME = "package";
- @Mock private BackupDataInput mBackupDataInput;
- @Mock private BackupDataOutput mBackupDataOutput;
- @Mock private UserBackupManagerService mBackupManagerService;
- @Mock private TransportConnection mTransportConnection;
+ private static final String V_TO_U_ALLOWLIST = "pkg1";
+ private static final String V_TO_U_DENYLIST = "pkg2";
+
+ @Mock
+ private BackupDataInput mBackupDataInput;
+ @Mock
+ private BackupDataOutput mBackupDataOutput;
+ @Mock
+ private UserBackupManagerService mBackupManagerService;
+ @Mock
+ private TransportConnection mTransportConnection;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -91,6 +104,10 @@ public class PerformUnifiedRestoreTaskTest {
public TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
new TestableDeviceConfig.TestableDeviceConfigRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+
private Context mContext;
@Before
@@ -118,7 +135,8 @@ public class PerformUnifiedRestoreTaskTest {
return null;
});
- mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection);
+ mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection,
+ V_TO_U_ALLOWLIST, V_TO_U_DENYLIST);
}
private void populateTestData() {
@@ -235,6 +253,122 @@ public class PerformUnifiedRestoreTaskTest {
== UnifiedRestoreState.FINAL);
}
+ @Test
+ public void testCreateVToUList_listSettingIsNull_returnEmptyList() {
+ List<String> expectedEmptyList = new ArrayList<>();
+
+ List<String> list = mRestoreTask.createVToUList(null);
+
+ assertEquals(list, expectedEmptyList);
+ }
+
+ @Test
+ public void testCreateVToUList_listIsNotNull_returnCorrectList() {
+ List<String> expectedList = Arrays.asList("a", "b", "c");
+ String listString = "a,b,c";
+
+ List<String> list = mRestoreTask.createVToUList(listString);
+
+ assertEquals(list, expectedList);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOffAndTargetIsUSourceIsV_returnFalse() {
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(
+ Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ assertFalse(isVToUDowngrade);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOnAndTargetIsUSourceIsV_returnTrue() {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(
+ Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ assertTrue(isVToUDowngrade);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOnAndSourceIsNotV_returnFalse() {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ assertFalse(isVToUDowngrade);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOnAndTargetIsNotU_returnFalse() {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(
+ Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.VANILLA_ICE_CREAM);
+
+ assertFalse(isVToUDowngrade);
+ }
+
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsNotOnAllowlist_returnFalse() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is off
+ testPackageInfo.applicationInfo.flags = 0;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertFalse(eligibilityCriteria);
+ }
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsOnAllowlist_returnTrue() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg1";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is off
+ testPackageInfo.applicationInfo.flags = 0;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertTrue(eligibilityCriteria);
+ }
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsNotOnDenyList_returnTrue() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is on
+ testPackageInfo.applicationInfo.flags = ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertTrue(eligibilityCriteria);
+ }
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsOnDenyList_returnFalse() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg2";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is on
+ testPackageInfo.applicationInfo.flags = ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertFalse(eligibilityCriteria);
+ }
+
private void setupForRestoreKeyValueState(int transportStatus)
throws RemoteException, TransportNotAvailableException {
// Mock BackupHandler to do nothing when executeNextState() is called
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 6cd79bc09fb6..ae6984ef0a8d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -18,12 +18,17 @@ package com.android.server.power.stats;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.Context;
import android.hardware.SensorManager;
+import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -34,6 +39,7 @@ import android.os.Parcel;
import android.os.Process;
import android.os.UidBatteryConsumer;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.SparseLongArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -517,6 +523,57 @@ public class BatteryUsageStatsProviderTest {
}
@Test
+ public void saveBatteryUsageStatsOnReset_incompatibleEnergyConsumers() throws Throwable {
+ MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
+ int componentId0 = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ int componentId1 = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1;
+
+ synchronized (batteryStats) {
+ batteryStats.getUidStatsLocked(APP_UID);
+
+ SparseLongArray uidEnergies = new SparseLongArray();
+ uidEnergies.put(APP_UID, 30_000_000);
+ batteryStats.updateCustomEnergyConsumerStatsLocked(0, 100_000_000, uidEnergies);
+ batteryStats.updateCustomEnergyConsumerStatsLocked(1, 200_000_000, uidEnergies);
+ }
+
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
+ mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
+
+ PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
+ doAnswer(invocation -> {
+ BatteryUsageStats stats = invocation.getArgument(1);
+ AggregateBatteryConsumer device = stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ assertThat(device.getCustomPowerComponentName(componentId0)).isEqualTo("FOO");
+ assertThat(device.getCustomPowerComponentName(componentId1)).isEqualTo("BAR");
+ assertThat(device.getConsumedPowerForCustomComponent(componentId0))
+ .isWithin(PRECISION).of(27.77777);
+ assertThat(device.getConsumedPowerForCustomComponent(componentId1))
+ .isWithin(PRECISION).of(55.55555);
+
+ UidBatteryConsumer uid = stats.getUidBatteryConsumers().get(0);
+ assertThat(uid.getConsumedPowerForCustomComponent(componentId0))
+ .isWithin(PRECISION).of(8.33333);
+ assertThat(uid.getConsumedPowerForCustomComponent(componentId1))
+ .isWithin(PRECISION).of(8.33333);
+ return null;
+ }).when(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
+
+ mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore);
+
+ // Make an incompatible change of supported energy components. This will trigger
+ // a BatteryStats reset, which will generate a snapshot of battery stats.
+ mStatsRule.initMeasuredEnergyStatsLocked(
+ new String[]{"COMPONENT1"});
+
+ mStatsRule.waitForBackgroundThread();
+
+ verify(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
+ }
+
+ @Test
public void testAggregateBatteryStats_incompatibleSnapshot() {
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 8bdb0292bf00..7e8fa55020ab 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -16,6 +16,8 @@
package com.android.server.power.stats;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -28,6 +30,7 @@ import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
+import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UidBatteryConsumer;
@@ -74,6 +77,7 @@ public class BatteryUsageStatsRule implements TestRule {
private NetworkStats mNetworkStats;
private boolean[] mSupportedStandardBuckets;
private String[] mCustomPowerComponentNames;
+ private Throwable mThrowable;
public BatteryUsageStatsRule() {
this(0, null);
@@ -270,6 +274,7 @@ public class BatteryUsageStatsRule implements TestRule {
public void evaluate() throws Throwable {
before();
base.evaluate();
+ after();
}
};
}
@@ -277,6 +282,9 @@ public class BatteryUsageStatsRule implements TestRule {
private void before() {
lateInitBatteryStats();
HandlerThread bgThread = new HandlerThread("bg thread");
+ bgThread.setUncaughtExceptionHandler((thread, throwable)-> {
+ mThrowable = throwable;
+ });
bgThread.start();
mHandler = new Handler(bgThread.getLooper());
mBatteryStats.setHandler(mHandler);
@@ -285,6 +293,26 @@ public class BatteryUsageStatsRule implements TestRule {
mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0);
}
+ private void after() throws Throwable {
+ if (mHandler != null) {
+ waitForBackgroundThread();
+ }
+ }
+
+ public void waitForBackgroundThread() throws Throwable {
+ if (mThrowable != null) {
+ throw mThrowable;
+ }
+
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ assertThat(done.block(10000)).isTrue();
+
+ if (mThrowable != null) {
+ throw mThrowable;
+ }
+ }
+
public PowerProfile getPowerProfile() {
return mPowerProfile;
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
index b322dd709c2d..aec3f451fac6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,17 +34,24 @@ import android.testing.DexmakerShareClassLoaderRule;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.util.HexDump;
+
import com.google.common.truth.Expect;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.File;
import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
/**
@@ -51,184 +59,265 @@ import java.util.List;
*
* <p>Prefer adding new tests in CTS where possible.
*/
+@RunWith(Enclosed.class)
public class BrailleDisplayConnectionTest {
- private static final Path NULL_PATH = Path.of("/dev/null");
-
- private BrailleDisplayConnection mBrailleDisplayConnection;
- @Mock
- private BrailleDisplayConnection.NativeInterface mNativeInterface;
- @Mock
- private AccessibilityServiceConnection mServiceConnection;
-
- @Rule
- public final Expect expect = Expect.create();
-
- private Context mContext;
-
- // To mock package-private class
- @Rule
- public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
- new DexmakerShareClassLoaderRule();
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- when(mServiceConnection.isConnectedLocked()).thenReturn(true);
- mBrailleDisplayConnection =
- spy(new BrailleDisplayConnection(new Object(), mServiceConnection));
- }
- @Test
- public void defaultNativeScanner_getHidrawNodePaths_returnsHidrawPaths() throws Exception {
- File testDir = mContext.getFilesDir();
- Path hidrawNode0 = Path.of(testDir.getPath(), "hidraw0");
- Path hidrawNode1 = Path.of(testDir.getPath(), "hidraw1");
- Path otherDevice = Path.of(testDir.getPath(), "otherDevice");
- Path[] nodePaths = {hidrawNode0, hidrawNode1, otherDevice};
- try {
- for (Path node : nodePaths) {
- assertThat(node.toFile().createNewFile()).isTrue();
+ public static class ScannerTest {
+ private static final Path NULL_PATH = Path.of("/dev/null");
+
+ private BrailleDisplayConnection mBrailleDisplayConnection;
+ @Mock
+ private BrailleDisplayConnection.NativeInterface mNativeInterface;
+ @Mock
+ private AccessibilityServiceConnection mServiceConnection;
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ private Context mContext;
+
+ // To mock package-private class
+ @Rule
+ public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+ new DexmakerShareClassLoaderRule();
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ when(mServiceConnection.isConnectedLocked()).thenReturn(true);
+ mBrailleDisplayConnection =
+ spy(new BrailleDisplayConnection(new Object(), mServiceConnection));
+ }
+
+ @Test
+ public void defaultNativeScanner_getHidrawNodePaths_returnsHidrawPaths() throws Exception {
+ File testDir = mContext.getFilesDir();
+ Path hidrawNode0 = Path.of(testDir.getPath(), "hidraw0");
+ Path hidrawNode1 = Path.of(testDir.getPath(), "hidraw1");
+ Path otherDevice = Path.of(testDir.getPath(), "otherDevice");
+ Path[] nodePaths = {hidrawNode0, hidrawNode1, otherDevice};
+ try {
+ for (Path node : nodePaths) {
+ assertThat(node.toFile().createNewFile()).isTrue();
+ }
+
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+
+ assertThat(scanner.getHidrawNodePaths(testDir.toPath()))
+ .containsExactly(hidrawNode0, hidrawNode1);
+ } finally {
+ for (Path node : nodePaths) {
+ node.toFile().delete();
+ }
}
+ }
+
+ @Test
+ public void defaultNativeScanner_getReportDescriptor_returnsDescriptor() {
+ int descriptorSize = 4;
+ byte[] descriptor = {0xB, 0xE, 0xE, 0xF};
+ when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(descriptorSize);
+ when(mNativeInterface.getHidrawDesc(anyInt(), eq(descriptorSize))).thenReturn(
+ descriptor);
BrailleDisplayConnection.BrailleDisplayScanner scanner =
mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
- assertThat(scanner.getHidrawNodePaths(testDir.toPath()))
- .containsExactly(hidrawNode0, hidrawNode1);
- } finally {
- for (Path node : nodePaths) {
- node.toFile().delete();
- }
+ assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isEqualTo(descriptor);
}
- }
- @Test
- public void defaultNativeScanner_getReportDescriptor_returnsDescriptor() {
- int descriptorSize = 4;
- byte[] descriptor = {0xB, 0xE, 0xE, 0xF};
- when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(descriptorSize);
- when(mNativeInterface.getHidrawDesc(anyInt(), eq(descriptorSize))).thenReturn(descriptor);
+ @Test
+ public void defaultNativeScanner_getReportDescriptor_invalidSize_returnsNull() {
+ when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(0);
- BrailleDisplayConnection.BrailleDisplayScanner scanner =
- mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
- assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isEqualTo(descriptor);
- }
+ assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isNull();
+ }
- @Test
- public void defaultNativeScanner_getReportDescriptor_invalidSize_returnsNull() {
- when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(0);
+ @Test
+ public void defaultNativeScanner_getUniqueId_returnsUniq() {
+ String macAddress = "12:34:56:78";
+ when(mNativeInterface.getHidrawUniq(anyInt())).thenReturn(macAddress);
- BrailleDisplayConnection.BrailleDisplayScanner scanner =
- mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
- assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isNull();
- }
+ assertThat(scanner.getUniqueId(NULL_PATH)).isEqualTo(macAddress);
+ }
+
+ @Test
+ public void defaultNativeScanner_getDeviceBusType_busUsb() {
+ when(mNativeInterface.getHidrawBusType(anyInt()))
+ .thenReturn(BrailleDisplayConnection.BUS_USB);
- @Test
- public void defaultNativeScanner_getUniqueId_returnsUniq() {
- String macAddress = "12:34:56:78";
- when(mNativeInterface.getHidrawUniq(anyInt())).thenReturn(macAddress);
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
- BrailleDisplayConnection.BrailleDisplayScanner scanner =
- mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+ assertThat(scanner.getDeviceBusType(NULL_PATH))
+ .isEqualTo(BrailleDisplayConnection.BUS_USB);
+ }
- assertThat(scanner.getUniqueId(NULL_PATH)).isEqualTo(macAddress);
- }
+ @Test
+ public void defaultNativeScanner_getDeviceBusType_busBluetooth() {
+ when(mNativeInterface.getHidrawBusType(anyInt()))
+ .thenReturn(BrailleDisplayConnection.BUS_BLUETOOTH);
- @Test
- public void defaultNativeScanner_getDeviceBusType_busUsb() {
- when(mNativeInterface.getHidrawBusType(anyInt()))
- .thenReturn(BrailleDisplayConnection.BUS_USB);
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
- BrailleDisplayConnection.BrailleDisplayScanner scanner =
- mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+ assertThat(scanner.getDeviceBusType(NULL_PATH))
+ .isEqualTo(BrailleDisplayConnection.BUS_BLUETOOTH);
+ }
- assertThat(scanner.getDeviceBusType(NULL_PATH))
- .isEqualTo(BrailleDisplayConnection.BUS_USB);
- }
+ @Test
+ public void write_bypassesServiceSideCheckWithLargeBuffer_disconnects() {
+ Mockito.doNothing().when(mBrailleDisplayConnection).disconnect();
+ mBrailleDisplayConnection.write(
+ new byte[IBinder.getSuggestedMaxIpcSizeBytes() * 2]);
- @Test
- public void defaultNativeScanner_getDeviceBusType_busBluetooth() {
- when(mNativeInterface.getHidrawBusType(anyInt()))
- .thenReturn(BrailleDisplayConnection.BUS_BLUETOOTH);
+ verify(mBrailleDisplayConnection).disconnect();
+ }
- BrailleDisplayConnection.BrailleDisplayScanner scanner =
- mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+ @Test
+ public void write_notConnected_throwsIllegalStateException() {
+ when(mServiceConnection.isConnectedLocked()).thenReturn(false);
- assertThat(scanner.getDeviceBusType(NULL_PATH))
- .isEqualTo(BrailleDisplayConnection.BUS_BLUETOOTH);
- }
+ assertThrows(IllegalStateException.class,
+ () -> mBrailleDisplayConnection.write(new byte[1]));
+ }
- @Test
- public void write_bypassesServiceSideCheckWithLargeBuffer_disconnects() {
- Mockito.doNothing().when(mBrailleDisplayConnection).disconnect();
- mBrailleDisplayConnection.write(
- new byte[IBinder.getSuggestedMaxIpcSizeBytes() * 2]);
+ @Test
+ public void write_unableToCreateWriteStream_disconnects() {
+ Mockito.doNothing().when(mBrailleDisplayConnection).disconnect();
+ // mBrailleDisplayConnection#connectLocked was never called so the
+ // connection's mHidrawNode is still null. This will throw an exception
+ // when attempting to create FileOutputStream on the node.
+ mBrailleDisplayConnection.write(new byte[1]);
- verify(mBrailleDisplayConnection).disconnect();
- }
+ verify(mBrailleDisplayConnection).disconnect();
+ }
- @Test
- public void write_notConnected_throwsIllegalStateException() {
- when(mServiceConnection.isConnectedLocked()).thenReturn(false);
+ // BrailleDisplayConnection#setTestData() is used to enable CTS testing with
+ // test Braille display data, but its own implementation should also be tested
+ // so that issues in this helper don't cause confusing failures in CTS.
+
+ @Test
+ public void setTestData_scannerReturnsTestData() {
+ Bundle bd1 = new Bundle(), bd2 = new Bundle();
+
+ Path path1 = Path.of("/dev/path1"), path2 = Path.of("/dev/path2");
+ bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH,
+ path1.toString());
+ bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH,
+ path2.toString());
+ byte[] desc1 = {0xB, 0xE}, desc2 = {0xE, 0xF};
+ bd1.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, desc1);
+ bd2.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, desc2);
+ String uniq1 = "uniq1", uniq2 = "uniq2";
+ bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq1);
+ bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq2);
+ int bus1 = BrailleDisplayConnection.BUS_USB, bus2 =
+ BrailleDisplayConnection.BUS_BLUETOOTH;
+ bd1.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH,
+ bus1 == BrailleDisplayConnection.BUS_BLUETOOTH);
+ bd2.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH,
+ bus2 == BrailleDisplayConnection.BUS_BLUETOOTH);
- assertThrows(IllegalStateException.class,
- () -> mBrailleDisplayConnection.write(new byte[1]));
- }
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ mBrailleDisplayConnection.setTestData(List.of(bd1, bd2));
+
+ expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).containsExactly(path1, path2);
+ expect.that(scanner.getDeviceReportDescriptor(path1)).isEqualTo(desc1);
+ expect.that(scanner.getDeviceReportDescriptor(path2)).isEqualTo(desc2);
+ expect.that(scanner.getUniqueId(path1)).isEqualTo(uniq1);
+ expect.that(scanner.getUniqueId(path2)).isEqualTo(uniq2);
+ expect.that(scanner.getDeviceBusType(path1)).isEqualTo(bus1);
+ expect.that(scanner.getDeviceBusType(path2)).isEqualTo(bus2);
+ }
- @Test
- public void write_unableToCreateWriteStream_disconnects() {
- Mockito.doNothing().when(mBrailleDisplayConnection).disconnect();
- // mBrailleDisplayConnection#connectLocked was never called so the
- // connection's mHidrawNode is still null. This will throw an exception
- // when attempting to create FileOutputStream on the node.
- mBrailleDisplayConnection.write(new byte[1]);
+ @Test
+ public void setTestData_emptyTestData_returnsNullNodePaths() {
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ mBrailleDisplayConnection.setTestData(List.of());
- verify(mBrailleDisplayConnection).disconnect();
+ expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).isNull();
+ }
}
- // BrailleDisplayConnection#setTestData() is used to enable CTS testing with
- // test Braille display data, but its own implementation should also be tested
- // so that issues in this helper don't cause confusing failures in CTS.
-
- @Test
- public void setTestData_scannerReturnsTestData() {
- Bundle bd1 = new Bundle(), bd2 = new Bundle();
-
- Path path1 = Path.of("/dev/path1"), path2 = Path.of("/dev/path2");
- bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH, path1.toString());
- bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH, path2.toString());
- byte[] desc1 = {0xB, 0xE}, desc2 = {0xE, 0xF};
- bd1.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, desc1);
- bd2.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, desc2);
- String uniq1 = "uniq1", uniq2 = "uniq2";
- bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq1);
- bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq2);
- int bus1 = BrailleDisplayConnection.BUS_USB, bus2 = BrailleDisplayConnection.BUS_BLUETOOTH;
- bd1.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH,
- bus1 == BrailleDisplayConnection.BUS_BLUETOOTH);
- bd2.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH,
- bus2 == BrailleDisplayConnection.BUS_BLUETOOTH);
-
- BrailleDisplayConnection.BrailleDisplayScanner scanner =
- mBrailleDisplayConnection.setTestData(List.of(bd1, bd2));
-
- expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).containsExactly(path1, path2);
- expect.that(scanner.getDeviceReportDescriptor(path1)).isEqualTo(desc1);
- expect.that(scanner.getDeviceReportDescriptor(path2)).isEqualTo(desc2);
- expect.that(scanner.getUniqueId(path1)).isEqualTo(uniq1);
- expect.that(scanner.getUniqueId(path2)).isEqualTo(uniq2);
- expect.that(scanner.getDeviceBusType(path1)).isEqualTo(bus1);
- expect.that(scanner.getDeviceBusType(path2)).isEqualTo(bus2);
- }
+ @RunWith(Parameterized.class)
+ public static class BrailleDisplayDescriptorTest {
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {"match_BdPage", new byte[]{
+ // Just one item, defines the BD page
+ 0x05, 0x41}},
+ {"match_BdPageAfterAnotherPage", new byte[]{
+ // One item defines another page
+ 0x05, 0x01,
+ // Next item defines BD page
+ 0x05, 0x41}},
+ {"match_BdPageAfterSizeZeroItem", new byte[]{
+ // Size-zero item (last 2 bits are 00)
+ 0x00,
+ // Next item defines BD page
+ 0x05, 0x41}},
+ {"match_BdPageAfterSizeOneItem", new byte[]{
+ // Size-one item (last 2 bits are 01)
+ 0x01, 0x7F,
+ // Next item defines BD page
+ 0x05, 0x41}},
+ {"match_BdPageAfterSizeTwoItem", new byte[]{
+ // Size-two item (last 2 bits are 10)
+ 0x02, 0x7F, 0x7F,
+ 0x05, 0x41}},
+ {"match_BdPageAfterSizeFourItem", new byte[]{
+ // Size-four item (last 2 bits are 11)
+ 0x03, 0x7F, 0x7F, 0x7F, 0x7F,
+ 0x05, 0x41}},
+ {"match_BdPageInBetweenOtherPages", new byte[]{
+ // One item defines another page
+ 0x05, 0x01,
+ // Next item defines BD page
+ 0x05, 0x41,
+ // Next item defines another page
+ 0x05, 0x02}},
+ {"fail_OtherPage", new byte[]{
+ // Just one item, defines another page
+ 0x05, 0x01}},
+ {"fail_BdPageBeforeMissingData", new byte[]{
+ // This item defines BD page
+ 0x05, 0x41,
+ // Next item specifies size-one item (last 2 bits are 01) but
+ // that one data byte is missing; this descriptor is malformed.
+ 0x01}},
+ {"fail_BdPageWithWrongDataSize", new byte[]{
+ // This item defines a page with two-byte ID 0x41 0x7F, not 0x41.
+ 0x06, 0x41, 0x7F}},
+ {"fail_LongItem", new byte[]{
+ // Item has type bits 1111, indicating Long Item.
+ (byte) 0xF0}},
+ });
+ }
- @Test
- public void setTestData_emptyTestData_returnsNullNodePaths() {
- BrailleDisplayConnection.BrailleDisplayScanner scanner =
- mBrailleDisplayConnection.setTestData(List.of());
- expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).isNull();
+ @Parameterized.Parameter(0)
+ public String mTestName;
+ @Parameterized.Parameter(1)
+ public byte[] mDescriptor;
+
+ @Test
+ public void isBrailleDisplay() {
+ final boolean expectedMatch = mTestName.startsWith("match_");
+ assertWithMessage(
+ "Expected isBrailleDisplay==" + expectedMatch
+ + " for descriptor " + HexDump.toHexString(mDescriptor))
+ .that(BrailleDisplayConnection.isBrailleDisplay(mDescriptor))
+ .isEqualTo(expectedMatch);
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 81df597f3f33..3e748ffb37e9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -75,6 +75,7 @@ import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
import android.content.pm.UserInfo;
import android.content.pm.UserPackage;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
@@ -776,6 +777,15 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
new UserInfo(USER_P1, "userP1",
UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE), 0);
+ protected static final UserProperties USER_PROPERTIES_0 =
+ new UserProperties.Builder().setItemsRestrictedOnHomeScreen(false).build();
+
+ protected static final UserProperties USER_PROPERTIES_10 =
+ new UserProperties.Builder().setItemsRestrictedOnHomeScreen(false).build();
+
+ protected static final UserProperties USER_PROPERTIES_11 =
+ new UserProperties.Builder().setItemsRestrictedOnHomeScreen(true).build();
+
protected BiPredicate<String, Integer> mDefaultLauncherChecker =
(callingPackage, userId) ->
LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
@@ -817,6 +827,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
= new HashMap<>();
protected final Map<Integer, UserInfo> mUserInfos = new HashMap<>();
+ protected final Map<Integer, UserProperties> mUserProperties = new HashMap<>();
protected final Map<Integer, Boolean> mRunningUsers = new HashMap<>();
protected final Map<Integer, Boolean> mUnlockedUsers = new HashMap<>();
@@ -911,6 +922,9 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
mUserInfos.put(USER_11, USER_INFO_11);
mUserInfos.put(USER_P0, USER_INFO_P0);
mUserInfos.put(USER_P1, USER_INFO_P1);
+ mUserProperties.put(USER_0, USER_PROPERTIES_0);
+ mUserProperties.put(USER_10, USER_PROPERTIES_10);
+ mUserProperties.put(USER_11, USER_PROPERTIES_11);
when(mMockUserManagerInternal.isUserUnlockingOrUnlocked(anyInt()))
.thenAnswer(inv -> {
@@ -959,6 +973,15 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
inv -> mUserInfos.get((Integer) inv.getArguments()[0])));
when(mMockActivityManagerInternal.getUidProcessState(anyInt())).thenReturn(
ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ when(mMockUserManagerInternal.getUserProperties(anyInt()))
+ .thenAnswer(inv -> {
+ final int userId = (Integer) inv.getArguments()[0];
+ final UserProperties userProperties = mUserProperties.get(userId);
+ if (userProperties == null) {
+ return new UserProperties.Builder().build();
+ }
+ return userProperties;
+ });
// User 0 and P0 are always running
mRunningUsers.put(USER_0, true);
diff --git a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
index f5c6795484fa..771a76517b22 100644
--- a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
+++ b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
@@ -92,7 +92,7 @@ public class SearchablesTest {
public void testNonSearchable() {
// test basic array & hashmap
Searchables searchables = new Searchables(mContext, 0);
- searchables.updateSearchableList();
+ searchables.updateSearchableListIfNeeded();
// confirm that we return null for non-searchy activities
ComponentName nonActivity = new ComponentName("com.android.frameworks.servicestests",
@@ -121,7 +121,7 @@ public class SearchablesTest {
doReturn(true).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt());
Searchables searchables = new Searchables(mContext, 0);
- searchables.updateSearchableList();
+ searchables.updateSearchableListIfNeeded();
// tests with "real" searchables (deprecate, this should be a unit test)
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
int count = searchablesList.size();
@@ -139,7 +139,7 @@ public class SearchablesTest {
doReturn(false).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt());
Searchables searchables = new Searchables(mContext, 0);
- searchables.updateSearchableList();
+ searchables.updateSearchableListIfNeeded();
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
assertNotNull(searchablesList);
MoreAsserts.assertEmpty(searchablesList);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index cff7f460feb5..715c9d4081b2 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -12008,7 +12008,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// style + self managed call - bypasses block
when(mTelecomManager.isInSelfManagedCall(
- r.getSbn().getPackageName(), r.getUser(), true)).thenReturn(true);
+ r.getSbn().getPackageName(), true)).thenReturn(true);
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
@@ -12091,7 +12091,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// style + self managed call - bypasses block
mService.clearNotifications();
reset(mUsageStats);
- when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), r.getUser(), true))
+ when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), true))
.thenReturn(true);
mService.addEnqueuedNotification(r);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index b431888a72fb..3e59878f9e1e 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -35,8 +35,8 @@ import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContextWrapper;
import android.content.pm.PackageManagerInternal;
+import android.os.ExternalVibrationScale;
import android.os.Handler;
-import android.os.IExternalVibratorService;
import android.os.PowerManagerInternal;
import android.os.UserHandle;
import android.os.VibrationAttributes;
@@ -49,6 +49,7 @@ import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -119,29 +120,65 @@ public class VibrationScalerTest {
public void testGetExternalVibrationScale() {
setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
- assertEquals(IExternalVibratorService.SCALE_VERY_HIGH,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
- assertEquals(IExternalVibratorService.SCALE_HIGH,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_HIGH,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
- assertEquals(IExternalVibratorService.SCALE_NONE,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_NONE,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
- assertEquals(IExternalVibratorService.SCALE_LOW,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_LOW,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
- assertEquals(IExternalVibratorService.SCALE_VERY_LOW,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
// Vibration setting being bypassed will use default setting and not scale.
- assertEquals(IExternalVibratorService.SCALE_NONE,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_NONE,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void testAdaptiveHapticsScale_withAdaptiveHapticsAvailable() {
+ setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+ setDefaultIntensity(USAGE_RINGTONE, Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_TOUCH, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.2f);
+
+ assertEquals(0.5f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_TOUCH));
+ assertEquals(0.2f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
+ assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_NOTIFICATION));
+
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ // Vibration setting being bypassed will apply adaptive haptics scales.
+ assertEquals(0.2f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void testAdaptiveHapticsScale_flagDisabled_adaptiveHapticScaleAlwaysNone() {
+ setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+ setDefaultIntensity(USAGE_RINGTONE, Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_TOUCH, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.2f);
+
+ assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_TOUCH));
+ assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
+ assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_NOTIFICATION));
}
@Test
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 2823223e4859..417fbd06be66 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -35,7 +35,6 @@ import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
import android.frameworks.vibrator.ScaleParam;
-import android.frameworks.vibrator.VibrationParam;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -55,8 +54,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -135,7 +132,7 @@ public class VibratorControlServiceTest {
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.onRequestVibrationParamsComplete(token,
- generateVibrationParams(vibrationScales));
+ VibrationParamGenerator.generateVibrationParams(vibrationScales));
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
@@ -162,7 +159,7 @@ public class VibratorControlServiceTest {
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.onRequestVibrationParamsComplete(new Binder(),
- generateVibrationParams(vibrationScales));
+ VibrationParamGenerator.generateVibrationParams(vibrationScales));
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -175,7 +172,8 @@ public class VibratorControlServiceTest {
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
- mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
mFakeVibratorController);
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
@@ -193,7 +191,8 @@ public class VibratorControlServiceTest {
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
- mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
mFakeVibratorController);
verifyZeroInteractions(mMockVibrationScaler);
@@ -268,28 +267,6 @@ public class VibratorControlServiceTest {
}
}
- private VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
- List<VibrationParam> vibrationParamList = new ArrayList<>();
- for (int i = 0; i < vibrationScales.size(); i++) {
- int type = vibrationScales.keyAt(i);
- float scale = vibrationScales.valueAt(i);
-
- vibrationParamList.add(generateVibrationParam(type, scale));
- }
-
- return vibrationParamList.toArray(new VibrationParam[0]);
- }
-
- private VibrationParam generateVibrationParam(int type, float scale) {
- ScaleParam scaleParam = new ScaleParam();
- scaleParam.typesMask = type;
- scaleParam.scale = scale;
- VibrationParam vibrationParam = new VibrationParam();
- vibrationParam.setScale(scaleParam);
-
- return vibrationParam;
- }
-
private int buildVibrationTypesMask(int... types) {
int typesMask = 0;
for (int type : types) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index e7571ef47864..ed89ccf07453 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -50,6 +50,7 @@ import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
+import android.frameworks.vibrator.ScaleParam;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
@@ -59,16 +60,17 @@ import android.media.AudioAttributes;
import android.media.AudioManager;
import android.os.CombinedVibration;
import android.os.ExternalVibration;
+import android.os.ExternalVibrationScale;
import android.os.Handler;
import android.os.IBinder;
import android.os.IExternalVibrationController;
-import android.os.IExternalVibratorService;
import android.os.IVibratorStateListener;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.VibrationAttributes;
@@ -82,6 +84,10 @@ import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.SparseArray;
@@ -153,6 +159,8 @@ public class VibratorManagerServiceTest {
public MockitoRule rule = MockitoJUnit.rule();
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -185,8 +193,10 @@ public class VibratorManagerServiceTest {
private Context mContextSpy;
private TestLooper mTestLooper;
private FakeVibrator mVibrator;
+ private FakeVibratorController mFakeVibratorController;
private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
+ private VibratorControlService mVibratorControlService;
private VibrationConfig mVibrationConfig;
private InputManagerGlobal.TestSession mInputManagerGlobalSession;
private InputManager mInputManager;
@@ -197,6 +207,7 @@ public class VibratorManagerServiceTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
+ mFakeVibratorController = new FakeVibratorController();
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
@@ -310,6 +321,8 @@ public class VibratorManagerServiceTest {
if (service instanceof VibratorManagerService.ExternalVibratorService) {
mExternalVibratorService =
(VibratorManagerService.ExternalVibratorService) service;
+ } else if (service instanceof VibratorControlService) {
+ mVibratorControlService = (VibratorControlService) service;
}
}
@@ -321,9 +334,13 @@ public class VibratorManagerServiceTest {
VibratorControllerHolder createVibratorControllerHolder() {
VibratorControllerHolder holder = new VibratorControllerHolder();
- holder.setVibratorController(new FakeVibratorController());
+ holder.setVibratorController(mFakeVibratorController);
return holder;
}
+
+ boolean isServiceDeclared(String name) {
+ return true;
+ }
});
return mService;
}
@@ -1108,12 +1125,13 @@ public class VibratorManagerServiceTest {
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
controller, firstToken);
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
RINGTONE_ATTRS);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// The external vibration should have been cancelled
verify(controller).mute();
assertEquals(Arrays.asList(false, true, false),
@@ -1708,13 +1726,14 @@ public class VibratorManagerServiceTest {
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
mock(IExternalVibrationController.class), binderToken);
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
when(mVirtualDeviceManagerInternalMock.isAppRunningOnAnyVirtualDevice(UID))
.thenReturn(true);
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
}
@Test
@@ -1727,10 +1746,11 @@ public class VibratorManagerServiceTest {
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
mock(IExternalVibrationController.class), binderToken);
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
mExternalVibratorService.onExternalVibrationStop(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
assertEquals(Arrays.asList(false, true, false),
mVibratorProviders.get(1).getExternalControlStates());
@@ -1753,17 +1773,19 @@ public class VibratorManagerServiceTest {
ExternalVibration firstVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
firstController, firstToken);
- int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration);
+ ExternalVibrationScale firstScale =
+ mExternalVibratorService.onExternalVibrationStart(firstVibration);
AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build();
ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME,
ringtoneAudioAttrs, secondController, secondToken);
- int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
+ ExternalVibrationScale secondScale =
+ mExternalVibratorService.onExternalVibrationStart(secondVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, firstScale);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, secondScale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, firstScale.scaleLevel);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, secondScale.scaleLevel);
verify(firstController).mute();
verify(secondController, never()).mute();
// Set external control called only once.
@@ -1799,8 +1821,9 @@ public class VibratorManagerServiceTest {
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is cancelled.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -1825,9 +1848,10 @@ public class VibratorManagerServiceTest {
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
// External vibration is ignored.
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is not cancelled.
assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
@@ -1852,8 +1876,9 @@ public class VibratorManagerServiceTest {
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is cancelled.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -1879,9 +1904,10 @@ public class VibratorManagerServiceTest {
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_NOTIFICATION_ATTRS,
mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
// New vibration is ignored.
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is not cancelled.
assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
@@ -1901,18 +1927,19 @@ public class VibratorManagerServiceTest {
setRingerMode(AudioManager.RINGER_MODE_SILENT);
createSystemReadyService();
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
createSystemReadyService();
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
createSystemReadyService();
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
}
@Test
@@ -1935,14 +1962,14 @@ public class VibratorManagerServiceTest {
ExternalVibration vib = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(vib);
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(vib);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
mExternalVibratorService.onExternalVibrationStop(vib);
scale = mExternalVibratorService.onExternalVibrationStart(
new ExternalVibration(UID, PACKAGE_NAME, flaggedAudioAttrs,
mock(IExternalVibrationController.class)));
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
}
@Test
@@ -1956,10 +1983,91 @@ public class VibratorManagerServiceTest {
.build();
createSystemReadyService();
- int scale = mExternalVibratorService.onExternalVibrationStart(
- new ExternalVibration(/* uid= */ 123, PACKAGE_NAME, flaggedAudioAttrs,
- mock(IExternalVibrationController.class)));
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(
+ new ExternalVibration(/* uid= */ 123, PACKAGE_NAME, flaggedAudioAttrs,
+ mock(IExternalVibrationController.class)));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales()
+ throws RemoteException {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
+ IVibrator.CAP_AMPLITUDE_CONTROL);
+ createSystemReadyService();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
+ mFakeVibratorController);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ mock(IExternalVibrationController.class));
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 0.7f, 0);
+
+ externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_NOTIFICATION_ATTRS,
+ mock(IExternalVibrationController.class));
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 0.4f, 0);
+
+ AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ ringtoneAudioAttrs,
+ mock(IExternalVibrationController.class));
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone()
+ throws RemoteException {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
+ IVibrator.CAP_AMPLITUDE_CONTROL);
+ createSystemReadyService();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
+ mFakeVibratorController);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ mock(IExternalVibrationController.class));
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
+ mFakeVibratorController);
+ externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_NOTIFICATION_ATTRS,
+ mock(IExternalVibrationController.class));
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 1f, 0);
}
@Test
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
new file mode 100644
index 000000000000..a606388da190
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
@@ -0,0 +1,54 @@
+/*
+ * 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.server.vibrator;
+
+import android.frameworks.vibrator.ScaleParam;
+import android.frameworks.vibrator.VibrationParam;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class that can be used to generate arrays of {@link VibrationParam}.
+ */
+public final class VibrationParamGenerator {
+ /**
+ * Generates an array of {@link VibrationParam}.
+ */
+ public static VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
+ List<VibrationParam> vibrationParamList = new ArrayList<>();
+ for (int i = 0; i < vibrationScales.size(); i++) {
+ int type = vibrationScales.keyAt(i);
+ float scale = vibrationScales.valueAt(i);
+
+ vibrationParamList.add(generateVibrationParam(type, scale));
+ }
+
+ return vibrationParamList.toArray(new VibrationParam[0]);
+ }
+
+ private static VibrationParam generateVibrationParam(int type, float scale) {
+ ScaleParam scaleParam = new ScaleParam();
+ scaleParam.typesMask = type;
+ scaleParam.scale = scale;
+ VibrationParam vibrationParam = new VibrationParam();
+ vibrationParam.setScale(scaleParam);
+
+ return vibrationParam;
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index d84620b4444a..ead36f1d353f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.platform.test.annotations.Presubmit;
+import android.server.wm.BuildUtils;
import android.view.SurfaceControl;
import android.window.SurfaceSyncGroup;
@@ -370,6 +371,7 @@ public class SurfaceSyncGroupTest {
assertEquals(0, finishedLatch.getCount());
}
+ @Test
public void testSurfaceSyncGroupTimeout() throws InterruptedException {
final CountDownLatch finishedLatch = new CountDownLatch(1);
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
@@ -386,7 +388,7 @@ public class SurfaceSyncGroupTest {
// Never finish syncTarget2 so it forces the timeout. Timeout is 1 second so wait a little
// over 1 second to make sure it completes.
- finishedLatch.await(1100, TimeUnit.MILLISECONDS);
+ finishedLatch.await(1100L * BuildUtils.HW_TIMEOUT_MULTIPLIER, TimeUnit.MILLISECONDS);
assertEquals(0, finishedLatch.getCount());
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 889f8429077c..c217780d90d6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2582,7 +2582,6 @@ public class VoiceInteractionManagerService extends SystemService {
if (anyPackagesAppearing()) {
initRecognizer(userHandle);
}
- return;
}
if (curInteractor != null) {
@@ -2631,15 +2630,16 @@ public class VoiceInteractionManagerService extends SystemService {
}
}
- // There is no interactor, so just deal with a simple recognizer.
- int change = isPackageDisappearing(curRecognizer.getPackageName());
- if (change == PACKAGE_PERMANENT_CHANGE
- || change == PACKAGE_TEMPORARY_CHANGE) {
- setCurRecognizer(findAvailRecognizer(null, userHandle), userHandle);
+ if (curRecognizer != null) {
+ int change = isPackageDisappearing(curRecognizer.getPackageName());
+ if (change == PACKAGE_PERMANENT_CHANGE
+ || change == PACKAGE_TEMPORARY_CHANGE) {
+ setCurRecognizer(findAvailRecognizer(null, userHandle), userHandle);
- } else if (isPackageModified(curRecognizer.getPackageName())) {
- setCurRecognizer(findAvailRecognizer(curRecognizer.getPackageName(),
- userHandle), userHandle);
+ } else if (isPackageModified(curRecognizer.getPackageName())) {
+ setCurRecognizer(findAvailRecognizer(curRecognizer.getPackageName(),
+ userHandle), userHandle);
+ }
}
}
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 08c76af70511..9792cdd80a00 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2797,13 +2797,10 @@ public class TelecomManager {
* calls for a given {@code packageName} and {@code userHandle}.
*
* @param packageName the package name of the app to check calls for.
- * @param userHandle the user handle on which to check for calls.
- * @param detectForAllUsers indicates if calls should be detected across all users. If it is
- * set to {@code true}, and the caller has the ability to interact
- * across users, the userHandle parameter is disregarded.
+ * @param userHandle the user handle to check calls for.
* @return {@code true} if there are ongoing calls, {@code false} otherwise.
- * @throws SecurityException if detectForAllUsers is true or userHandle is not the calling user
- * and the caller does not grant the ability to interact across users.
+ * @throws SecurityException if the userHandle is not the calling user and the caller does not
+ * grant the ability to interact across users.
* @hide
*/
@SystemApi
@@ -2811,11 +2808,45 @@ public class TelecomManager {
@RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isInSelfManagedCall(@NonNull String packageName,
- @NonNull UserHandle userHandle, boolean detectForAllUsers) {
+ @NonNull UserHandle userHandle) {
ITelecomService service = getTelecomService();
if (service != null) {
try {
return service.isInSelfManagedCall(packageName, userHandle,
+ mContext.getOpPackageName(), false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is not present");
+ }
+ }
+
+ /**
+ * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
+ * calls for a given {@code packageName} amongst all users, given that detectForAllUsers is true
+ * and the caller has the ability to interact across users. If detectForAllUsers isn't enabled,
+ * the calls will be checked against the caller.
+ *
+ * @param packageName the package name of the app to check calls for.
+ * @param detectForAllUsers indicates if calls should be detected across all users.
+ * @return {@code true} if there are ongoing calls, {@code false} otherwise.
+ * @throws SecurityException if detectForAllUsers is true and the caller does not grant the
+ * ability to interact across users.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ @RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public boolean isInSelfManagedCall(@NonNull String packageName,
+ boolean detectForAllUsers) {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ return service.isInSelfManagedCall(packageName, null,
mContext.getOpPackageName(), detectForAllUsers);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 5af2c3458368..55245419c570 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -856,10 +856,22 @@ public abstract class EuiccService extends Service {
int slotId, IGetAvailableMemoryInBytesCallback callback) {
mExecutor.execute(
() -> {
- long availableMemoryInBytes =
- EuiccService.this.onGetAvailableMemoryInBytes(slotId);
+ long availableMemoryInBytes = EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE;
+ String unsupportedOperationMessage = "";
try {
- callback.onSuccess(availableMemoryInBytes);
+ availableMemoryInBytes =
+ EuiccService.this.onGetAvailableMemoryInBytes(slotId);
+ } catch (UnsupportedOperationException e) {
+ unsupportedOperationMessage = e.getMessage();
+ }
+
+ try {
+ if (!unsupportedOperationMessage.isEmpty()) {
+ callback.onUnsupportedOperationException(
+ unsupportedOperationMessage);
+ } else {
+ callback.onSuccess(availableMemoryInBytes);
+ }
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
diff --git a/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
index bd6d19b81d47..e550e77a3605 100644
--- a/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
+++ b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
@@ -19,4 +19,5 @@ package android.service.euicc;
/** @hide */
oneway interface IGetAvailableMemoryInBytesCallback {
void onSuccess(long availableMemoryInBytes);
+ void onUnsupportedOperationException(String message);
}