summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/KeyguardManager.java3
-rw-r--r--core/java/android/app/Notification.java17
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceInternal.java8
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java5
-rw-r--r--core/java/android/content/res/ApkAssets.java56
-rw-r--r--core/java/android/content/res/ResourceTimer.java56
-rw-r--r--core/java/android/os/ServiceManager.java2
-rw-r--r--core/java/android/os/ServiceManagerNative.java15
-rw-r--r--core/java/android/security/flags.aconfig10
-rw-r--r--core/java/android/view/PointerIcon.java12
-rw-r--r--core/java/android/widget/TextView.java28
-rw-r--r--core/java/android/window/DisplayAreaOrganizer.java8
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig11
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java131
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl3
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java52
-rw-r--r--core/java/com/android/internal/widget/LockscreenCredential.java4
-rw-r--r--core/java/com/android/internal/widget/NotificationExpandButton.java48
-rw-r--r--core/jni/android_content_res_ApkAssets.cpp66
-rw-r--r--core/jni/android_hardware_UsbDeviceConnection.cpp22
-rw-r--r--core/tests/coretests/src/android/os/OWNERS3
-rw-r--r--graphics/java/android/graphics/Paint.java54
-rw-r--r--graphics/java/android/graphics/fonts/FontVariationAxis.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOut.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java158
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutDisplayAreaOrganizer.java157
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/appzoomout/AppZoomOutControllerTest.java87
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt536
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt54
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java56
-rw-r--r--libs/androidfw/ApkAssets.cpp9
-rw-r--r--libs/androidfw/AssetsProvider.cpp82
-rw-r--r--libs/androidfw/Idmap.cpp21
-rw-r--r--libs/androidfw/ResourceTypes.cpp78
-rw-r--r--libs/androidfw/Util.cpp25
-rw-r--r--libs/androidfw/ZipUtils.cpp34
-rw-r--r--libs/androidfw/include/androidfw/ApkAssets.h2
-rw-r--r--libs/androidfw/include/androidfw/AssetsProvider.h25
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h32
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h48
-rw-r--r--libs/androidfw/include/androidfw/misc.h6
-rw-r--r--libs/androidfw/misc.cpp69
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp27
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp8
-rw-r--r--libs/hwui/hwui/Typeface.cpp83
-rw-r--r--libs/hwui/hwui/Typeface.h30
-rw-r--r--libs/hwui/jni/Paint.cpp5
-rw-r--r--libs/hwui/jni/Typeface.cpp40
-rw-r--r--libs/hwui/jni/text/TextShaper.cpp2
-rw-r--r--libs/hwui/tests/common/TestUtils.cpp7
-rw-r--r--libs/hwui/tests/unit/TypefaceTests.cpp252
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig10
-rw-r--r--mime/Android.bp37
-rw-r--r--mime/jarjar-rules-alt.txt1
-rw-r--r--mime/jarjar-rules.txt2
-rw-r--r--native/android/tests/system_health/OWNERS1
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt1
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt1
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt (renamed from packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt)4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java8
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java39
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt125
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt33
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt52
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt100
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt126
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt45
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt8
-rw-r--r--packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt106
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipViewBinding.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModel.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ui/StatusBarUiLayerModule.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt109
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/VolumeDialogKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponentKosmos.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/VolumeDialogRingerViewBinderKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractorKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinderKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModelKosmos.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt84
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinderKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinderKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModelKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModelKosmos.kt37
-rw-r--r--packages/Vcn/service-b/Android.bp4
-rw-r--r--packages/Vcn/service-b/service-utils/android/util/LocalLog.java148
-rw-r--r--packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java142
-rw-r--r--packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt3
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java2
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java12
-rw-r--r--services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java5
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java4
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java2
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java7
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java8
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java34
-rw-r--r--services/core/java/com/android/server/notification/NotificationDelegate.java5
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java34
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java15
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java2
-rw-r--r--services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java1
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java13
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java27
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaPolicy.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java12
-rw-r--r--services/core/java/com/android/server/wm/DragState.java24
-rw-r--r--services/core/java/com/android/server/wm/PersisterQueue.java28
-rw-r--r--services/core/java/com/android/server/wm/SnapshotPersistQueue.java8
-rw-r--r--services/core/java/com/android/server/wm/StartingData.java7
-rw-r--r--services/core/java/com/android/server/wm/Transition.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java15
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java14
-rw-r--r--services/tests/powerstatstests/Android.bp3
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTraceTest.java105
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java128
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java79
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java164
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java7
-rw-r--r--tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java25
196 files changed, 4889 insertions, 1480 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1f3e6559a695..717a2acb4b4a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -105,7 +105,6 @@ import android.content.pm.ServiceInfo;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.content.res.ResourceTimer;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.loader.ResourcesLoader;
@@ -5254,7 +5253,6 @@ public final class ActivityThread extends ClientTransactionHandler
Resources.dumpHistory(pw, "");
pw.flush();
- ResourceTimer.dumpTimers(info.fd.getFileDescriptor(), "-refresh");
if (info.finishCallback != null) {
info.finishCallback.sendResult(null);
}
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 67f7bee4028e..b5ac4e78c7ad 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -70,7 +70,6 @@ import com.android.internal.widget.VerifyCredentialResponse;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.Charset;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -1064,7 +1063,7 @@ public class KeyguardManager {
Log.e(TAG, "Save lock exception", e);
success = false;
} finally {
- Arrays.fill(password, (byte) 0);
+ LockPatternUtils.zeroize(password);
}
return success;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5176aee9051f..252978facac0 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1968,6 +1968,13 @@ public class Notification implements Parcelable
@SystemApi
public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
+ /**
+ * {@link #extras} key to a boolean defining if this action requires special visual
+ * treatment.
+ * @hide
+ */
+ public static final String EXTRA_IS_MAGIC = "android.extra.IS_MAGIC";
+
private final Bundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Icon mIcon;
@@ -6207,7 +6214,7 @@ public class Notification implements Parcelable
int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
- // Use different highlighted colors for e.g. unopened groups
+ // Use different highlighted colors for conversations' unread count
if (p.mHighlightExpander) {
pillColor = Colors.flattenAlpha(
getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
@@ -6807,8 +6814,6 @@ public class Notification implements Parcelable
public RemoteViews makeNotificationGroupHeader() {
return makeNotificationHeader(mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
- // Highlight group expander until the group is first opened
- .highlightExpander(Flags.notificationsRedesignTemplates())
.fillTextsFrom(this));
}
@@ -6984,14 +6989,12 @@ public class Notification implements Parcelable
* @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
* a new subtext is created consisting of the content of the
* notification.
- * @param highlightExpander whether the expander should use the highlighted colors
* @hide
*/
- public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext,
- boolean highlightExpander) {
+ public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
StandardTemplateParams p = mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
- .highlightExpander(highlightExpander)
+ .highlightExpander(false)
.fillTextsFrom(this);
if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) {
p.summaryText(createSummaryText());
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 42c74414ecd9..311e24ba6254 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -83,7 +83,6 @@ import java.util.function.IntConsumer;
public class VirtualDeviceInternal {
private final Context mContext;
- private final IVirtualDeviceManager mService;
private final IVirtualDevice mVirtualDevice;
private final Object mActivityListenersLock = new Object();
@GuardedBy("mActivityListenersLock")
@@ -206,7 +205,6 @@ public class VirtualDeviceInternal {
Context context,
int associationId,
VirtualDeviceParams params) throws RemoteException {
- mService = service;
mContext = context.getApplicationContext();
mVirtualDevice = service.createVirtualDevice(
new Binder(),
@@ -217,11 +215,7 @@ public class VirtualDeviceInternal {
mSoundEffectListener);
}
- VirtualDeviceInternal(
- IVirtualDeviceManager service,
- Context context,
- IVirtualDevice virtualDevice) {
- mService = service;
+ VirtualDeviceInternal(Context context, IVirtualDevice virtualDevice) {
mContext = context.getApplicationContext();
mVirtualDevice = virtualDevice;
try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index ed2fd99c55c5..73ea9f0462d5 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -577,9 +577,8 @@ public final class VirtualDeviceManager {
}
/** @hide */
- public VirtualDevice(IVirtualDeviceManager service, Context context,
- IVirtualDevice virtualDevice) {
- mVirtualDeviceInternal = new VirtualDeviceInternal(service, context, virtualDevice);
+ public VirtualDevice(Context context, IVirtualDevice virtualDevice) {
+ mVirtualDeviceInternal = new VirtualDeviceInternal(context, virtualDevice);
}
/**
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index f538e9ffffdd..075457885586 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -25,7 +25,6 @@ import android.content.res.loader.ResourcesProvider;
import android.ravenwood.annotation.RavenwoodClassLoadHook;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.text.TextUtils;
-import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -51,7 +50,6 @@ import java.util.Objects;
@RavenwoodKeepWholeClass
@RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public final class ApkAssets {
- private static final boolean DEBUG = false;
/**
* The apk assets contains framework resource values specified by the system.
@@ -136,17 +134,6 @@ public final class ApkAssets {
@Nullable
private final AssetsProvider mAssets;
- @NonNull
- private String mName;
-
- private static final int UPTODATE_FALSE = 0;
- private static final int UPTODATE_TRUE = 1;
- private static final int UPTODATE_ALWAYS_TRUE = 2;
-
- // Start with the only value that may change later and would force a native call to
- // double check it.
- private int mPreviousUpToDateResult = UPTODATE_TRUE;
-
/**
* Creates a new ApkAssets instance from the given path on disk.
*
@@ -317,7 +304,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets, path);
+ this(format, flags, assets);
Objects.requireNonNull(path, "path");
mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
@@ -326,7 +313,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
throws IOException {
- this(format, flags, assets, friendlyName);
+ this(format, flags, assets);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
@@ -336,7 +323,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets, friendlyName);
+ this(format, flags, assets);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
@@ -344,17 +331,16 @@ public final class ApkAssets {
}
private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
- this(FORMAT_APK, flags, assets, "empty");
+ this(FORMAT_APK, flags, assets);
mNativePtr = nativeLoadEmpty(flags, assets);
mStringBlock = null;
}
private ApkAssets(@FormatType int format, @PropertyFlags int flags,
- @Nullable AssetsProvider assets, @NonNull String name) {
+ @Nullable AssetsProvider assets) {
mFlags = flags;
mAssets = assets;
mIsOverlay = format == FORMAT_IDMAP;
- if (DEBUG) mName = name;
}
@UnsupportedAppUsage
@@ -435,41 +421,13 @@ public final class ApkAssets {
}
}
- private static double intervalMs(long beginNs, long endNs) {
- return (endNs - beginNs) / 1000000.0;
- }
-
/**
* Returns false if the underlying APK was changed since this ApkAssets was loaded.
*/
public boolean isUpToDate() {
- // This function is performance-critical - it's called multiple times on every Resources
- // object creation, and on few other cache accesses - so it's important to avoid the native
- // call when we know for sure what it will return (which is the case for both ALWAYS_TRUE
- // and FALSE).
- if (mPreviousUpToDateResult != UPTODATE_TRUE) {
- return mPreviousUpToDateResult == UPTODATE_ALWAYS_TRUE;
- }
- final long beforeTs, afterLockTs, afterNativeTs, afterUnlockTs;
- if (DEBUG) beforeTs = System.nanoTime();
- final int res;
synchronized (this) {
- if (DEBUG) afterLockTs = System.nanoTime();
- res = nativeIsUpToDate(mNativePtr);
- if (DEBUG) afterNativeTs = System.nanoTime();
- }
- if (DEBUG) {
- afterUnlockTs = System.nanoTime();
- if (afterUnlockTs - beforeTs >= 10L * 1000000) {
- Log.d("ApkAssets", "isUpToDate(" + mName + ") took "
- + intervalMs(beforeTs, afterUnlockTs)
- + " ms: " + intervalMs(beforeTs, afterLockTs)
- + " / " + intervalMs(afterLockTs, afterNativeTs)
- + " / " + intervalMs(afterNativeTs, afterUnlockTs));
- }
+ return nativeIsUpToDate(mNativePtr);
}
- mPreviousUpToDateResult = res;
- return res != UPTODATE_FALSE;
}
public boolean isSystem() {
@@ -529,7 +487,7 @@ public final class ApkAssets {
private static native @NonNull String nativeGetAssetPath(long ptr);
private static native @NonNull String nativeGetDebugName(long ptr);
private static native long nativeGetStringBlock(long ptr);
- @CriticalNative private static native int nativeIsUpToDate(long ptr);
+ @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
String overlayableName) throws IOException;
diff --git a/core/java/android/content/res/ResourceTimer.java b/core/java/android/content/res/ResourceTimer.java
index 2d1bf4d9d296..d51f64ce8106 100644
--- a/core/java/android/content/res/ResourceTimer.java
+++ b/core/java/android/content/res/ResourceTimer.java
@@ -17,10 +17,13 @@
package android.content.res;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+
import android.app.AppProtoEnums;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemClock;
import android.text.TextUtils;
@@ -30,7 +33,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
-import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -275,40 +277,38 @@ public final class ResourceTimer {
* Update the metrics information and dump it.
* @hide
*/
- public static void dumpTimers(@NonNull FileDescriptor fd, String... args) {
- try (PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd))) {
- pw.println("\nDumping ResourceTimers");
-
- final boolean enabled;
- synchronized (sLock) {
- enabled = sEnabled && sConfig != null;
- }
- if (!enabled) {
+ public static void dumpTimers(@NonNull ParcelFileDescriptor pfd, @Nullable String[] args) {
+ FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
+ PrintWriter pw = new FastPrintWriter(fout);
+ synchronized (sLock) {
+ if (!sEnabled || (sConfig == null)) {
pw.println(" Timers are not enabled in this process");
+ pw.flush();
return;
}
+ }
- // Look for the --refresh switch. If the switch is present, then sTimers is updated.
- // Otherwise, the current value of sTimers is displayed.
- boolean refresh = Arrays.asList(args).contains("-refresh");
-
- synchronized (sLock) {
- update(refresh);
- long runtime = sLastUpdated - sProcessStart;
- pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName());
- for (int i = 0; i < sTimers.length; i++) {
- Timer t = sTimers[i];
- if (t.count != 0) {
- String name = sConfig.timers[i];
- pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
- + "largest=%s\n",
- name, t.count, t.total / t.count, t.mintime, t.maxtime,
- packedString(t.percentile),
- packedString(t.largest));
- }
+ // Look for the --refresh switch. If the switch is present, then sTimers is updated.
+ // Otherwise, the current value of sTimers is displayed.
+ boolean refresh = Arrays.asList(args).contains("-refresh");
+
+ synchronized (sLock) {
+ update(refresh);
+ long runtime = sLastUpdated - sProcessStart;
+ pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName());
+ for (int i = 0; i < sTimers.length; i++) {
+ Timer t = sTimers[i];
+ if (t.count != 0) {
+ String name = sConfig.timers[i];
+ pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
+ + "largest=%s\n",
+ name, t.count, t.total / t.count, t.mintime, t.maxtime,
+ packedString(t.percentile),
+ packedString(t.largest));
}
}
}
+ pw.flush();
}
// Enable (or disabled) the runtime timers. Note that timers are disabled by default. This
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 9085fe09bdaa..a58fea891851 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -278,7 +278,7 @@ public final class ServiceManager {
return service;
} else {
return Binder.allowBlocking(
- getIServiceManager().checkService(name).getServiceWithMetadata().service);
+ getIServiceManager().checkService2(name).getServiceWithMetadata().service);
}
} catch (RemoteException e) {
Log.e(TAG, "error in checkService", e);
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 7ea521ec5dd4..a5aa1b3efcd2 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -62,16 +62,23 @@ class ServiceManagerProxy implements IServiceManager {
@UnsupportedAppUsage
public IBinder getService(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
- return checkService(name).getServiceWithMetadata().service;
+ return checkService2(name).getServiceWithMetadata().service;
}
public Service getService2(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
- return checkService(name);
+ return checkService2(name);
}
- public Service checkService(String name) throws RemoteException {
- return mServiceManager.checkService(name);
+ // TODO(b/355394904): This function has been deprecated, please use checkService2 instead.
+ @UnsupportedAppUsage
+ public IBinder checkService(String name) throws RemoteException {
+ // Same as checkService (old versions of servicemanager had both methods).
+ return checkService2(name).getServiceWithMetadata().service;
+ }
+
+ public Service checkService2(String name) throws RemoteException {
+ return mServiceManager.checkService2(name);
}
public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index ebb6fb451699..4a9e945e62a9 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -42,6 +42,16 @@ flag {
}
flag {
+ name: "secure_array_zeroization"
+ namespace: "platform_security"
+ description: "Enable secure array zeroization"
+ bug: "320392352"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "deprecate_fsv_sig"
namespace: "hardware_backed_security"
description: "Feature flag for deprecating .fsv_sig"
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index b21e85aeeb6a..da3a817f0341 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -514,10 +514,14 @@ public final class PointerIcon implements Parcelable {
final TypedArray a = resources.obtainAttributes(
parser, com.android.internal.R.styleable.PointerIcon);
bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
- hotSpotX = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0)
- * pointerScale;
- hotSpotY = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0)
- * pointerScale;
+ // Cast the hotspot dimensions to int before scaling to match the scaling logic of
+ // the bitmap, whose intrinsic size is also an int before it is scaled.
+ final int unscaledHotSpotX =
+ (int) a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+ final int unscaledHotSpotY =
+ (int) a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+ hotSpotX = unscaledHotSpotX * pointerScale;
+ hotSpotY = unscaledHotSpotY * pointerScale;
a.recycle();
} catch (Exception ex) {
throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 71a832d84f08..99fe0cbdca25 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -18,7 +18,6 @@ package android.widget;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.graphics.Paint.NEW_FONT_VARIATION_MANAGEMENT;
import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
import static android.view.ContentInfo.SOURCE_CLIPBOARD;
@@ -5544,13 +5543,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
}
- final boolean useFontVariationStore = Flags.typefaceRedesignReadonly()
- && CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT);
boolean effective;
- if (useFontVariationStore) {
+ if (Flags.typefaceRedesignReadonly()) {
if (mFontWeightAdjustment != 0
&& mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
- mTextPaint.setFontVariationSettings(fontVariationSettings, mFontWeightAdjustment);
+ List<FontVariationAxis> axes = FontVariationAxis.fromFontVariationSettingsForList(
+ fontVariationSettings);
+ if (axes == null) {
+ return false; // invalid format of the font variation settings.
+ }
+ boolean wghtAdjusted = false;
+ for (int i = 0; i < axes.size(); ++i) {
+ FontVariationAxis axis = axes.get(i);
+ if (axis.getOpenTypeTagValue() == 0x77676874 /* wght */) {
+ axes.set(i, new FontVariationAxis("wght",
+ Math.clamp(axis.getStyleValue() + mFontWeightAdjustment,
+ FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
+ wghtAdjusted = true;
+ }
+ }
+ if (!wghtAdjusted) {
+ axes.add(new FontVariationAxis("wght",
+ Math.clamp(400 + mFontWeightAdjustment,
+ FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
+ }
+ mTextPaint.setFontVariationSettings(
+ FontVariationAxis.toFontVariationSettings(axes));
} else {
mTextPaint.setFontVariationSettings(fontVariationSettings);
}
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 84ce247264f6..bd711fc79083 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -121,6 +121,14 @@ public class DisplayAreaOrganizer extends WindowOrganizer {
public static final int FEATURE_WINDOWING_LAYER = FEATURE_SYSTEM_FIRST + 9;
/**
+ * Display area for rendering app zoom out. When there are multiple layers on the screen,
+ * we want to render these layers based on a depth model. Here we zoom out the layer behind,
+ * whether it's an app or the homescreen.
+ * @hide
+ */
+ public static final int FEATURE_APP_ZOOM_OUT = FEATURE_SYSTEM_FIRST + 10;
+
+ /**
* The last boundary of display area for system features
*/
public static final int FEATURE_SYSTEM_LAST = 10_000;
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index de3e0d3faf43..7a1078f8718f 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -415,6 +415,17 @@ flag {
}
flag {
+ name: "keep_app_window_hide_while_locked"
+ namespace: "windowing_frontend"
+ description: "Do not let app window visible while device is locked"
+ is_fixed_read_only: true
+ bug: "378088391"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "port_window_size_animation"
namespace: "windowing_frontend"
description: "Port window-resize animation from legacy to shell"
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index c9c4be1e2c93..dc440e36ca0d 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -19,6 +19,7 @@ package com.android.internal.os;
import static android.os.BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
import static android.os.BatteryStats.HistoryItem.EVENT_FLAG_START;
import static android.os.BatteryStats.HistoryItem.EVENT_STATE_CHANGE;
+import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -215,6 +216,7 @@ public class BatteryStatsHistory {
private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
private byte mLastHistoryStepLevel = 0;
private boolean mMutable = true;
+ private int mIteratorCookie;
private final BatteryStatsHistory mWritableHistory;
private static class BatteryHistoryFile implements Comparable<BatteryHistoryFile> {
@@ -289,6 +291,7 @@ public class BatteryStatsHistory {
}
void load() {
+ Trace.asyncTraceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.load", 0);
mDirectory.mkdirs();
if (!mDirectory.exists()) {
Slog.wtf(TAG, "HistoryDir does not exist:" + mDirectory.getPath());
@@ -325,8 +328,11 @@ public class BatteryStatsHistory {
}
} finally {
unlock();
+ Trace.asyncTraceEnd(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.load", 0);
}
});
+ } else {
+ Trace.asyncTraceEnd(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.load", 0);
}
}
@@ -418,6 +424,7 @@ public class BatteryStatsHistory {
}
void writeToParcel(Parcel out, boolean useBlobs) {
+ Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.writeToParcel");
lock();
try {
final long start = SystemClock.uptimeMillis();
@@ -443,6 +450,7 @@ public class BatteryStatsHistory {
}
} finally {
unlock();
+ Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
}
}
@@ -482,34 +490,39 @@ public class BatteryStatsHistory {
}
private void cleanup() {
- if (mDirectory == null) {
- return;
- }
-
- if (!tryLock()) {
- mCleanupNeeded = true;
- return;
- }
-
- mCleanupNeeded = false;
+ Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.cleanup");
try {
- // if free disk space is less than 100MB, delete oldest history file.
- if (!hasFreeDiskSpace(mDirectory)) {
- BatteryHistoryFile oldest = mHistoryFiles.remove(0);
- oldest.atomicFile.delete();
+ if (mDirectory == null) {
+ return;
+ }
+
+ if (!tryLock()) {
+ mCleanupNeeded = true;
+ return;
}
- // if there is more history stored than allowed, delete oldest history files.
- int size = getSize();
- while (size > mMaxHistorySize) {
- BatteryHistoryFile oldest = mHistoryFiles.get(0);
- int length = (int) oldest.atomicFile.getBaseFile().length();
- oldest.atomicFile.delete();
- mHistoryFiles.remove(0);
- size -= length;
+ mCleanupNeeded = false;
+ try {
+ // if free disk space is less than 100MB, delete oldest history file.
+ if (!hasFreeDiskSpace(mDirectory)) {
+ BatteryHistoryFile oldest = mHistoryFiles.remove(0);
+ oldest.atomicFile.delete();
+ }
+
+ // if there is more history stored than allowed, delete oldest history files.
+ int size = getSize();
+ while (size > mMaxHistorySize) {
+ BatteryHistoryFile oldest = mHistoryFiles.get(0);
+ int length = (int) oldest.atomicFile.getBaseFile().length();
+ oldest.atomicFile.delete();
+ mHistoryFiles.remove(0);
+ size -= length;
+ }
+ } finally {
+ unlock();
}
} finally {
- unlock();
+ Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
}
}
}
@@ -710,13 +723,18 @@ public class BatteryStatsHistory {
* in the system directory, so it is not safe while actively writing history.
*/
public BatteryStatsHistory copy() {
- synchronized (this) {
- // Make a copy of battery history to avoid concurrent modification.
- Parcel historyBufferCopy = Parcel.obtain();
- historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+ Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.copy");
+ try {
+ synchronized (this) {
+ // Make a copy of battery history to avoid concurrent modification.
+ Parcel historyBufferCopy = Parcel.obtain();
+ historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
- return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
- null, mEventLogger, this);
+ return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null,
+ null, null, mEventLogger, this);
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
}
}
@@ -826,7 +844,7 @@ public class BatteryStatsHistory {
*/
@NonNull
public BatteryStatsHistoryIterator iterate(long startTimeMs, long endTimeMs) {
- if (mMutable) {
+ if (mMutable || mIteratorCookie != 0) {
return copy().iterate(startTimeMs, endTimeMs);
}
@@ -837,7 +855,12 @@ public class BatteryStatsHistory {
mCurrentParcel = null;
mCurrentParcelEnd = 0;
mParcelIndex = 0;
- return new BatteryStatsHistoryIterator(this, startTimeMs, endTimeMs);
+ BatteryStatsHistoryIterator iterator = new BatteryStatsHistoryIterator(
+ this, startTimeMs, endTimeMs);
+ mIteratorCookie = System.identityHashCode(iterator);
+ Trace.asyncTraceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.iterate",
+ mIteratorCookie);
+ return iterator;
}
/**
@@ -848,6 +871,9 @@ public class BatteryStatsHistory {
if (mHistoryDir != null) {
mHistoryDir.unlock();
}
+ Trace.asyncTraceEnd(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.iterate",
+ mIteratorCookie);
+ mIteratorCookie = 0;
}
/**
@@ -949,28 +975,33 @@ public class BatteryStatsHistory {
* @return true if success, false otherwise.
*/
public boolean readFileToParcel(Parcel out, AtomicFile file) {
- byte[] raw = null;
+ Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, "BatteryStatsHistory.read");
try {
- final long start = SystemClock.uptimeMillis();
- raw = file.readFully();
- if (DEBUG) {
- Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath()
- + " duration ms:" + (SystemClock.uptimeMillis() - start));
+ byte[] raw = null;
+ try {
+ final long start = SystemClock.uptimeMillis();
+ raw = file.readFully();
+ if (DEBUG) {
+ Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath()
+ + " duration ms:" + (SystemClock.uptimeMillis() - start));
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
+ return false;
}
- } catch (Exception e) {
- Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
- return false;
- }
- out.unmarshall(raw, 0, raw.length);
- out.setDataPosition(0);
- if (!verifyVersion(out)) {
- return false;
+ out.unmarshall(raw, 0, raw.length);
+ out.setDataPosition(0);
+ if (!verifyVersion(out)) {
+ return false;
+ }
+ // skip monotonic time field.
+ out.readLong();
+ // skip monotonic size field
+ out.readLong();
+ return true;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
}
- // skip monotonic time field.
- out.readLong();
- // skip monotonic size field
- out.readLong();
- return true;
}
/**
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index f14e1f63cdf6..ec0954d5590a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -239,4 +239,7 @@ interface IStatusBarService
/** Unbundle a categorized notification */
void unbundleNotification(String key);
+
+ /** Rebundle an (un)categorized notification */
+ void rebundleNotification(String key);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 39ddea614ee4..74707703f5f2 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -65,6 +65,7 @@ import android.util.SparseLongArray;
import android.view.InputDevice;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.google.android.collect.Lists;
@@ -75,6 +76,7 @@ import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -292,6 +294,56 @@ public class LockPatternUtils {
}
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static byte[] newNonMovableByteArray(int length) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ return new byte[length];
+ }
+ return ArrayUtils.newNonMovableByteArray(length);
+ }
+
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static char[] newNonMovableCharArray(int length) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ return new char[length];
+ }
+ return ArrayUtils.newNonMovableCharArray(length);
+ }
+
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static void zeroize(byte[] array) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ if (array != null) {
+ Arrays.fill(array, (byte) 0);
+ }
+ return;
+ }
+ ArrayUtils.zeroize(array);
+ }
+
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static void zeroize(char[] array) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ if (array != null) {
+ Arrays.fill(array, (char) 0);
+ }
+ return;
+ }
+ ArrayUtils.zeroize(array);
+ }
+
@UnsupportedAppUsage
public DevicePolicyManager getDevicePolicyManager() {
if (mDevicePolicyManager == null) {
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 54b9a225f944..92ce990c67df 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -246,7 +246,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
*/
public void zeroize() {
if (mCredential != null) {
- Arrays.fill(mCredential, (byte) 0);
+ LockPatternUtils.zeroize(mCredential);
mCredential = null;
}
}
@@ -346,7 +346,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
- Arrays.fill(saltedPassword, (byte) 0);
+ LockPatternUtils.zeroize(saltedPassword);
return HexEncoding.encodeToString(ArrayUtils.concat(sha1, md5));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Missing digest algorithm: ", e);
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 80bc4fd89c8d..dd12f69a56fb 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -56,8 +56,6 @@ public class NotificationExpandButton extends FrameLayout {
private int mDefaultTextColor;
private int mHighlightPillColor;
private int mHighlightTextColor;
- // Track whether this ever had mExpanded = true, so that we don't highlight it anymore.
- private boolean mWasExpanded = false;
public NotificationExpandButton(Context context) {
this(context, null, 0, 0);
@@ -136,7 +134,6 @@ public class NotificationExpandButton extends FrameLayout {
int contentDescriptionId;
if (mExpanded) {
if (notificationsRedesignTemplates()) {
- mWasExpanded = true;
drawableId = R.drawable.ic_notification_2025_collapse;
} else {
drawableId = R.drawable.ic_collapse_notification;
@@ -156,8 +153,6 @@ public class NotificationExpandButton extends FrameLayout {
if (!notificationsRedesignTemplates()) {
// changing the expanded state can affect the number display
updateNumber();
- } else {
- updateColors();
}
}
@@ -197,43 +192,22 @@ public class NotificationExpandButton extends FrameLayout {
);
}
- /**
- * Use highlight colors for the expander for groups (when the number is showing) that haven't
- * been opened before, as long as the colors are available.
- */
- private boolean shouldBeHighlighted() {
- return !mWasExpanded && shouldShowNumber()
- && mHighlightPillColor != 0 && mHighlightTextColor != 0;
- }
-
private void updateColors() {
- if (notificationsRedesignTemplates()) {
- if (shouldBeHighlighted()) {
+ if (shouldShowNumber()) {
+ if (mHighlightPillColor != 0) {
mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
- mIconView.setColorFilter(mHighlightTextColor);
+ }
+ mIconView.setColorFilter(mHighlightTextColor);
+ if (mHighlightTextColor != 0) {
mNumberView.setTextColor(mHighlightTextColor);
- } else {
- mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
- mIconView.setColorFilter(mDefaultTextColor);
- mNumberView.setTextColor(mDefaultTextColor);
}
} else {
- if (shouldShowNumber()) {
- if (mHighlightPillColor != 0) {
- mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
- }
- mIconView.setColorFilter(mHighlightTextColor);
- if (mHighlightTextColor != 0) {
- mNumberView.setTextColor(mHighlightTextColor);
- }
- } else {
- if (mDefaultPillColor != 0) {
- mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
- }
- mIconView.setColorFilter(mDefaultTextColor);
- if (mDefaultTextColor != 0) {
- mNumberView.setTextColor(mDefaultTextColor);
- }
+ if (mDefaultPillColor != 0) {
+ mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
+ }
+ mIconView.setColorFilter(mDefaultTextColor);
+ if (mDefaultTextColor != 0) {
+ mNumberView.setTextColor(mDefaultTextColor);
}
}
}
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index e6364a96bd9f..1e7bfe32ba79 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -111,8 +111,9 @@ static void DeleteGuardedApkAssets(Guarded<AssetManager2::ApkAssetsPtr>& apk_ass
class LoaderAssetsProvider : public AssetsProvider {
public:
static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) {
- return std::unique_ptr<AssetsProvider>{
- assets_provider ? new LoaderAssetsProvider(env, assets_provider) : nullptr};
+ return (!assets_provider) ? EmptyAssetsProvider::Create()
+ : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider(
+ env, assets_provider));
}
bool ForEachFile(const std::string& /* root_path */,
@@ -128,8 +129,8 @@ class LoaderAssetsProvider : public AssetsProvider {
return debug_name_;
}
- UpToDate IsUpToDate() const override {
- return UpToDate::Always;
+ bool IsUpToDate() const override {
+ return true;
}
~LoaderAssetsProvider() override {
@@ -211,7 +212,7 @@ class LoaderAssetsProvider : public AssetsProvider {
auto string_result = static_cast<jstring>(env->CallObjectMethod(
assets_provider_, gAssetsProviderOffsets.toString));
ScopedUtfChars str(env, string_result);
- debug_name_ = std::string(str.c_str());
+ debug_name_ = std::string(str.c_str(), str.size());
}
// The global reference to the AssetsProvider
@@ -242,10 +243,10 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma
apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
break;
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
- MultiAssetsProvider::Create(std::move(loader_assets)),
- property_flags);
- break;
+ apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
+ std::move(loader_assets),
+ property_flags);
+ break;
case FORMAT_DIRECTORY: {
auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
DirectoryAssetsProvider::Create(path.c_str()));
@@ -315,11 +316,10 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t
break;
}
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
- nullptr /* path */),
- MultiAssetsProvider::Create(std::move(loader_assets)),
- property_flags);
- break;
+ apk_assets = ApkAssets::LoadTable(
+ AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */),
+ std::move(loader_assets), property_flags);
+ break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -386,15 +386,12 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
break;
}
case FORMAT_ARSC:
- apk_assets =
- ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
- nullptr /* path */,
- static_cast<off64_t>(offset),
- static_cast<off64_t>(
- length)),
- MultiAssetsProvider::Create(std::move(loader_assets)),
- property_flags);
- break;
+ apk_assets = ApkAssets::LoadTable(
+ AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */,
+ static_cast<off64_t>(offset),
+ static_cast<off64_t>(length)),
+ std::move(loader_assets), property_flags);
+ break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -411,16 +408,13 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
}
static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
- auto apk_assets = ApkAssets::Load(MultiAssetsProvider::Create(
- LoaderAssetsProvider::Create(env, assets_provider)),
- flags);
- if (apk_assets == nullptr) {
- const std::string error_msg =
- base::StringPrintf("Failed to load empty assets with provider %p",
- (void*)assets_provider);
- jniThrowException(env, "java/io/IOException", error_msg.c_str());
- return 0;
- }
+ auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags);
+ if (apk_assets == nullptr) {
+ const std::string error_msg =
+ base::StringPrintf("Failed to load empty assets with provider %p", (void*)assets_provider);
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
return CreateGuardedApkAssets(std::move(apk_assets));
}
@@ -449,10 +443,10 @@ static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr)
return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
}
-static jint NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+static jboolean NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
auto apk_assets = scoped_apk_assets->get();
- return (jint)apk_assets->IsUpToDate();
+ return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
}
static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
@@ -564,7 +558,7 @@ static const JNINativeMethod gApkAssetsMethods[] = {
{"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
{"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
// @CriticalNative
- {"nativeIsUpToDate", "(J)I", (void*)NativeIsUpToDate},
+ {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
{"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
{"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
(void*)NativeGetOverlayableInfo},
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index b1221ee38db3..68ef3d424d12 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -165,19 +165,25 @@ android_hardware_UsbDeviceConnection_control_request(JNIEnv *env, jobject thiz,
return -1;
}
- jbyte* bufferBytes = NULL;
- if (buffer) {
- bufferBytes = (jbyte*)env->GetPrimitiveArrayCritical(buffer, NULL);
+ bool is_dir_in = (requestType & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN;
+ std::unique_ptr<jbyte[]> bufferBytes(new (std::nothrow) jbyte[length]);
+ if (!bufferBytes) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return -1;
+ }
+
+ if (!is_dir_in && buffer) {
+ env->GetByteArrayRegion(buffer, start, length, bufferBytes.get());
}
- jint result = usb_device_control_transfer(device, requestType, request,
- value, index, bufferBytes + start, length, timeout);
+ jint bytes_transferred = usb_device_control_transfer(device, requestType, request,
+ value, index, bufferBytes.get(), length, timeout);
- if (bufferBytes) {
- env->ReleasePrimitiveArrayCritical(buffer, bufferBytes, 0);
+ if (bytes_transferred > 0 && is_dir_in) {
+ env->SetByteArrayRegion(buffer, start, bytes_transferred, bufferBytes.get());
}
- return result;
+ return bytes_transferred;
}
static jint
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index c45080fb5e26..5fd4ffc7329a 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -10,6 +10,9 @@ per-file PowerManager*.java = file:/services/core/java/com/android/server/power/
# PerformanceHintManager
per-file PerformanceHintManagerTest.java = file:/ADPF_OWNERS
+# SystemHealthManager
+per-file SystemHealthManagerUnitTest.java = file:/ADPF_OWNERS
+
# Caching
per-file IpcDataCache* = file:/PERFORMANCE_OWNERS
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 50c95a9fa882..3378cc11d565 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -16,9 +16,9 @@
package android.graphics;
+import static com.android.text.flags.Flags.FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API;
import static com.android.text.flags.Flags.FLAG_VERTICAL_TEXT_LAYOUT;
import android.annotation.ColorInt;
@@ -34,7 +34,6 @@ import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.graphics.text.TextRunShaper;
import android.os.Build;
@@ -2100,14 +2099,6 @@ public class Paint {
}
/**
- * A change ID for new font variation settings management.
- * @hide
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = 36)
- public static final long NEW_FONT_VARIATION_MANAGEMENT = 361260253L;
-
- /**
* Sets TrueType or OpenType font variation settings. The settings string is constructed from
* multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
* and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
@@ -2136,16 +2127,12 @@ public class Paint {
* </li>
* </ul>
*
- * <p>Note: If the application that targets API 35 or before, this function mutates the
- * underlying typeface instance.
- *
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
- * @return If the application that targets API 36 or later and is running on devices API 36 or
- * later, this function always returns true. Otherwise, this function returns true if
- * the given settings is effective to at least one font file underlying this typeface.
- * This function also returns true for empty settings string. Otherwise returns false.
+ * @return true if the given settings is effective to at least one font file underlying this
+ * typeface. This function also returns true for empty settings string. Otherwise
+ * returns false
*
* @throws IllegalArgumentException If given string is not a valid font variation settings
* format
@@ -2154,39 +2141,6 @@ public class Paint {
* @see FontVariationAxis
*/
public boolean setFontVariationSettings(String fontVariationSettings) {
- return setFontVariationSettings(fontVariationSettings, 0 /* wght adjust */);
- }
-
- /**
- * Set font variation settings with weight adjustment
- * @hide
- */
- public boolean setFontVariationSettings(String fontVariationSettings, int wghtAdjust) {
- final boolean useFontVariationStore = Flags.typefaceRedesignReadonly()
- && CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT);
- if (useFontVariationStore) {
- FontVariationAxis[] axes =
- FontVariationAxis.fromFontVariationSettings(fontVariationSettings);
- if (axes == null) {
- nSetFontVariationOverride(mNativePaint, 0);
- mFontVariationSettings = null;
- return true;
- }
-
- long builderPtr = nCreateFontVariationBuilder(axes.length);
- for (int i = 0; i < axes.length; ++i) {
- int tag = axes[i].getOpenTypeTagValue();
- float value = axes[i].getStyleValue();
- if (tag == 0x77676874 /* wght */) {
- value = Math.clamp(value + wghtAdjust,
- FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX);
- }
- nAddFontVariationToBuilder(builderPtr, tag, value);
- }
- nSetFontVariationOverride(mNativePaint, builderPtr);
- mFontVariationSettings = fontVariationSettings;
- return true;
- }
final String settings = TextUtils.nullIfEmpty(fontVariationSettings);
if (settings == mFontVariationSettings
|| (settings != null && settings.equals(mFontVariationSettings))) {
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index d1fe2cdbcd77..30a248bb3e0e 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -23,6 +23,7 @@ import android.os.Build;
import android.text.TextUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
@@ -139,9 +140,19 @@ public final class FontVariationAxis {
*/
public static @Nullable FontVariationAxis[] fromFontVariationSettings(
@Nullable String settings) {
- if (settings == null || settings.isEmpty()) {
+ List<FontVariationAxis> result = fromFontVariationSettingsForList(settings);
+ if (result.isEmpty()) {
return null;
}
+ return result.toArray(new FontVariationAxis[0]);
+ }
+
+ /** @hide */
+ public static @NonNull List<FontVariationAxis> fromFontVariationSettingsForList(
+ @Nullable String settings) {
+ if (settings == null || settings.isEmpty()) {
+ return Collections.emptyList();
+ }
final ArrayList<FontVariationAxis> axisList = new ArrayList<>();
final int length = settings.length();
for (int i = 0; i < length; i++) {
@@ -172,9 +183,9 @@ public final class FontVariationAxis {
i = endOfValueString;
}
if (axisList.isEmpty()) {
- return null;
+ return Collections.emptyList();
}
- return axisList.toArray(new FontVariationAxis[0]);
+ return axisList;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOut.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOut.java
new file mode 100644
index 000000000000..9451374befe0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOut.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.appzoomout;
+
+import com.android.wm.shell.shared.annotations.ExternalThread;
+
+/**
+ * Interface to engage with the app zoom out feature.
+ */
+@ExternalThread
+public interface AppZoomOut {
+
+ /**
+ * Called when the zoom out progress is updated, which is used to scale down the current app
+ * surface from fullscreen to the max pushback level we want to apply. {@param progress} ranges
+ * between [0,1], 0 when fullscreen, 1 when it's at the max pushback level.
+ */
+ void setProgress(float progress);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
new file mode 100644
index 000000000000..8cd7b0f48003
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.appzoomout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.Slog;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayChangeController;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+/** Class that manages the app zoom out UI and states. */
+public class AppZoomOutController implements RemoteCallable<AppZoomOutController>,
+ ShellTaskOrganizer.FocusListener, DisplayChangeController.OnDisplayChangingListener {
+
+ private static final String TAG = "AppZoomOutController";
+
+ private final Context mContext;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final DisplayController mDisplayController;
+ private final AppZoomOutDisplayAreaOrganizer mDisplayAreaOrganizer;
+ private final ShellExecutor mMainExecutor;
+ private final AppZoomOutImpl mImpl = new AppZoomOutImpl();
+
+ private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ updateDisplayLayout(displayId);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ updateDisplayLayout(displayId);
+ }
+ };
+
+
+ public static AppZoomOutController create(Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController,
+ DisplayLayout displayLayout, @ShellMainThread ShellExecutor mainExecutor) {
+ AppZoomOutDisplayAreaOrganizer displayAreaOrganizer = new AppZoomOutDisplayAreaOrganizer(
+ context, displayLayout, mainExecutor);
+ return new AppZoomOutController(context, shellInit, shellTaskOrganizer, displayController,
+ displayAreaOrganizer, mainExecutor);
+ }
+
+ @VisibleForTesting
+ AppZoomOutController(Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController,
+ AppZoomOutDisplayAreaOrganizer displayAreaOrganizer,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mTaskOrganizer = shellTaskOrganizer;
+ mDisplayController = displayController;
+ mDisplayAreaOrganizer = displayAreaOrganizer;
+ mMainExecutor = mainExecutor;
+
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mTaskOrganizer.addFocusListener(this);
+
+ mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
+ mDisplayController.addDisplayChangingController(this);
+
+ mDisplayAreaOrganizer.registerOrganizer();
+ }
+
+ public AppZoomOut asAppZoomOut() {
+ return mImpl;
+ }
+
+ public void setProgress(float progress) {
+ mDisplayAreaOrganizer.setProgress(progress);
+ }
+
+ void updateDisplayLayout(int displayId) {
+ final DisplayLayout newDisplayLayout = mDisplayController.getDisplayLayout(displayId);
+ if (newDisplayLayout == null) {
+ Slog.w(TAG, "Failed to get new DisplayLayout.");
+ return;
+ }
+ mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
+ }
+
+ @Override
+ public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (taskInfo == null) {
+ return;
+ }
+ if (taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME) {
+ mDisplayAreaOrganizer.setIsHomeTaskFocused(taskInfo.isFocused);
+ }
+ }
+
+ @Override
+ public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
+ // TODO: verify if there is synchronization issues.
+ mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ @ExternalThread
+ private class AppZoomOutImpl implements AppZoomOut {
+ @Override
+ public void setProgress(float progress) {
+ mMainExecutor.execute(() -> AppZoomOutController.this.setProgress(progress));
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutDisplayAreaOrganizer.java
new file mode 100644
index 000000000000..1c37461b2d2b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutDisplayAreaOrganizer.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.appzoomout;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerToken;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/** Display area organizer that manages the app zoom out UI and states. */
+public class AppZoomOutDisplayAreaOrganizer extends DisplayAreaOrganizer {
+
+ private static final float PUSHBACK_SCALE_FOR_LAUNCHER = 0.05f;
+ private static final float PUSHBACK_SCALE_FOR_APP = 0.025f;
+ private static final float INVALID_PROGRESS = -1;
+
+ private final DisplayLayout mDisplayLayout = new DisplayLayout();
+ private final Context mContext;
+ private final float mCornerRadius;
+ private final Map<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap =
+ new ArrayMap<>();
+
+ private float mProgress = INVALID_PROGRESS;
+ // Denote whether the home task is focused, null when it's not yet initialized.
+ @Nullable private Boolean mIsHomeTaskFocused;
+
+ public AppZoomOutDisplayAreaOrganizer(Context context,
+ DisplayLayout displayLayout, Executor mainExecutor) {
+ super(mainExecutor);
+ mContext = context;
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
+ setDisplayLayout(displayLayout);
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo, SurfaceControl leash) {
+ leash.setUnreleasedWarningCallSite(
+ "AppZoomOutDisplayAreaOrganizer.onDisplayAreaAppeared");
+ mDisplayAreaTokenMap.put(displayAreaInfo.token, leash);
+ }
+
+ @Override
+ public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
+ final SurfaceControl leash = mDisplayAreaTokenMap.get(displayAreaInfo.token);
+ if (leash != null) {
+ leash.release();
+ }
+ mDisplayAreaTokenMap.remove(displayAreaInfo.token);
+ }
+
+ public void registerOrganizer() {
+ final List<DisplayAreaAppearedInfo> displayAreaInfos = registerOrganizer(
+ AppZoomOutDisplayAreaOrganizer.FEATURE_APP_ZOOM_OUT);
+ for (int i = 0; i < displayAreaInfos.size(); i++) {
+ final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
+ onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
+ }
+ }
+
+ @Override
+ public void unregisterOrganizer() {
+ super.unregisterOrganizer();
+ reset();
+ }
+
+ void setProgress(float progress) {
+ if (mProgress == progress) {
+ return;
+ }
+
+ mProgress = progress;
+ apply();
+ }
+
+ void setIsHomeTaskFocused(boolean isHomeTaskFocused) {
+ if (mIsHomeTaskFocused != null && mIsHomeTaskFocused == isHomeTaskFocused) {
+ return;
+ }
+
+ mIsHomeTaskFocused = isHomeTaskFocused;
+ apply();
+ }
+
+ private void apply() {
+ if (mIsHomeTaskFocused == null || mProgress == INVALID_PROGRESS) {
+ return;
+ }
+
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ float scale = mProgress * (mIsHomeTaskFocused
+ ? PUSHBACK_SCALE_FOR_LAUNCHER : PUSHBACK_SCALE_FOR_APP);
+ mDisplayAreaTokenMap.forEach((token, leash) -> updateSurface(tx, leash, scale));
+ tx.apply();
+ }
+
+ void setDisplayLayout(DisplayLayout displayLayout) {
+ mDisplayLayout.set(displayLayout);
+ }
+
+ private void reset() {
+ setProgress(0);
+ mProgress = INVALID_PROGRESS;
+ mIsHomeTaskFocused = null;
+ }
+
+ private void updateSurface(SurfaceControl.Transaction tx, SurfaceControl leash, float scale) {
+ if (scale == 0) {
+ // Reset when scale is set back to 0.
+ tx
+ .setCrop(leash, null)
+ .setScale(leash, 1, 1)
+ .setPosition(leash, 0, 0)
+ .setCornerRadius(leash, 0);
+ return;
+ }
+
+ tx
+ // Rounded corner can only be applied if a crop is set.
+ .setCrop(leash, 0, 0, mDisplayLayout.width(), mDisplayLayout.height())
+ .setScale(leash, 1 - scale, 1 - scale)
+ .setPosition(leash, scale * mDisplayLayout.width() * 0.5f,
+ scale * mDisplayLayout.height() * 0.5f)
+ .setCornerRadius(leash, mCornerRadius * (1 - scale));
+ }
+
+ void onRotateDisplay(Context context, int toRotation) {
+ if (mDisplayLayout.rotation() == toRotation) {
+ return;
+ }
+ mDisplayLayout.rotateTo(context.getResources(), toRotation);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
index c493aadd57b0..151dc438702d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
@@ -20,6 +20,7 @@ import android.os.HandlerThread;
import androidx.annotation.Nullable;
+import com.android.wm.shell.appzoomout.AppZoomOut;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
@@ -112,4 +113,7 @@ public interface WMComponent {
*/
@WMSingleton
Optional<DesktopMode> getDesktopMode();
+
+ @WMSingleton
+ Optional<AppZoomOut> getAppZoomOut();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 090d1444dbef..ab3c33ec7e43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -112,6 +112,8 @@ import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.appzoomout.AppZoomOut;
+import com.android.wm.shell.appzoomout.AppZoomOutController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -1051,6 +1053,20 @@ public abstract class WMShellBaseModule {
}
//
+ // App zoom out (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<AppZoomOut> provideAppZoomOut(
+ Optional<AppZoomOutController> appZoomOutController) {
+ return appZoomOutController.map((controller) -> controller.asAppZoomOut());
+ }
+
+ @BindsOptionalOf
+ abstract AppZoomOutController optionalAppZoomOutController();
+
+ //
// Task Stack
//
@@ -1094,6 +1110,7 @@ public abstract class WMShellBaseModule {
Optional<RecentTasksController> recentTasksOptional,
Optional<RecentsTransitionHandler> recentsTransitionHandlerOptional,
Optional<OneHandedController> oneHandedControllerOptional,
+ Optional<AppZoomOutController> appZoomOutControllerOptional,
Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
Optional<ActivityEmbeddingController> activityEmbeddingOptional,
Optional<MixedTransitionHandler> mixedTransitionHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 483c8008ba65..e8add56619c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -50,6 +50,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.apptoweb.AssistContentRequester;
+import com.android.wm.shell.appzoomout.AppZoomOutController;
import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -945,7 +946,8 @@ public abstract class WMShellModule {
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- WindowDecorTaskResourceLoader taskResourceLoader
+ WindowDecorTaskResourceLoader taskResourceLoader,
+ RecentsTransitionHandler recentsTransitionHandler
) {
if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
return Optional.empty();
@@ -961,7 +963,7 @@ public abstract class WMShellModule {
desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
- taskResourceLoader));
+ taskResourceLoader, recentsTransitionHandler));
}
@WMSingleton
@@ -1313,6 +1315,23 @@ public abstract class WMShellModule {
}
//
+ // App zoom out
+ //
+
+ @WMSingleton
+ @Provides
+ static AppZoomOutController provideAppZoomOutController(
+ Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ DisplayController displayController,
+ DisplayLayout displayLayout,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return AppZoomOutController.create(context, shellInit, shellTaskOrganizer,
+ displayController, displayLayout, mainExecutor);
+ }
+
+ //
// Drag and drop
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
index 32c79a2d02de..8cdb8c4512a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
@@ -17,9 +17,10 @@
package com.android.wm.shell.recents;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.RemoteAnimationTarget;
import android.window.TaskSnapshot;
-import android.os.Bundle;
+import android.window.TransitionInfo;
import com.android.wm.shell.recents.IRecentsAnimationController;
@@ -57,7 +58,8 @@ oneway interface IRecentsAnimationRunner {
*/
void onAnimationStart(in IRecentsAnimationController controller,
in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
- in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras) = 2;
+ in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras,
+ in TransitionInfo info) = 2;
/**
* Called when the task of an activity that has been started while the recents animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 76496b06a4dd..aeccd86e122c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -411,10 +411,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mInstanceId = System.identityHashCode(this);
mListener = listener;
mDeathHandler = () -> {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
- finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
- "deathRecipient");
+ mExecutor.execute(() -> {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
+ finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
+ "deathRecipient");
+ });
};
try {
mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -585,7 +587,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
new RemoteAnimationTarget[0],
- new Rect(0, 0, 0, 0), new Rect(), new Bundle());
+ new Rect(0, 0, 0, 0), new Rect(), new Bundle(),
+ null);
for (int i = 0; i < mStateListeners.size(); i++) {
mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
}
@@ -816,7 +819,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
- new Rect(0, 0, 0, 0), new Rect(), b);
+ new Rect(0, 0, 0, 0), new Rect(), b, info);
for (int i = 0; i < mStateListeners.size(); i++) {
mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
}
@@ -1273,6 +1276,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
"requested"));
}
+ /**
+ * @param runnerFinishCb The remote finish callback to run after finish is complete, this is
+ * not the same as mFinishCb which reports the transition is finished
+ * to WM.
+ */
private void finishInner(boolean toHome, boolean sendUserLeaveHint,
IResultReceiver runnerFinishCb, String reason) {
if (finishSyntheticTransition(runnerFinishCb, reason)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 5aa329108596..b6bd879c75eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2974,9 +2974,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final int transitType = info.getType();
TransitionInfo.Change pipChange = null;
int closingSplitTaskId = -1;
- // This array tracks if we are sending stages TO_BACK in this transition.
- // TODO (b/349828130): Update for n apps
- boolean[] stagesSentToBack = new boolean[2];
+ // This array tracks where we are sending stages (TO_BACK/TO_FRONT) in this transition.
+ // TODO (b/349828130): Update for n apps (needs to handle different indices than 0/1).
+ // Also make sure having multiple changes per stage (2+ tasks in one stage) is being
+ // handled properly.
+ int[] stageChanges = new int[2];
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
@@ -3040,18 +3042,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ " with " + taskId + " before startAnimation().");
}
}
- if (isClosingType(change.getMode()) &&
- getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED) {
-
- // Record which stages are getting sent to back
- if (change.getMode() == TRANSIT_TO_BACK) {
- stagesSentToBack[getStageOfTask(taskId)] = true;
- }
+ final int stageOfTaskId = getStageOfTask(taskId);
+ if (stageOfTaskId == STAGE_TYPE_UNDEFINED) {
+ continue;
+ }
+ if (isClosingType(change.getMode())) {
// (For PiP transitions) If either one of the 2 stages is closing we're assuming
// we'll break split
closingSplitTaskId = taskId;
}
+ if (transitType == WindowManager.TRANSIT_WAKE) {
+ // Record which stages are receiving which changes
+ if ((change.getMode() == TRANSIT_TO_BACK
+ || change.getMode() == TRANSIT_TO_FRONT)
+ && (stageOfTaskId == STAGE_TYPE_MAIN
+ || stageOfTaskId == STAGE_TYPE_SIDE)) {
+ stageChanges[stageOfTaskId] = change.getMode();
+ }
+ }
}
if (pipChange != null) {
@@ -3076,19 +3085,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
- // If keyguard is active, check to see if we have our TO_BACK transitions in order.
- // This array should either be all false (no split stages sent to back) or all true
- // (all stages sent to back). In any other case (which can happen with SHOW_ABOVE_LOCKED
- // apps) we should break split.
- if (mKeyguardActive) {
- boolean isFirstStageSentToBack = stagesSentToBack[0];
- for (boolean b : stagesSentToBack) {
- // Compare each boolean to the first one. If any are different, break split.
- if (b != isFirstStageSentToBack) {
- dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- break;
- }
- }
+ // If keyguard is active, check to see if we have all our stages showing. If one stage
+ // was moved but not the other (which can happen with SHOW_ABOVE_LOCKED apps), we should
+ // break split.
+ if (mKeyguardActive && stageChanges[STAGE_TYPE_MAIN] != stageChanges[STAGE_TYPE_SIDE]) {
+ dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 9fbda46bd2b7..429e0564dd2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -126,6 +126,8 @@ import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt;
import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -157,8 +159,10 @@ import kotlinx.coroutines.MainCoroutineDispatcher;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Supplier;
/**
@@ -247,6 +251,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final DesktopModeEventLogger mDesktopModeEventLogger;
private final DesktopModeUiEventLogger mDesktopModeUiEventLogger;
private final WindowDecorTaskResourceLoader mTaskResourceLoader;
+ private final RecentsTransitionHandler mRecentsTransitionHandler;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -282,7 +287,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- WindowDecorTaskResourceLoader taskResourceLoader) {
+ WindowDecorTaskResourceLoader taskResourceLoader,
+ RecentsTransitionHandler recentsTransitionHandler) {
this(
context,
shellExecutor,
@@ -323,7 +329,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
focusTransitionObserver,
desktopModeEventLogger,
desktopModeUiEventLogger,
- taskResourceLoader);
+ taskResourceLoader,
+ recentsTransitionHandler);
}
@VisibleForTesting
@@ -367,7 +374,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
- WindowDecorTaskResourceLoader taskResourceLoader) {
+ WindowDecorTaskResourceLoader taskResourceLoader,
+ RecentsTransitionHandler recentsTransitionHandler) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -436,6 +444,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDesktopModeEventLogger = desktopModeEventLogger;
mDesktopModeUiEventLogger = desktopModeUiEventLogger;
mTaskResourceLoader = taskResourceLoader;
+ mRecentsTransitionHandler = recentsTransitionHandler;
shellInit.addInitCallback(this::onInit, this);
}
@@ -450,6 +459,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
new DesktopModeOnTaskResizeAnimationListener());
mDesktopTasksController.setOnTaskRepositionAnimationListener(
new DesktopModeOnTaskRepositionAnimationListener());
+ if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
+ mRecentsTransitionHandler.addTransitionStateListener(
+ new DesktopModeRecentsTransitionStateListener());
+ }
mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
try {
mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
@@ -1859,6 +1872,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
}
+ private class DesktopModeRecentsTransitionStateListener
+ implements RecentsTransitionStateListener {
+ final Set<Integer> mAnimatingTaskIds = new HashSet<>();
+
+ @Override
+ public void onTransitionStateChanged(int state) {
+ switch (state) {
+ case RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED:
+ for (int n = 0; n < mWindowDecorByTaskId.size(); n++) {
+ int taskId = mWindowDecorByTaskId.keyAt(n);
+ mAnimatingTaskIds.add(taskId);
+ setIsRecentsTransitionRunningForTask(taskId, true);
+ }
+ return;
+ case RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING:
+ // No Recents transition running - clean up window decorations
+ for (int taskId : mAnimatingTaskIds) {
+ setIsRecentsTransitionRunningForTask(taskId, false);
+ }
+ mAnimatingTaskIds.clear();
+ return;
+ default:
+ }
+ }
+
+ private void setIsRecentsTransitionRunningForTask(int taskId, boolean isRecentsRunning) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) return;
+ decoration.setIsRecentsTransitionRunning(isRecentsRunning);
+ }
+ }
+
private class DragEventListenerImpl
implements DragPositioningCallbackUtility.DragEventListener {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 4ac89546c9c7..39a989ce7c7f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -204,6 +204,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
private final DesktopUserRepositories mDesktopUserRepositories;
+ private boolean mIsRecentsTransitionRunning = false;
private Runnable mLoadAppInfoRunnable;
private Runnable mSetAppInfoRunnable;
@@ -498,7 +499,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
- displayExclusionRegion);
+ displayExclusionRegion, mIsRecentsTransitionRunning);
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -869,7 +870,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean inFullImmersiveMode,
@NonNull InsetsState displayInsetsState,
boolean hasGlobalFocus,
- @NonNull Region displayExclusionRegion) {
+ @NonNull Region displayExclusionRegion,
+ boolean shouldIgnoreCornerRadius) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
final boolean isAppHeader =
captionLayoutId == R.layout.desktop_mode_app_header;
@@ -1006,13 +1008,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mWindowDecorConfig = windowDecorConfig;
if (DesktopModeStatus.useRoundedCorners()) {
- relayoutParams.mCornerRadius = taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- ? loadDimensionPixelSize(context.getResources(),
- R.dimen.desktop_windowing_freeform_rounded_corner_radius)
- : INVALID_CORNER_RADIUS;
+ relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS :
+ getCornerRadius(context, relayoutParams.mLayoutResId);
}
}
+ private static int getCornerRadius(@NonNull Context context, int layoutResId) {
+ if (layoutResId == R.layout.desktop_mode_app_header) {
+ return loadDimensionPixelSize(context.getResources(),
+ R.dimen.desktop_windowing_freeform_rounded_corner_radius);
+ }
+ return INVALID_CORNER_RADIUS;
+ }
+
/**
* If task has focused window decor, return the caption id of the fullscreen caption size
* resource. Otherwise, return ID_NULL and caption width be set to task width.
@@ -1740,6 +1748,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
+ * Declares whether a Recents transition is currently active.
+ *
+ * <p> When a Recents transition is active we allow that transition to take ownership of the
+ * corner radius of its task surfaces, so each window decoration should stop updating the corner
+ * radius of its task surface during that time.
+ */
+ void setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning) {
+ mIsRecentsTransitionRunning = isRecentsTransitionRunning;
+ }
+
+ /**
* Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
*/
void onMaximizeButtonHoverExit() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 5d1bedb85b5e..fa7183ad0fd8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -967,4 +967,4 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects), mFlags);
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/appzoomout/AppZoomOutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/appzoomout/AppZoomOutControllerTest.java
new file mode 100644
index 000000000000..e91a1238a390
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/appzoomout/AppZoomOutControllerTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.appzoomout;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.testing.AndroidTestingRunner;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AppZoomOutControllerTest extends ShellTestCase {
+
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+ @Mock private DisplayController mDisplayController;
+ @Mock private AppZoomOutDisplayAreaOrganizer mDisplayAreaOrganizer;
+ @Mock private ShellExecutor mExecutor;
+ @Mock private ActivityManager.RunningTaskInfo mRunningTaskInfo;
+
+ private AppZoomOutController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Display display = mContext.getDisplay();
+ DisplayLayout displayLayout = new DisplayLayout(mContext, display);
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(displayLayout);
+
+ ShellInit shellInit = spy(new ShellInit(mExecutor));
+ mController = spy(new AppZoomOutController(mContext, shellInit, mTaskOrganizer,
+ mDisplayController, mDisplayAreaOrganizer, mExecutor));
+ }
+
+ @Test
+ public void isHomeTaskFocused_zoomOutForHome() {
+ mRunningTaskInfo.isFocused = true;
+ when(mRunningTaskInfo.getActivityType()).thenReturn(ACTIVITY_TYPE_HOME);
+ mController.onFocusTaskChanged(mRunningTaskInfo);
+
+ verify(mDisplayAreaOrganizer).setIsHomeTaskFocused(true);
+ }
+
+ @Test
+ public void isHomeTaskNotFocused_zoomOutForApp() {
+ mRunningTaskInfo.isFocused = false;
+ when(mRunningTaskInfo.getActivityType()).thenReturn(ACTIVITY_TYPE_HOME);
+ mController.onFocusTaskChanged(mRunningTaskInfo);
+
+ verify(mDisplayAreaOrganizer).setIsHomeTaskFocused(false);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index c0ff2f0652b3..9b24c1c06cec 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -52,6 +52,7 @@ import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.whenever
/** Tests for [DesktopModeEventLogger]. */
@@ -90,20 +91,12 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
val sessionId = desktopModeEventLogger.currentSessionId.get()
assertThat(sessionId).isNotEqualTo(NO_SESSION_ID)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
- /* event */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER),
- /* enter_reason */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER),
- /* exit_reason */
- eq(0),
- /* sessionId */
- eq(sessionId),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneUiChangedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER,
+ 0,
+ sessionId,
+ )
verify {
EventLogTags.writeWmShellEnterDesktopMode(
eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason),
@@ -122,20 +115,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
val sessionId = desktopModeEventLogger.currentSessionId.get()
assertThat(sessionId).isNotEqualTo(NO_SESSION_ID)
assertThat(sessionId).isNotEqualTo(previousSessionId)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
- /* event */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER),
- /* enter_reason */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER),
- /* exit_reason */
- eq(0),
- /* sessionId */
- eq(sessionId),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneUiChangedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER,
+ /* exit_reason */
+ 0,
+ sessionId,
+ )
verify {
EventLogTags.writeWmShellEnterDesktopMode(
eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason),
@@ -149,7 +135,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logSessionExit_noOngoingSession_doesNotLog() {
desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -159,20 +145,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
- /* event */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT),
- /* enter_reason */
- eq(0),
- /* exit_reason */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT),
- /* sessionId */
- eq(sessionId),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneUiChangedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT,
+ /* enter_reason */
+ 0,
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT,
+ sessionId,
+ )
verify {
EventLogTags.writeWmShellExitDesktopMode(
eq(ExitReason.DRAG_TO_EXIT.reason),
@@ -187,7 +166,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logTaskAdded_noOngoingSession_doesNotLog() {
desktopModeEventLogger.logTaskAdded(TASK_UPDATE)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -197,32 +176,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskAdded(TASK_UPDATE)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- eq(UNSET_MINIMIZE_REASON),
- eq(UNSET_UNMINIMIZE_REASON),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
@@ -245,7 +211,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logTaskRemoved_noOngoingSession_doesNotLog() {
desktopModeEventLogger.logTaskRemoved(TASK_UPDATE)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -255,32 +221,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskRemoved(TASK_UPDATE)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- eq(UNSET_MINIMIZE_REASON),
- eq(UNSET_UNMINIMIZE_REASON),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
@@ -303,7 +256,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logTaskInfoChanged_noOngoingSession_doesNotLog() {
desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -313,35 +266,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
- ),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- eq(UNSET_MINIMIZE_REASON),
- eq(UNSET_UNMINIMIZE_REASON),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(
@@ -371,37 +308,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT)
)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
- ),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- /* minimize_reason */
- eq(MinimizeReason.TASK_LIMIT.reason),
- /* unminimize_reason */
- eq(UNSET_UNMINIMIZE_REASON),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ MinimizeReason.TASK_LIMIT.reason,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(
@@ -431,37 +350,19 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP)
)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
- ),
- /* instance_id */
- eq(TASK_UPDATE.instanceId),
- /* uid */
- eq(TASK_UPDATE.uid),
- /* task_height */
- eq(TASK_UPDATE.taskHeight),
- /* task_width */
- eq(TASK_UPDATE.taskWidth),
- /* task_x */
- eq(TASK_UPDATE.taskX),
- /* task_y */
- eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(sessionId),
- /* minimize_reason */
- eq(UNSET_MINIMIZE_REASON),
- /* unminimize_reason */
- eq(UnminimizeReason.TASKBAR_TAP.reason),
- /* visible_task_count */
- eq(TASK_COUNT),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UnminimizeReason.TASKBAR_TAP.reason,
+ TASK_COUNT,
+ )
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(
@@ -491,7 +392,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
createTaskInfo(),
)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -509,39 +410,17 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
displayController,
)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
- /* resize_trigger */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
- ),
- /* resizing_stage */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE
- ),
- /* input_method */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
- ),
- /* desktop_mode_session_id */
- eq(sessionId),
- /* instance_id */
- eq(TASK_SIZE_UPDATE.instanceId),
- /* uid */
- eq(TASK_SIZE_UPDATE.uid),
- /* task_width */
- eq(TASK_SIZE_UPDATE.taskWidth),
- /* task_height */
- eq(TASK_SIZE_UPDATE.taskHeight),
- /* display_area */
- eq(DISPLAY_AREA),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskSizeUpdatedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD,
+ sessionId,
+ TASK_SIZE_UPDATE.instanceId,
+ TASK_SIZE_UPDATE.uid,
+ TASK_SIZE_UPDATE.taskWidth,
+ TASK_SIZE_UPDATE.taskHeight,
+ DISPLAY_AREA,
+ )
}
@Test
@@ -552,7 +431,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
createTaskInfo(),
)
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyNoLogging()
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
}
@@ -568,39 +447,17 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
displayController = displayController,
)
- verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
- /* resize_trigger */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
- ),
- /* resizing_stage */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE
- ),
- /* input_method */
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
- ),
- /* desktop_mode_session_id */
- eq(sessionId),
- /* instance_id */
- eq(TASK_SIZE_UPDATE.instanceId),
- /* uid */
- eq(TASK_SIZE_UPDATE.uid),
- /* task_width */
- eq(TASK_SIZE_UPDATE.taskWidth),
- /* task_height */
- eq(TASK_SIZE_UPDATE.taskHeight),
- /* display_area */
- eq(DISPLAY_AREA),
- )
- }
- verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
+ verifyOnlyOneTaskSizeUpdatedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD,
+ sessionId,
+ TASK_SIZE_UPDATE.instanceId,
+ TASK_SIZE_UPDATE.uid,
+ TASK_SIZE_UPDATE.taskWidth,
+ TASK_SIZE_UPDATE.taskHeight,
+ DISPLAY_AREA,
+ )
}
private fun startDesktopModeSession(): Int {
@@ -652,6 +509,171 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
.build()
}
+ private fun verifyNoLogging() {
+ verify(
+ {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ },
+ never(),
+ )
+ verify(
+ {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ },
+ never(),
+ )
+ verify(
+ {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ },
+ never(),
+ )
+ }
+
+ private fun verifyOnlyOneUiChangedLogging(
+ event: Int,
+ enterReason: Int,
+ exitReason: Int,
+ sessionId: Int,
+ ) {
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ eq(event),
+ eq(enterReason),
+ eq(exitReason),
+ eq(sessionId),
+ )
+ })
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ })
+ }
+
+ private fun verifyOnlyOneTaskUpdateLogging(
+ taskEvent: Int,
+ instanceId: Int,
+ uid: Int,
+ taskHeight: Int,
+ taskWidth: Int,
+ taskX: Int,
+ taskY: Int,
+ sessionId: Int,
+ minimizeReason: Int,
+ unminimizeReason: Int,
+ visibleTaskCount: Int,
+ ) {
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ eq(taskEvent),
+ eq(instanceId),
+ eq(uid),
+ eq(taskHeight),
+ eq(taskWidth),
+ eq(taskX),
+ eq(taskY),
+ eq(sessionId),
+ eq(minimizeReason),
+ eq(unminimizeReason),
+ eq(visibleTaskCount),
+ )
+ })
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ })
+ }
+
+ private fun verifyOnlyOneTaskSizeUpdatedLogging(
+ resizeTrigger: Int,
+ resizingStage: Int,
+ inputMethod: Int,
+ sessionId: Int,
+ instanceId: Int,
+ uid: Int,
+ taskWidth: Int,
+ taskHeight: Int,
+ displayArea: Int,
+ ) {
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ eq(resizeTrigger),
+ eq(resizingStage),
+ eq(inputMethod),
+ eq(sessionId),
+ eq(instanceId),
+ eq(uid),
+ eq(taskWidth),
+ eq(taskHeight),
+ eq(displayArea),
+ )
+ })
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ })
+ }
+
private companion object {
private const val TASK_ID = 1
private const val TASK_UID = 1
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index 894d238b7e15..ab43119b14c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -169,7 +169,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
final IResultReceiver finishCallback = mock(IResultReceiver.class);
final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner);
- verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any(), any());
// Finish and verify no transition remains and that the provided finish callback is called
mRecentsTransitionHandler.findController(transition).finish(true /* toHome */,
@@ -184,7 +184,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner);
- verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any(), any());
mRecentsTransitionHandler.findController(transition).cancel("test");
mMainExecutor.flushAll();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index ffe8e7135513..79e9b9c8cd77 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -59,11 +59,12 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.window.flags.Flags
import com.android.wm.shell.R
-import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
+import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -539,7 +540,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
onLeftSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
+ .snapToHalfScreen(
+ eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
eq(ResizeTrigger.MAXIMIZE_BUTTON),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
eq(decor),
@@ -616,11 +618,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
onRightSnapClickListenerCaptor.value.invoke()
verify(mockDesktopTasksController, never())
- .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
+ .snapToHalfScreen(
+ eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
eq(ResizeTrigger.MAXIMIZE_BUTTON),
eq(InputMethod.UNKNOWN_INPUT_METHOD),
eq(decor),
- )
+ )
}
@Test
@@ -1223,6 +1226,49 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
verify(task2, never()).onExclusionRegionChanged(newRegion)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ fun testRecentsTransitionStateListener_requestedState_setsTransitionRunning() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ onTaskOpening(task, SurfaceControl())
+
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
+
+ verify(decoration).setIsRecentsTransitionRunning(true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ fun testRecentsTransitionStateListener_nonRunningState_setsTransitionNotRunning() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ onTaskOpening(task, SurfaceControl())
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
+
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING)
+
+ verify(decoration).setIsRecentsTransitionRunning(false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ fun testRecentsTransitionStateListener_requestedAndAnimating_setsTransitionRunningOnce() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ onTaskOpening(task, SurfaceControl())
+
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
+ desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
+ RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING)
+
+ verify(decoration, times(1)).setIsRecentsTransitionRunning(true)
+ }
+
private fun createOpenTaskDecoration(
@WindowingMode windowingMode: Int,
taskSurface: SurfaceControl = SurfaceControl(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index b5e8cebc1277..8af8285d031c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -40,6 +40,7 @@ import android.view.SurfaceControl
import android.view.WindowInsets.Type.statusBars
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.InteractionJankMonitor
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
@@ -65,6 +66,8 @@ import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
import com.android.wm.shell.desktopmode.education.AppHandleEducationController
import com.android.wm.shell.desktopmode.education.AppToWebEducationController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -151,6 +154,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockFocusTransitionObserver = mock<FocusTransitionObserver>()
protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>()
protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
+ protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
protected val motionEvent = mock<MotionEvent>()
val displayLayout = mock<DisplayLayout>()
protected lateinit var spyContext: TestableContext
@@ -164,6 +168,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected lateinit var mockitoSession: StaticMockitoSession
protected lateinit var shellInit: ShellInit
internal lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
+ protected lateinit var desktopModeRecentsTransitionStateListener: RecentsTransitionStateListener
protected lateinit var displayChangingListener:
DisplayChangeController.OnDisplayChangingListener
internal lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
@@ -219,7 +224,8 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockFocusTransitionObserver,
desktopModeEventLogger,
mock<DesktopModeUiEventLogger>(),
- mock<WindowDecorTaskResourceLoader>()
+ mock<WindowDecorTaskResourceLoader>(),
+ mockRecentsTransitionHandler,
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -256,6 +262,13 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
verify(displayInsetsController)
.addGlobalInsetsChangedListener(insetsChangedCaptor.capture())
desktopModeOnInsetsChangedListener = insetsChangedCaptor.firstValue
+ val recentsTransitionStateListenerCaptor = argumentCaptor<RecentsTransitionStateListener>()
+ if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
+ verify(mockRecentsTransitionHandler)
+ .addTransitionStateListener(recentsTransitionStateListenerCaptor.capture())
+ desktopModeRecentsTransitionStateListener =
+ recentsTransitionStateListenerCaptor.firstValue
+ }
val keyguardChangedCaptor =
argumentCaptor<DesktopModeKeyguardChangeListener>()
verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 6b02aeffd42a..9ea5fd6e1abe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -169,6 +169,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final boolean DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED = false;
private static final boolean DEFAULT_IS_IN_FULL_IMMERSIVE_MODE = false;
private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true;
+ private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false;
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -396,6 +397,31 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ fillRoundedCornersResources(/* fillValue= */ 30);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+ DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+ DEFAULT_IS_STATUSBAR_VISIBLE,
+ DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+ DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ new InsetsState(),
+ DEFAULT_HAS_GLOBAL_FOCUS,
+ mExclusionRegion,
+ /* shouldIgnoreCornerRadius= */ true);
+
+ assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
public void updateRelayoutParams_appHeader_usesTaskDensity() {
final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
@@ -634,7 +660,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
insetsState,
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
// Takes status bar inset as padding, ignores caption bar inset.
assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -659,7 +686,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsInsetSource).isFalse();
}
@@ -683,7 +711,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
// Header is always shown because it's assumed the status bar is always visible.
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -707,7 +736,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -730,7 +760,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -753,7 +784,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -777,7 +809,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -793,7 +826,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -817,7 +851,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* inFullImmersiveMode */ true,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -1480,7 +1515,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion);
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
}
private DesktopModeWindowDecoration createWindowDecoration(
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index e693fcfd3918..dbb891455ddd 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -162,13 +162,10 @@ const std::string& ApkAssets::GetDebugName() const {
return assets_provider_->GetDebugName();
}
-UpToDate ApkAssets::IsUpToDate() const {
+bool ApkAssets::IsUpToDate() const {
// Loaders are invalidated by the app, not the system, so assume they are up to date.
- if (IsLoader()) {
- return UpToDate::Always;
- }
- const auto idmap_res = loaded_idmap_ ? loaded_idmap_->IsUpToDate() : UpToDate::Always;
- return combine(idmap_res, [this] { return assets_provider_->IsUpToDate(); });
+ return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate())
+ && assets_provider_->IsUpToDate());
}
} // namespace android
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 11b12eb030a6..2d3c06506a1f 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -24,8 +24,9 @@
#include <ziparchive/zip_archive.h>
namespace android {
-
-static constexpr std::string_view kEmptyDebugString = "<empty>";
+namespace {
+constexpr const char* kEmptyDebugString = "<empty>";
+} // namespace
std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode,
bool* file_exists) const {
@@ -85,9 +86,11 @@ void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const {
}
ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
- package_property_t flags, ModDate last_mod_time)
- : zip_handle_(handle), name_(std::move(path)), flags_(flags), last_mod_time_(last_mod_time) {
-}
+ package_property_t flags, time_t last_mod_time)
+ : zip_handle_(handle),
+ name_(std::move(path)),
+ flags_(flags),
+ last_mod_time_(last_mod_time) {}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
package_property_t flags,
@@ -101,10 +104,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
return {};
}
- ModDate mod_date = kInvalidModDate;
+ struct stat sb{.st_mtime = -1};
// Skip all up-to-date checks if the file won't ever change.
- if (isKnownWritablePath(path.c_str()) || !isReadonlyFilesystem(GetFileDescriptor(handle))) {
- if (mod_date = getFileModDate(GetFileDescriptor(handle)); mod_date == kInvalidModDate) {
+ if (!isReadonlyFilesystem(path.c_str())) {
+ if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
@@ -113,7 +116,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
}
return std::unique_ptr<ZipAssetsProvider>(
- new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, mod_date));
+ new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime));
}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
@@ -134,10 +137,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
return {};
}
- ModDate mod_date = kInvalidModDate;
+ struct stat sb{.st_mtime = -1};
// Skip all up-to-date checks if the file won't ever change.
if (!isReadonlyFilesystem(released_fd)) {
- if (mod_date = getFileModDate(released_fd); mod_date == kInvalidModDate) {
+ if (fstat(released_fd, &sb) < 0) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
@@ -147,7 +150,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
}
return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider(
- handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, mod_date));
+ handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime));
}
std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
@@ -279,16 +282,21 @@ const std::string& ZipAssetsProvider::GetDebugName() const {
return name_.GetDebugName();
}
-UpToDate ZipAssetsProvider::IsUpToDate() const {
- if (last_mod_time_ == kInvalidModDate) {
- return UpToDate::Always;
+bool ZipAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == -1) {
+ return true;
+ }
+ struct stat sb{};
+ if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
+ // If fstat fails on the zip archive, return true so the zip archive the resource system does
+ // attempt to refresh the ApkAsset.
+ return true;
}
- return fromBool(last_mod_time_ == getFileModDate(GetFileDescriptor(zip_handle_.get())));
+ return last_mod_time_ == sb.st_mtime;
}
-DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time)
- : dir_(std::move(path)), last_mod_time_(last_mod_time) {
-}
+DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
+ : dir_(std::move(path)), last_mod_time_(last_mod_time) {}
std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
struct stat sb;
@@ -309,7 +317,7 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st
const bool isReadonly = isReadonlyFilesystem(path.c_str());
return std::unique_ptr<DirectoryAssetsProvider>(
- new DirectoryAssetsProvider(std::move(path), isReadonly ? kInvalidModDate : getModDate(sb)));
+ new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
}
std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -338,11 +346,17 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const {
return dir_;
}
-UpToDate DirectoryAssetsProvider::IsUpToDate() const {
- if (last_mod_time_ == kInvalidModDate) {
- return UpToDate::Always;
+bool DirectoryAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == -1) {
+ return true;
+ }
+ struct stat sb;
+ if (stat(dir_.c_str(), &sb) < 0) {
+ // If stat fails on the zip archive, return true so the zip archive the resource system does
+ // attempt to refresh the ApkAsset.
+ return true;
}
- return fromBool(last_mod_time_ == getFileModDate(dir_.c_str()));
+ return last_mod_time_ == sb.st_mtime;
}
MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
@@ -355,14 +369,8 @@ MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& prima
std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create(
std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) {
- if (primary == nullptr && secondary == nullptr) {
- return EmptyAssetsProvider::Create();
- }
- if (!primary) {
- return secondary;
- }
- if (!secondary) {
- return primary;
+ if (primary == nullptr || secondary == nullptr) {
+ return nullptr;
}
return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary),
std::move(secondary)));
@@ -389,8 +397,8 @@ const std::string& MultiAssetsProvider::GetDebugName() const {
return debug_name_;
}
-UpToDate MultiAssetsProvider::IsUpToDate() const {
- return combine(primary_->IsUpToDate(), [this] { return secondary_->IsUpToDate(); });
+bool MultiAssetsProvider::IsUpToDate() const {
+ return primary_->IsUpToDate() && secondary_->IsUpToDate();
}
EmptyAssetsProvider::EmptyAssetsProvider(std::optional<std::string>&& path) :
@@ -430,12 +438,12 @@ const std::string& EmptyAssetsProvider::GetDebugName() const {
if (path_.has_value()) {
return *path_;
}
- constexpr static std::string kEmpty{kEmptyDebugString};
+ const static std::string kEmpty = kEmptyDebugString;
return kEmpty;
}
-UpToDate EmptyAssetsProvider::IsUpToDate() const {
- return UpToDate::Always;
+bool EmptyAssetsProvider::IsUpToDate() const {
+ return true;
}
} // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 262e7df185b7..3ecd82b074a1 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -22,10 +22,9 @@
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/utf8.h"
-#include "androidfw/AssetManager.h"
+#include "androidfw/misc.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
-#include "androidfw/misc.h"
#include "utils/ByteOrder.h"
#include "utils/Trace.h"
@@ -269,16 +268,11 @@ LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* head
configurations_(configs),
overlay_entries_(overlay_entries),
string_pool_(std::move(string_pool)),
+ idmap_fd_(
+ android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)),
overlay_apk_path_(overlay_apk_path),
target_apk_path_(target_apk_path),
- idmap_last_mod_time_(kInvalidModDate) {
- if (!isReadonlyFilesystem(std::string(overlay_apk_path_).c_str()) ||
- !(target_apk_path_ == AssetManager::TARGET_APK_PATH ||
- isReadonlyFilesystem(std::string(target_apk_path_).c_str()))) {
- idmap_fd_.reset(
- android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH));
- idmap_last_mod_time_ = getFileModDate(idmap_fd_);
- }
+ idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {
}
std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
@@ -387,11 +381,8 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie
overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path));
}
-UpToDate LoadedIdmap::IsUpToDate() const {
- if (idmap_last_mod_time_ == kInvalidModDate) {
- return UpToDate::Always;
- }
- return fromBool(idmap_last_mod_time_ == getFileModDate(idmap_fd_.get()));
+bool LoadedIdmap::IsUpToDate() const {
+ return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get());
}
} // namespace android
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index a8eb062a2ece..de9991a8be5e 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -152,11 +152,12 @@ static void fill9patchOffsets(Res_png_9patch* patch) {
patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t));
}
-void Res_value::copyFrom_dtoh_slow(const Res_value& src) {
- size = dtohs(src.size);
- res0 = src.res0;
- dataType = src.dataType;
- data = dtohl(src.data);
+void Res_value::copyFrom_dtoh(const Res_value& src)
+{
+ size = dtohs(src.size);
+ res0 = src.res0;
+ dataType = src.dataType;
+ data = dtohl(src.data);
}
void Res_png_9patch::deviceToFile()
@@ -2030,6 +2031,16 @@ status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const
// --------------------------------------------------------------------
// --------------------------------------------------------------------
+void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
+ const size_t size = dtohl(o.size);
+ if (size >= sizeof(ResTable_config)) {
+ *this = o;
+ } else {
+ memcpy(this, &o, size);
+ memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
+ }
+}
+
/* static */ size_t unpackLanguageOrRegion(const char in[2], const char base,
char out[4]) {
if (in[0] & 0x80) {
@@ -2094,33 +2105,34 @@ size_t ResTable_config::unpackRegion(char region[4]) const {
return unpackLanguageOrRegion(this->country, '0', region);
}
-void ResTable_config::copyFromDtoH_slow(const ResTable_config& o) {
- copyFromDeviceNoSwap(o);
- size = sizeof(ResTable_config);
- mcc = dtohs(mcc);
- mnc = dtohs(mnc);
- density = dtohs(density);
- screenWidth = dtohs(screenWidth);
- screenHeight = dtohs(screenHeight);
- sdkVersion = dtohs(sdkVersion);
- minorVersion = dtohs(minorVersion);
- smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
- screenWidthDp = dtohs(screenWidthDp);
- screenHeightDp = dtohs(screenHeightDp);
-}
-
-void ResTable_config::swapHtoD_slow() {
- size = htodl(size);
- mcc = htods(mcc);
- mnc = htods(mnc);
- density = htods(density);
- screenWidth = htods(screenWidth);
- screenHeight = htods(screenHeight);
- sdkVersion = htods(sdkVersion);
- minorVersion = htods(minorVersion);
- smallestScreenWidthDp = htods(smallestScreenWidthDp);
- screenWidthDp = htods(screenWidthDp);
- screenHeightDp = htods(screenHeightDp);
+
+void ResTable_config::copyFromDtoH(const ResTable_config& o) {
+ copyFromDeviceNoSwap(o);
+ size = sizeof(ResTable_config);
+ mcc = dtohs(mcc);
+ mnc = dtohs(mnc);
+ density = dtohs(density);
+ screenWidth = dtohs(screenWidth);
+ screenHeight = dtohs(screenHeight);
+ sdkVersion = dtohs(sdkVersion);
+ minorVersion = dtohs(minorVersion);
+ smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
+ screenWidthDp = dtohs(screenWidthDp);
+ screenHeightDp = dtohs(screenHeightDp);
+}
+
+void ResTable_config::swapHtoD() {
+ size = htodl(size);
+ mcc = htods(mcc);
+ mnc = htods(mnc);
+ density = htods(density);
+ screenWidth = htods(screenWidth);
+ screenHeight = htods(screenHeight);
+ sdkVersion = htods(sdkVersion);
+ minorVersion = htods(minorVersion);
+ smallestScreenWidthDp = htods(smallestScreenWidthDp);
+ screenWidthDp = htods(screenWidthDp);
+ screenHeightDp = htods(screenHeightDp);
}
/* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) {
@@ -2133,7 +2145,7 @@ void ResTable_config::swapHtoD_slow() {
// systems should happen very infrequently (if at all.)
// The comparison code relies on memcmp low-level optimizations that make it
// more efficient than strncmp.
- static constexpr char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
+ const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript;
const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 86c459fb4647..be55fe8b4bb6 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -32,18 +32,13 @@ namespace android {
namespace util {
void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) {
- static constexpr bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
- if constexpr (kDeviceEndiannessSame) {
- *out = Utf16ToUtf8({(const char16_t*)src, strnlen16((const char16_t*)src, len)});
- } else {
- char buf[5];
- while (*src && len != 0) {
- char16_t c = static_cast<char16_t>(dtohs(*src));
- utf16_to_utf8(&c, 1, buf, sizeof(buf));
- out->append(buf, strlen(buf));
- ++src;
- --len;
- }
+ char buf[5];
+ while (*src && len != 0) {
+ char16_t c = static_cast<char16_t>(dtohs(*src));
+ utf16_to_utf8(&c, 1, buf, sizeof(buf));
+ out->append(buf, strlen(buf));
+ ++src;
+ --len;
}
}
@@ -68,10 +63,8 @@ std::string Utf16ToUtf8(StringPiece16 utf16) {
}
std::string utf8;
- utf8.resize_and_overwrite(utf8_length, [&utf16](char* data, size_t size) {
- utf16_to_utf8(utf16.data(), utf16.length(), data, size + 1);
- return size;
- });
+ utf8.resize(utf8_length);
+ utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
return utf8;
}
diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
index a1385f2cf7b1..f7f62c51a25b 100644
--- a/libs/androidfw/ZipUtils.cpp
+++ b/libs/androidfw/ZipUtils.cpp
@@ -87,19 +87,29 @@ class BufferReader final : public zip_archive::Reader {
}
bool ReadAtOffset(uint8_t* buf, size_t len, off64_t offset) const override {
- if (mInputSize < len || offset > mInputSize - len) {
- return false;
- }
-
- const incfs::map_ptr<uint8_t> pos = mInput.offset(offset);
- if (!pos.verify(len)) {
+ auto in = AccessAtOffset(buf, len, offset);
+ if (!in) {
return false;
}
-
- memcpy(buf, pos.unsafe_ptr(), len);
+ memcpy(buf, in, len);
return true;
}
+ const uint8_t* AccessAtOffset(uint8_t*, size_t len, off64_t offset) const override {
+ if (offset > mInputSize - len) {
+ return nullptr;
+ }
+ const incfs::map_ptr<uint8_t> pos = mInput.offset(offset);
+ if (!pos.verify(len)) {
+ return nullptr;
+ }
+ return pos.unsafe_ptr();
+ }
+
+ bool IsZeroCopy() const override {
+ return true;
+ }
+
private:
const incfs::map_ptr<uint8_t> mInput;
const size_t mInputSize;
@@ -107,7 +117,7 @@ class BufferReader final : public zip_archive::Reader {
class BufferWriter final : public zip_archive::Writer {
public:
- BufferWriter(void* output, size_t outputSize) : Writer(),
+ BufferWriter(void* output, size_t outputSize) :
mOutput(reinterpret_cast<uint8_t*>(output)), mOutputSize(outputSize), mBytesWritten(0) {
}
@@ -121,6 +131,12 @@ class BufferWriter final : public zip_archive::Writer {
return true;
}
+ Buffer GetBuffer(size_t length) override {
+ const auto remaining_size = mOutputSize - mBytesWritten;
+ return remaining_size >= length
+ ? Buffer(mOutput + mBytesWritten, remaining_size) : Buffer();
+ }
+
private:
uint8_t* const mOutput;
const size_t mOutputSize;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 3f6f4661f2f7..231808beb718 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -116,7 +116,7 @@ class ApkAssets : public RefBase {
return resources_asset_ != nullptr && resources_asset_->isAllocated();
}
- UpToDate IsUpToDate() const;
+ bool IsUpToDate() const;
// DANGER!
// This is a destructive method that rips the assets provider out of ApkAssets object.
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index e3b3ae41f7f4..d33c325ff369 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-#pragma once
+#ifndef ANDROIDFW_ASSETSPROVIDER_H
+#define ANDROIDFW_ASSETSPROVIDER_H
#include <memory>
#include <string>
@@ -57,7 +58,7 @@ struct AssetsProvider {
WARN_UNUSED virtual const std::string& GetDebugName() const = 0;
// Returns whether the interface provides the most recent version of its files.
- WARN_UNUSED virtual UpToDate IsUpToDate() const = 0;
+ WARN_UNUSED virtual bool IsUpToDate() const = 0;
// Creates an Asset from a file on disk.
static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
@@ -94,7 +95,7 @@ struct ZipAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED UpToDate IsUpToDate() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const;
~ZipAssetsProvider() override = default;
@@ -105,7 +106,7 @@ struct ZipAssetsProvider : public AssetsProvider {
private:
struct PathOrDebugName;
ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, package_property_t flags,
- ModDate last_mod_time);
+ time_t last_mod_time);
struct PathOrDebugName {
static PathOrDebugName Path(std::string value) {
@@ -134,7 +135,7 @@ struct ZipAssetsProvider : public AssetsProvider {
std::unique_ptr<ZipArchive, ZipCloser> zip_handle_;
PathOrDebugName name_;
package_property_t flags_;
- ModDate last_mod_time_;
+ time_t last_mod_time_;
};
// Supplies assets from a root directory.
@@ -146,7 +147,7 @@ struct DirectoryAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED UpToDate IsUpToDate() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
~DirectoryAssetsProvider() override = default;
protected:
@@ -155,23 +156,23 @@ struct DirectoryAssetsProvider : public AssetsProvider {
bool* file_exists) const override;
private:
- explicit DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time);
+ explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time);
std::string dir_;
- ModDate last_mod_time_;
+ time_t last_mod_time_;
};
// Supplies assets from a `primary` asset provider and falls back to supplying assets from the
// `secondary` asset provider if the asset cannot be found in the `primary`.
struct MultiAssetsProvider : public AssetsProvider {
static std::unique_ptr<AssetsProvider> Create(std::unique_ptr<AssetsProvider>&& primary,
- std::unique_ptr<AssetsProvider>&& secondary = {});
+ std::unique_ptr<AssetsProvider>&& secondary);
bool ForEachFile(const std::string& root_path,
base::function_ref<void(StringPiece, FileType)> f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED UpToDate IsUpToDate() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
~MultiAssetsProvider() override = default;
protected:
@@ -198,7 +199,7 @@ struct EmptyAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED UpToDate IsUpToDate() const override;
+ WARN_UNUSED bool IsUpToDate() const override;
~EmptyAssetsProvider() override = default;
protected:
@@ -211,3 +212,5 @@ struct EmptyAssetsProvider : public AssetsProvider {
};
} // namespace android
+
+#endif /* ANDROIDFW_ASSETSPROVIDER_H */
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 87f3c9df9a91..ac75eb3bb98c 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-#pragma once
+#ifndef IDMAP_H_
+#define IDMAP_H_
#include <memory>
#include <string>
@@ -31,31 +32,6 @@
namespace android {
-// An enum that tracks more states than just 'up to date' or 'not' for a resources container:
-// there are several cases where we know for sure that the object can't change and won't get
-// out of date. Reporting those states to the managed layer allows it to stop checking here
-// completely, speeding up the cache lookups by dozens of milliseconds.
-enum class UpToDate : int { False, True, Always };
-
-// Combines two UpToDate values, and only accesses the second one if it matters to the result.
-template <class Getter>
-UpToDate combine(UpToDate first, Getter secondGetter) {
- switch (first) {
- case UpToDate::False:
- return UpToDate::False;
- case UpToDate::True: {
- const auto second = secondGetter();
- return second == UpToDate::False ? UpToDate::False : UpToDate::True;
- }
- case UpToDate::Always:
- return secondGetter();
- }
-}
-
-inline UpToDate fromBool(bool value) {
- return value ? UpToDate::True : UpToDate::False;
-}
-
class LoadedIdmap;
class IdmapResMap;
struct Idmap_header;
@@ -220,7 +196,7 @@ class LoadedIdmap {
// Returns whether the idmap file on disk has not been modified since the construction of this
// LoadedIdmap.
- UpToDate IsUpToDate() const;
+ bool IsUpToDate() const;
protected:
// Exposed as protected so that tests can subclass and mock this class out.
@@ -255,3 +231,5 @@ class LoadedIdmap {
};
} // namespace android
+
+#endif // IDMAP_H_
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 819fe4b38c87..e330410ed1a0 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -47,8 +47,6 @@
namespace android {
-constexpr const bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
-
constexpr const uint32_t kIdmapMagic = 0x504D4449u;
constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Au;
@@ -410,16 +408,7 @@ struct Res_value
typedef uint32_t data_type;
data_type data;
- void copyFrom_dtoh(const Res_value& src) {
- if constexpr (kDeviceEndiannessSame) {
- *this = src;
- } else {
- copyFrom_dtoh_slow(src);
- }
- }
-
- private:
- void copyFrom_dtoh_slow(const Res_value& src);
+ void copyFrom_dtoh(const Res_value& src);
};
/**
@@ -1265,32 +1254,11 @@ struct ResTable_config
// Varies in length from 3 to 8 chars. Zero-filled value.
char localeNumberingSystem[8];
- void copyFromDeviceNoSwap(const ResTable_config& o) {
- const auto o_size = dtohl(o.size);
- if (o_size >= sizeof(ResTable_config)) [[likely]] {
- *this = o;
- } else {
- memcpy(this, &o, o_size);
- memset(((uint8_t*)this) + o_size, 0, sizeof(ResTable_config) - o_size);
- }
- this->size = sizeof(*this);
- }
-
- void copyFromDtoH(const ResTable_config& o) {
- if constexpr (kDeviceEndiannessSame) {
- copyFromDeviceNoSwap(o);
- } else {
- copyFromDtoH_slow(o);
- }
- }
-
- void swapHtoD() {
- if constexpr (kDeviceEndiannessSame) {
- ; // noop
- } else {
- swapHtoD_slow();
- }
- }
+ void copyFromDeviceNoSwap(const ResTable_config& o);
+
+ void copyFromDtoH(const ResTable_config& o);
+
+ void swapHtoD();
int compare(const ResTable_config& o) const;
int compareLogical(const ResTable_config& o) const;
@@ -1416,10 +1384,6 @@ struct ResTable_config
bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
String8 toString() const;
-
- private:
- void copyFromDtoH_slow(const ResTable_config& o);
- void swapHtoD_slow();
};
/**
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index d8ca64a174a2..c9ba8a01a5e9 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -15,7 +15,6 @@
*/
#pragma once
-#include <sys/stat.h>
#include <time.h>
//
@@ -65,15 +64,10 @@ ModDate getFileModDate(const char* fileName);
/* same, but also returns -1 if the file has already been deleted */
ModDate getFileModDate(int fd);
-// Extract the modification date from the stat structure.
-ModDate getModDate(const struct ::stat& st);
-
// Check if |path| or |fd| resides on a readonly filesystem.
bool isReadonlyFilesystem(const char* path);
bool isReadonlyFilesystem(int fd);
-bool isKnownWritablePath(const char* path);
-
} // namespace android
// Whoever uses getFileModDate() will need this as well
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 26eb320805c9..32f3624a3aee 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -16,10 +16,10 @@
#define LOG_TAG "misc"
-#include "androidfw/misc.h"
-
-#include <errno.h>
-#include <sys/stat.h>
+//
+// Miscellaneous utility functions.
+//
+#include <androidfw/misc.h>
#include "android-base/logging.h"
@@ -28,7 +28,9 @@
#include <sys/vfs.h>
#endif // __linux__
-#include <array>
+#include <errno.h>
+#include <sys/stat.h>
+
#include <cstdio>
#include <cstring>
#include <tuple>
@@ -38,26 +40,28 @@ namespace android {
/*
* Get a file's type.
*/
-FileType getFileType(const char* fileName) {
- struct stat sb;
- if (stat(fileName, &sb) < 0) {
- if (errno == ENOENT || errno == ENOTDIR)
- return kFileTypeNonexistent;
- else {
- PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
- return kFileTypeUnknown;
- }
- } else {
- if (S_ISREG(sb.st_mode))
- return kFileTypeRegular;
- else if (S_ISDIR(sb.st_mode))
- return kFileTypeDirectory;
- else if (S_ISCHR(sb.st_mode))
- return kFileTypeCharDev;
- else if (S_ISBLK(sb.st_mode))
- return kFileTypeBlockDev;
- else if (S_ISFIFO(sb.st_mode))
- return kFileTypeFifo;
+FileType getFileType(const char* fileName)
+{
+ struct stat sb;
+
+ if (stat(fileName, &sb) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return kFileTypeNonexistent;
+ else {
+ PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
+ return kFileTypeUnknown;
+ }
+ } else {
+ if (S_ISREG(sb.st_mode))
+ return kFileTypeRegular;
+ else if (S_ISDIR(sb.st_mode))
+ return kFileTypeDirectory;
+ else if (S_ISCHR(sb.st_mode))
+ return kFileTypeCharDev;
+ else if (S_ISBLK(sb.st_mode))
+ return kFileTypeBlockDev;
+ else if (S_ISFIFO(sb.st_mode))
+ return kFileTypeFifo;
#if defined(S_ISLNK)
else if (S_ISLNK(sb.st_mode))
return kFileTypeSymlink;
@@ -71,7 +75,7 @@ FileType getFileType(const char* fileName) {
}
}
-ModDate getModDate(const struct stat& st) {
+static ModDate getModDate(const struct stat& st) {
#ifdef _WIN32
return st.st_mtime;
#elif defined(__APPLE__)
@@ -109,14 +113,8 @@ bool isReadonlyFilesystem(const char*) {
bool isReadonlyFilesystem(int) {
return false;
}
-bool isKnownWritablePath(const char*) {
- return false;
-}
#else // __linux__
bool isReadonlyFilesystem(const char* path) {
- if (isKnownWritablePath(path)) {
- return false;
- }
struct statfs sfs;
if (::statfs(path, &sfs)) {
PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
@@ -133,13 +131,6 @@ bool isReadonlyFilesystem(int fd) {
}
return (sfs.f_flags & ST_RDONLY) != 0;
}
-
-bool isKnownWritablePath(const char* path) {
- // We know that all paths in /data/ are writable.
- static constexpr char kRwPrefix[] = "/data/";
- return strncmp(kRwPrefix, path, std::size(kRwPrefix) - 1) == 0;
-}
-
#endif // __linux__
} // namespace android
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 22b9e69500d9..cb2e56f5f5e4 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -218,11 +218,10 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
auto apk_assets = ApkAssets::LoadOverlay(temp_file.path);
ASSERT_NE(nullptr, apk_assets);
- ASSERT_TRUE(apk_assets->IsOverlay());
- ASSERT_EQ(UpToDate::True, apk_assets->IsUpToDate());
+ ASSERT_TRUE(apk_assets->IsUpToDate());
unlink(temp_file.path);
- ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
+ ASSERT_FALSE(apk_assets->IsUpToDate());
const auto sleep_duration =
std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull));
@@ -231,27 +230,7 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
base::WriteStringToFile("hello", temp_file.path);
std::this_thread::sleep_for(sleep_duration);
- ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
-}
-
-TEST(IdmapTestUpToDate, Combine) {
- ASSERT_EQ(UpToDate::False, combine(UpToDate::False, [] {
- ADD_FAILURE(); // Shouldn't get called at all.
- return UpToDate::False;
- }));
-
- ASSERT_EQ(UpToDate::False, combine(UpToDate::True, [] { return UpToDate::False; }));
-
- ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::True; }));
- ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::Always; }));
- ASSERT_EQ(UpToDate::True, combine(UpToDate::Always, [] { return UpToDate::True; }));
-
- ASSERT_EQ(UpToDate::Always, combine(UpToDate::Always, [] { return UpToDate::Always; }));
-}
-
-TEST(IdmapTestUpToDate, FromBool) {
- ASSERT_EQ(UpToDate::False, fromBool(false));
- ASSERT_EQ(UpToDate::True, fromBool(true));
+ ASSERT_FALSE(apk_assets->IsUpToDate());
}
} // namespace
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 7b45070af312..290df997a8ed 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -36,7 +36,7 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
const SkFont& font = paint->getSkFont();
- minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection);
+ minikin::MinikinPaint minikinPaint(resolvedFace->getFontCollection());
/* Prepare minikin Paint */
minikinPaint.size =
font.isLinearMetrics() ? font.getSize() : static_cast<int>(font.getSize());
@@ -46,9 +46,9 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
minikinPaint.wordSpacing = paint->getWordSpacing();
minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
minikinPaint.localeListId = paint->getMinikinLocaleListId();
- minikinPaint.fontStyle = resolvedFace->fStyle;
+ minikinPaint.fontStyle = resolvedFace->getFontStyle();
minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
- if (!resolvedFace->fIsVariationInstance) {
+ if (!resolvedFace->isVariationInstance()) {
// This is an optimization for direct private API use typically done by System UI.
// In the public API surface, if Typeface is already configured for variation instance
// (Target SDK <= 35) the font variation settings of Paint is not set.
@@ -132,7 +132,7 @@ minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::
bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) {
const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
- return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
+ return resolvedFace->getFontCollection()->hasVariationSelector(codepoint, vs);
}
float MinikinUtils::xOffsetForTextAlign(Paint* paint, const minikin::Layout& layout) {
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 4dfe05377a48..a73aac632752 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -70,74 +70,45 @@ const Typeface* Typeface::resolveDefault(const Typeface* src) {
Typeface* Typeface::createRelative(Typeface* src, Typeface::Style style) {
const Typeface* resolvedFace = Typeface::resolveDefault(src);
- Typeface* result = new Typeface;
- if (result != nullptr) {
- result->fFontCollection = resolvedFace->fFontCollection;
- result->fBaseWeight = resolvedFace->fBaseWeight;
- result->fAPIStyle = style;
- result->fStyle = computeRelativeStyle(result->fBaseWeight, style);
- result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
- }
- return result;
+ return new Typeface(resolvedFace->getFontCollection(),
+ computeRelativeStyle(resolvedFace->getBaseWeight(), style), style,
+ resolvedFace->getBaseWeight(), resolvedFace->isVariationInstance());
}
Typeface* Typeface::createAbsolute(Typeface* base, int weight, bool italic) {
const Typeface* resolvedFace = Typeface::resolveDefault(base);
- Typeface* result = new Typeface();
- if (result != nullptr) {
- result->fFontCollection = resolvedFace->fFontCollection;
- result->fBaseWeight = resolvedFace->fBaseWeight;
- result->fAPIStyle = computeAPIStyle(weight, italic);
- result->fStyle = computeMinikinStyle(weight, italic);
- result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
- }
- return result;
+ return new Typeface(resolvedFace->getFontCollection(), computeMinikinStyle(weight, italic),
+ computeAPIStyle(weight, italic), resolvedFace->getBaseWeight(),
+ resolvedFace->isVariationInstance());
}
Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src,
const minikin::VariationSettings& variations) {
const Typeface* resolvedFace = Typeface::resolveDefault(src);
- Typeface* result = new Typeface();
- if (result != nullptr) {
- result->fFontCollection =
- resolvedFace->fFontCollection->createCollectionWithVariation(variations);
- if (result->fFontCollection == nullptr) {
+ const std::shared_ptr<minikin::FontCollection>& fc =
+ resolvedFace->getFontCollection()->createCollectionWithVariation(variations);
+ return new Typeface(
// None of passed axes are supported by this collection.
// So we will reuse the same collection with incrementing reference count.
- result->fFontCollection = resolvedFace->fFontCollection;
- }
- // Do not update styles.
- // TODO: We may want to update base weight if the 'wght' is specified.
- result->fBaseWeight = resolvedFace->fBaseWeight;
- result->fAPIStyle = resolvedFace->fAPIStyle;
- result->fStyle = resolvedFace->fStyle;
- result->fIsVariationInstance = true;
- }
- return result;
+ fc ? fc : resolvedFace->getFontCollection(),
+ // Do not update styles.
+ // TODO: We may want to update base weight if the 'wght' is specified.
+ resolvedFace->fStyle, resolvedFace->getAPIStyle(), resolvedFace->getBaseWeight(), true);
}
Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) {
const Typeface* resolvedFace = Typeface::resolveDefault(src);
- Typeface* result = new Typeface;
- if (result != nullptr) {
- result->fFontCollection = resolvedFace->fFontCollection;
- result->fBaseWeight = weight;
- result->fAPIStyle = resolvedFace->fAPIStyle;
- result->fStyle = computeRelativeStyle(weight, result->fAPIStyle);
- result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
- }
- return result;
+ return new Typeface(resolvedFace->getFontCollection(),
+ computeRelativeStyle(weight, resolvedFace->getAPIStyle()),
+ resolvedFace->getAPIStyle(), weight, resolvedFace->isVariationInstance());
}
Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
int weight, int italic, const Typeface* fallback) {
- Typeface* result = new Typeface;
- if (fallback == nullptr) {
- result->fFontCollection = minikin::FontCollection::create(std::move(families));
- } else {
- result->fFontCollection =
- fallback->fFontCollection->createCollectionWithFamilies(std::move(families));
- }
+ const std::shared_ptr<minikin::FontCollection>& fc =
+ fallback ? fallback->getFontCollection()->createCollectionWithFamilies(
+ std::move(families))
+ : minikin::FontCollection::create(std::move(families));
if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
int weightFromFont;
@@ -171,11 +142,8 @@ Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::Font
weight = SkFontStyle::kNormal_Weight;
}
- result->fBaseWeight = weight;
- result->fAPIStyle = computeAPIStyle(weight, italic);
- result->fStyle = computeMinikinStyle(weight, italic);
- result->fIsVariationInstance = false;
- return result;
+ return new Typeface(fc, computeMinikinStyle(weight, italic), computeAPIStyle(weight, italic),
+ weight, false);
}
void Typeface::setDefault(const Typeface* face) {
@@ -205,11 +173,8 @@ void Typeface::setRobotoTypefaceForTest() {
std::shared_ptr<minikin::FontCollection> collection =
minikin::FontCollection::create(minikin::FontFamily::create(std::move(fonts)));
- Typeface* hwTypeface = new Typeface();
- hwTypeface->fFontCollection = collection;
- hwTypeface->fAPIStyle = Typeface::kNormal;
- hwTypeface->fBaseWeight = SkFontStyle::kNormal_Weight;
- hwTypeface->fStyle = minikin::FontStyle();
+ Typeface* hwTypeface = new Typeface(collection, minikin::FontStyle(), Typeface::kNormal,
+ SkFontStyle::kNormal_Weight, false);
Typeface::setDefault(hwTypeface);
#endif
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 97d1bf4ef011..e8233a6bc6d8 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -32,21 +32,39 @@ constexpr int RESOLVE_BY_FONT_TABLE = -1;
struct ANDROID_API Typeface {
public:
- std::shared_ptr<minikin::FontCollection> fFontCollection;
+ enum Style : uint8_t { kNormal = 0, kBold = 0x01, kItalic = 0x02, kBoldItalic = 0x03 };
+ Typeface(const std::shared_ptr<minikin::FontCollection> fc, minikin::FontStyle style,
+ Style apiStyle, int baseWeight, bool isVariationInstance)
+ : fFontCollection(fc)
+ , fStyle(style)
+ , fAPIStyle(apiStyle)
+ , fBaseWeight(baseWeight)
+ , fIsVariationInstance(isVariationInstance) {}
+
+ const std::shared_ptr<minikin::FontCollection>& getFontCollection() const {
+ return fFontCollection;
+ }
// resolved style actually used for rendering
- minikin::FontStyle fStyle;
+ minikin::FontStyle getFontStyle() const { return fStyle; }
// style used in the API
- enum Style : uint8_t { kNormal = 0, kBold = 0x01, kItalic = 0x02, kBoldItalic = 0x03 };
- Style fAPIStyle;
+ Style getAPIStyle() const { return fAPIStyle; }
// base weight in CSS-style units, 1..1000
- int fBaseWeight;
+ int getBaseWeight() const { return fBaseWeight; }
// True if the Typeface is already created for variation settings.
- bool fIsVariationInstance;
+ bool isVariationInstance() const { return fIsVariationInstance; }
+private:
+ std::shared_ptr<minikin::FontCollection> fFontCollection;
+ minikin::FontStyle fStyle;
+ Style fAPIStyle;
+ int fBaseWeight;
+ bool fIsVariationInstance = false;
+
+public:
static const Typeface* resolveDefault(const Typeface* src);
// The following three functions create new Typeface from an existing Typeface with a different
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 8d3a5eb2b4af..f6fdec1c82bc 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -609,7 +609,8 @@ namespace PaintGlue {
SkFont* font = &paint->getSkFont();
const Typeface* typeface = paint->getAndroidTypeface();
typeface = Typeface::resolveDefault(typeface);
- minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
+ minikin::FakedFont baseFont =
+ typeface->getFontCollection()->baseFontFaked(typeface->getFontStyle());
float saveSkewX = font->getSkewX();
bool savefakeBold = font->isEmbolden();
MinikinFontSkia::populateSkFont(font, baseFont.typeface().get(), baseFont.fakery);
@@ -641,7 +642,7 @@ namespace PaintGlue {
if (useLocale) {
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
minikin::MinikinExtent extent =
- typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint);
+ typeface->getFontCollection()->getReferenceExtentForLocale(minikinPaint);
metrics->fAscent = std::min(extent.ascent, metrics->fAscent);
metrics->fDescent = std::max(extent.descent, metrics->fDescent);
metrics->fTop = std::min(metrics->fAscent, metrics->fTop);
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index c5095c1a0704..63906de80745 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -99,17 +99,17 @@ static jlong Typeface_getReleaseFunc(CRITICAL_JNI_PARAMS) {
// CriticalNative
static jint Typeface_getStyle(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- return toTypeface(faceHandle)->fAPIStyle;
+ return toTypeface(faceHandle)->getAPIStyle();
}
// CriticalNative
static jint Typeface_getWeight(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- return toTypeface(faceHandle)->fStyle.weight();
+ return toTypeface(faceHandle)->getFontStyle().weight();
}
// Critical Native
static jboolean Typeface_isVariationInstance(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- return toTypeface(faceHandle)->fIsVariationInstance;
+ return toTypeface(faceHandle)->isVariationInstance();
}
static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
@@ -128,18 +128,18 @@ static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArr
// CriticalNative
static void Typeface_setDefault(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
Typeface::setDefault(toTypeface(faceHandle));
- minikin::SystemFonts::registerDefault(toTypeface(faceHandle)->fFontCollection);
+ minikin::SystemFonts::registerDefault(toTypeface(faceHandle)->getFontCollection());
}
static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle) {
Typeface* face = toTypeface(faceHandle);
- const size_t length = face->fFontCollection->getSupportedAxesCount();
+ const size_t length = face->getFontCollection()->getSupportedAxesCount();
if (length == 0) {
return nullptr;
}
std::vector<jint> tagVec(length);
for (size_t i = 0; i < length; i++) {
- tagVec[i] = face->fFontCollection->getSupportedAxisAt(i);
+ tagVec[i] = face->getFontCollection()->getSupportedAxisAt(i);
}
std::sort(tagVec.begin(), tagVec.end());
const jintArray result = env->NewIntArray(length);
@@ -150,7 +150,7 @@ static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle)
static void Typeface_registerGenericFamily(JNIEnv *env, jobject, jstring familyName, jlong ptr) {
ScopedUtfChars familyNameChars(env, familyName);
minikin::SystemFonts::registerFallback(familyNameChars.c_str(),
- toTypeface(ptr)->fFontCollection);
+ toTypeface(ptr)->getFontCollection());
}
#ifdef __ANDROID__
@@ -315,18 +315,19 @@ static jint Typeface_writeTypefaces(JNIEnv* env, jobject, jobject buffer, jint p
std::vector<std::shared_ptr<minikin::FontCollection>> fontCollections;
std::unordered_map<std::shared_ptr<minikin::FontCollection>, size_t> fcToIndex;
for (Typeface* typeface : typefaces) {
- bool inserted = fcToIndex.emplace(typeface->fFontCollection, fontCollections.size()).second;
+ bool inserted =
+ fcToIndex.emplace(typeface->getFontCollection(), fontCollections.size()).second;
if (inserted) {
- fontCollections.push_back(typeface->fFontCollection);
+ fontCollections.push_back(typeface->getFontCollection());
}
}
minikin::FontCollection::writeVector(&writer, fontCollections);
writer.write<uint32_t>(typefaces.size());
for (Typeface* typeface : typefaces) {
- writer.write<uint32_t>(fcToIndex.find(typeface->fFontCollection)->second);
- typeface->fStyle.writeTo(&writer);
- writer.write<Typeface::Style>(typeface->fAPIStyle);
- writer.write<int>(typeface->fBaseWeight);
+ writer.write<uint32_t>(fcToIndex.find(typeface->getFontCollection())->second);
+ typeface->getFontStyle().writeTo(&writer);
+ writer.write<Typeface::Style>(typeface->getAPIStyle());
+ writer.write<int>(typeface->getBaseWeight());
}
return static_cast<jint>(writer.size());
}
@@ -349,12 +350,10 @@ static jlongArray Typeface_readTypefaces(JNIEnv* env, jobject, jobject buffer, j
std::vector<jlong> faceHandles;
faceHandles.reserve(typefaceCount);
for (uint32_t i = 0; i < typefaceCount; i++) {
- Typeface* typeface = new Typeface;
- typeface->fFontCollection = fontCollections[reader.read<uint32_t>()];
- typeface->fStyle = minikin::FontStyle(&reader);
- typeface->fAPIStyle = reader.read<Typeface::Style>();
- typeface->fBaseWeight = reader.read<int>();
- typeface->fIsVariationInstance = false;
+ Typeface* typeface =
+ new Typeface(fontCollections[reader.read<uint32_t>()], minikin::FontStyle(&reader),
+ reader.read<Typeface::Style>(), reader.read<int>(),
+ false /* isVariationInstance */);
faceHandles.push_back(toJLong(typeface));
}
const jlongArray result = env->NewLongArray(typefaceCount);
@@ -382,7 +381,8 @@ static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) {
// Critical Native
static void Typeface_addFontCollection(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- std::shared_ptr<minikin::FontCollection> collection = toTypeface(faceHandle)->fFontCollection;
+ std::shared_ptr<minikin::FontCollection> collection =
+ toTypeface(faceHandle)->getFontCollection();
minikin::SystemFonts::addFontMap(std::move(collection));
}
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index d1782b285b34..7a4ae8330de8 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -104,7 +104,7 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou
} else {
fontId = fonts.size(); // This is new to us. Create new one.
std::shared_ptr<minikin::Font> font;
- if (resolvedFace->fIsVariationInstance) {
+ if (resolvedFace->isVariationInstance()) {
// The optimization for target SDK 35 or before because the variation instance
// is already created and no runtime variation resolution happens on such
// environment.
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 93118aeafaaf..b51414fd3c02 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -183,8 +183,11 @@ SkRect TestUtils::getLocalClipBounds(const SkCanvas* canvas) {
}
SkFont TestUtils::defaultFont() {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont =
- Typeface::resolveDefault(nullptr)->fFontCollection->getFamilyAt(0)->getFont(0)->baseTypeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = Typeface::resolveDefault(nullptr)
+ ->getFontCollection()
+ ->getFamilyAt(0)
+ ->getFont(0)
+ ->baseTypeface();
SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(minikinFont.get())->GetSkTypeface();
LOG_ALWAYS_FATAL_IF(skTypeface == nullptr);
return SkFont(sk_ref_sp(skTypeface));
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index c71c4d243a8b..7bcd937397b0 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -90,40 +90,40 @@ TEST(TypefaceTest, resolveDefault_and_setDefaultTest) {
TEST(TypefaceTest, createWithDifferentBaseWeight) {
std::unique_ptr<Typeface> bold(Typeface::createWithDifferentBaseWeight(nullptr, 700));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, bold->getAPIStyle());
std::unique_ptr<Typeface> light(Typeface::createWithDifferentBaseWeight(nullptr, 300));
- EXPECT_EQ(300, light->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, light->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, light->fAPIStyle);
+ EXPECT_EQ(300, light->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, light->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, light->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_fromRegular) {
// In Java, Typeface.create(Typeface.DEFAULT, Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(nullptr, Typeface::kNormal));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(nullptr, Typeface::kBold));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, Typeface.create(Typeface.DEFAULT, Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(nullptr, Typeface::kItalic));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(Typeface::createRelative(nullptr, Typeface::kBoldItalic));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_BoldBase) {
@@ -132,31 +132,31 @@ TEST(TypefaceTest, createRelativeTest_BoldBase) {
// In Java, Typeface.create(Typeface.create("sans-serif-bold"),
// Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(700, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(700, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-bold"),
// Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(1000, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(1000, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-bold"),
// Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(700, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(700, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-bold"),
// Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(1000, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(1000, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_LightBase) {
@@ -165,31 +165,31 @@ TEST(TypefaceTest, createRelativeTest_LightBase) {
// In Java, Typeface.create(Typeface.create("sans-serif-light"),
// Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(300, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(300, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-light"),
// Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(600, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(600, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-light"),
// Typeface.ITLIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(300, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(300, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java, Typeface.create(Typeface.create("sans-serif-light"),
// Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(600, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(600, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_fromBoldStyled) {
@@ -198,32 +198,32 @@ TEST(TypefaceTest, createRelativeTest_fromBoldStyled) {
// In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
// Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
// Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
// Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
// Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_fromItalicStyled) {
@@ -233,33 +233,33 @@ TEST(TypefaceTest, createRelativeTest_fromItalicStyled) {
// Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
// Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java, Typeface.create(Typeface.create(Typeface.DEFAULT,
// Typeface.ITALIC), Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java,
// Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
// Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
// Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createRelativeTest_fromSpecifiedStyled) {
@@ -270,27 +270,27 @@ TEST(TypefaceTest, createRelativeTest_fromSpecifiedStyled) {
// .setWeight(700).setItalic(false).build();
// Typeface.create(typeface, Typeface.NORMAL);
std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
- EXPECT_EQ(400, normal->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
+ EXPECT_EQ(400, normal->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, normal->getAPIStyle());
// In Java,
// Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
// .setWeight(700).setItalic(false).build();
// Typeface.create(typeface, Typeface.BOLD);
std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java,
// Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
// .setWeight(700).setItalic(false).build();
// Typeface.create(typeface, Typeface.ITALIC);
std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
@@ -298,9 +298,9 @@ TEST(TypefaceTest, createRelativeTest_fromSpecifiedStyled) {
// Typeface.create(typeface, Typeface.BOLD_ITALIC);
std::unique_ptr<Typeface> boldItalic(
Typeface::createRelative(base.get(), Typeface::kBoldItalic));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
}
TEST(TypefaceTest, createAbsolute) {
@@ -309,45 +309,45 @@ TEST(TypefaceTest, createAbsolute) {
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(false)
// .build();
std::unique_ptr<Typeface> regular(Typeface::createAbsolute(nullptr, 400, false));
- EXPECT_EQ(400, regular->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
+ EXPECT_EQ(400, regular->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, regular->getAPIStyle());
// In Java,
// new
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(false)
// .build();
std::unique_ptr<Typeface> bold(Typeface::createAbsolute(nullptr, 700, false));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java,
// new
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(true)
// .build();
std::unique_ptr<Typeface> italic(Typeface::createAbsolute(nullptr, 400, true));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// new
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(true)
// .build();
std::unique_ptr<Typeface> boldItalic(Typeface::createAbsolute(nullptr, 700, true));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBoldItalic, boldItalic->getAPIStyle());
// In Java,
// new
// Typeface.Builder(invalid).setFallback("sans-serif").setWeight(1100).setItalic(true)
// .build();
std::unique_ptr<Typeface> over1000(Typeface::createAbsolute(nullptr, 1100, false));
- EXPECT_EQ(1000, over1000->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
+ EXPECT_EQ(1000, over1000->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, over1000->getAPIStyle());
}
TEST(TypefaceTest, createFromFamilies_Single) {
@@ -355,43 +355,43 @@ TEST(TypefaceTest, createFromFamilies_Single) {
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 400, false, nullptr /* fallback */));
- EXPECT_EQ(400, regular->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
+ EXPECT_EQ(400, regular->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, regular->getAPIStyle());
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build();
std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 700, false, nullptr /* fallback */));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build();
std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 400, true, nullptr /* fallback */));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build();
std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 700, true, nullptr /* fallback */));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build();
std::unique_ptr<Typeface> over1000(Typeface::createFromFamilies(
makeSingleFamlyVector(kRobotoVariable), 1100, false, nullptr /* fallback */));
- EXPECT_EQ(1000, over1000->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
+ EXPECT_EQ(1000, over1000->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, over1000->getAPIStyle());
}
TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
@@ -399,33 +399,33 @@ TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
std::unique_ptr<Typeface> regular(
Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(400, regular->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
- EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
+ EXPECT_EQ(400, regular->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kNormal, regular->getAPIStyle());
// In Java, new Typeface.Builder("Family-Bold.ttf").build();
std::unique_ptr<Typeface> bold(
Typeface::createFromFamilies(makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(700, bold->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
- EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
+ EXPECT_EQ(700, bold->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kBold, bold->getAPIStyle());
// In Java, new Typeface.Builder("Family-Italic.ttf").build();
std::unique_ptr<Typeface> italic(
Typeface::createFromFamilies(makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(400, italic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(400, italic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
// In Java, new Typeface.Builder("Family-BoldItalic.ttf").build();
std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
makeSingleFamlyVector(kBoldItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
nullptr /* fallback */));
- EXPECT_EQ(700, boldItalic->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
- EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
+ EXPECT_EQ(700, boldItalic->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->getFontStyle().slant());
+ EXPECT_EQ(Typeface::kItalic, italic->getAPIStyle());
}
TEST(TypefaceTest, createFromFamilies_Family) {
@@ -435,8 +435,8 @@ TEST(TypefaceTest, createFromFamilies_Family) {
std::unique_ptr<Typeface> typeface(
Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(400, typeface->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
+ EXPECT_EQ(400, typeface->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->getFontStyle().slant());
}
TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
@@ -445,8 +445,8 @@ TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
std::unique_ptr<Typeface> typeface(
Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
- EXPECT_EQ(700, typeface->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
+ EXPECT_EQ(700, typeface->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->getFontStyle().slant());
}
TEST(TypefaceTest, createFromFamilies_Family_withFallback) {
@@ -458,8 +458,8 @@ TEST(TypefaceTest, createFromFamilies_Family_withFallback) {
std::unique_ptr<Typeface> regular(
Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
RESOLVE_BY_FONT_TABLE, fallback.get()));
- EXPECT_EQ(400, regular->fStyle.weight());
- EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
+ EXPECT_EQ(400, regular->getFontStyle().weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->getFontStyle().slant());
}
} // namespace
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 4398b261377b..c48b5f4e4aea 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -11,6 +11,16 @@ flag {
}
flag {
+ name: "disable_set_bluetooth_ad2p_on_calls"
+ namespace: "media_better_together"
+ description: "Prevents calls to AudioService.setBluetoothA2dpOn(), known to cause incorrect audio routing to the built-in speakers."
+ bug: "294968421"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_audio_input_device_routing_and_volume_control"
namespace: "media_better_together"
description: "Allows audio input devices routing and volume control via system settings."
diff --git a/mime/Android.bp b/mime/Android.bp
index 20110f1dfb47..b609548fcbab 100644
--- a/mime/Android.bp
+++ b/mime/Android.bp
@@ -49,6 +49,17 @@ java_library {
],
}
+java_library {
+ name: "mimemap-testing-alt",
+ defaults: ["mimemap-defaults"],
+ static_libs: ["mimemap-testing-alt-res.jar"],
+ jarjar_rules: "jarjar-rules-alt.txt",
+ visibility: [
+ "//cts/tests/tests/mimemap:__subpackages__",
+ "//frameworks/base:__subpackages__",
+ ],
+}
+
// The mimemap-res.jar and mimemap-testing-res.jar genrules produce a .jar that
// has the resource file in a subdirectory res/ and testres/, respectively.
// They need to be in different paths because one of them ends up in a
@@ -86,6 +97,19 @@ java_genrule {
cmd: "mkdir $(genDir)/testres/ && cp $(in) $(genDir)/testres/ && $(location soong_zip) -C $(genDir) -o $(out) -D $(genDir)/testres/",
}
+// The same as mimemap-testing-res.jar except that the resources are placed in a different directory.
+// They get bundled with CTS so that CTS can compare a device's MimeMap implementation vs.
+// the stock Android one from when CTS was built.
+java_genrule {
+ name: "mimemap-testing-alt-res.jar",
+ tools: [
+ "soong_zip",
+ ],
+ srcs: [":mime.types.minimized-alt"],
+ out: ["mimemap-testing-alt-res.jar"],
+ cmd: "mkdir $(genDir)/testres-alt/ && cp $(in) $(genDir)/testres-alt/ && $(location soong_zip) -C $(genDir) -o $(out) -D $(genDir)/testres-alt/",
+}
+
// Combination of all *mime.types.minimized resources.
filegroup {
name: "mime.types.minimized",
@@ -99,6 +123,19 @@ filegroup {
],
}
+// Combination of all *mime.types.minimized resources.
+filegroup {
+ name: "mime.types.minimized-alt",
+ visibility: [
+ "//visibility:private",
+ ],
+ device_common_srcs: [
+ ":debian.mime.types.minimized-alt",
+ ":android.mime.types.minimized",
+ ":vendor.mime.types.minimized",
+ ],
+}
+
java_genrule {
name: "android.mime.types.minimized",
visibility: [
diff --git a/mime/jarjar-rules-alt.txt b/mime/jarjar-rules-alt.txt
new file mode 100644
index 000000000000..9a7644325336
--- /dev/null
+++ b/mime/jarjar-rules-alt.txt
@@ -0,0 +1 @@
+rule android.content.type.DefaultMimeMapFactory android.content.type.cts.StockAndroidAltMimeMapFactory
diff --git a/mime/jarjar-rules.txt b/mime/jarjar-rules.txt
index 145d1dbf3d11..e1ea8e10314c 100644
--- a/mime/jarjar-rules.txt
+++ b/mime/jarjar-rules.txt
@@ -1 +1 @@
-rule android.content.type.DefaultMimeMapFactory android.content.type.cts.StockAndroidMimeMapFactory \ No newline at end of file
+rule android.content.type.DefaultMimeMapFactory android.content.type.cts.StockAndroidMimeMapFactory
diff --git a/native/android/tests/system_health/OWNERS b/native/android/tests/system_health/OWNERS
new file mode 100644
index 000000000000..e3bbee92057d
--- /dev/null
+++ b/native/android/tests/system_health/OWNERS
@@ -0,0 +1 @@
+include /ADPF_OWNERS
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt
index 2fac54557bef..6fc6b5405eb2 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt
@@ -22,6 +22,7 @@ import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.ipc.ApiDescriptor
import com.android.settingslib.ipc.ApiHandler
import com.android.settingslib.ipc.ApiPermissionChecker
+import com.android.settingslib.metadata.PreferenceCoordinate
import com.android.settingslib.metadata.PreferenceHierarchyNode
import com.android.settingslib.metadata.PreferenceScreenRegistry
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt
index ff14eb5aae55..70ce62c8383c 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt
@@ -20,6 +20,7 @@ import android.os.Bundle
import android.os.Parcel
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.ipc.MessageCodec
+import com.android.settingslib.metadata.PreferenceCoordinate
import java.util.Arrays
/** Message codec for [PreferenceGetterRequest]. */
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt
index 68aa2d258295..2dd736ae6083 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.graph
+package com.android.settingslib.metadata
import android.os.Parcel
import android.os.Parcelable
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index b2c279466ee4..e05f0a1bcde0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -483,14 +483,18 @@ public class HearingAidDeviceManager {
void onActiveDeviceChanged(CachedBluetoothDevice device) {
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING)) {
- if (device.isConnectedHearingAidDevice()) {
+ if (device.isConnectedHearingAidDevice()
+ && (device.isActiveDevice(BluetoothProfile.HEARING_AID)
+ || device.isActiveDevice(BluetoothProfile.LE_AUDIO))) {
setAudioRoutingConfig(device);
} else {
clearAudioRoutingConfig();
}
}
if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
- if (device.isConnectedHearingAidDevice()) {
+ if (device.isConnectedHearingAidDevice()
+ && (device.isActiveDevice(BluetoothProfile.HEARING_AID)
+ || device.isActiveDevice(BluetoothProfile.LE_AUDIO))) {
setMicrophoneForCalls(device);
} else {
clearMicrophoneForCalls();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 21dde1fd9411..a215464f66c2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -50,6 +50,9 @@ import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import android.os.Parcel;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.FeatureFlagUtils;
import androidx.test.core.app.ApplicationProvider;
@@ -72,6 +75,8 @@ import java.util.List;
public class HearingAidDeviceManagerTest {
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final long HISYNCID1 = 10;
private static final long HISYNCID2 = 11;
@@ -736,6 +741,7 @@ public class HearingAidDeviceManagerTest {
@Test
public void onActiveDeviceChanged_connected_callSetStrategies() {
+ when(mCachedDevice1.isConnectedHearingAidDevice()).thenReturn(true);
when(mHelper.getMatchedHearingDeviceAttributesForOutput(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
@@ -750,6 +756,7 @@ public class HearingAidDeviceManagerTest {
@Test
public void onActiveDeviceChanged_disconnected_callSetStrategiesWithAutoValue() {
+ when(mCachedDevice1.isConnectedHearingAidDevice()).thenReturn(false);
when(mHelper.getMatchedHearingDeviceAttributesForOutput(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
@@ -952,6 +959,38 @@ public class HearingAidDeviceManagerTest {
ConnectionStatus.CONNECTED);
}
+ @Test
+ @RequiresFlagsEnabled(
+ com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_INPUT_ROUTING_CONTROL)
+ public void onActiveDeviceChanged_activeHearingAidProfile_callSetInputDeviceForCalls() {
+ when(mCachedDevice1.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
+ when(mDevice1.isMicrophonePreferredForCalls()).thenReturn(true);
+ doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(), any(),
+ anyInt());
+
+ mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
+
+ verify(mHelper).setPreferredInputDeviceForCalls(
+ eq(mCachedDevice1), eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
+
+ }
+
+ @Test
+ @RequiresFlagsEnabled(
+ com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_INPUT_ROUTING_CONTROL)
+ public void onActiveDeviceChanged_notActiveHearingAidProfile_callClearInputDeviceForCalls() {
+ when(mCachedDevice1.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
+ when(mDevice1.isMicrophonePreferredForCalls()).thenReturn(true);
+ doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(), any(),
+ anyInt());
+
+ mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
+
+ verify(mHelper).clearPreferredInputDeviceForCalls();
+ }
+
private HearingAidInfo getLeftAshaHearingAidInfo(long hiSyncId) {
return new HearingAidInfo.Builder()
.setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index c7d6e8aed3b4..96401ce6e1c7 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -147,21 +147,66 @@ private data class NestedDraggableElement(
private val orientation: Orientation,
private val overscrollEffect: OverscrollEffect?,
private val enabled: Boolean,
-) : ModifierNodeElement<NestedDraggableNode>() {
- override fun create(): NestedDraggableNode {
- return NestedDraggableNode(draggable, orientation, overscrollEffect, enabled)
+) : ModifierNodeElement<NestedDraggableRootNode>() {
+ override fun create(): NestedDraggableRootNode {
+ return NestedDraggableRootNode(draggable, orientation, overscrollEffect, enabled)
}
- override fun update(node: NestedDraggableNode) {
+ override fun update(node: NestedDraggableRootNode) {
node.update(draggable, orientation, overscrollEffect, enabled)
}
}
+/**
+ * A root node on top of [NestedDraggableNode] so that no [PointerInputModifierNode] is installed
+ * when this draggable is disabled.
+ */
+private class NestedDraggableRootNode(
+ draggable: NestedDraggable,
+ orientation: Orientation,
+ overscrollEffect: OverscrollEffect?,
+ enabled: Boolean,
+) : DelegatingNode() {
+ private var delegateNode =
+ if (enabled) create(draggable, orientation, overscrollEffect) else null
+
+ fun update(
+ draggable: NestedDraggable,
+ orientation: Orientation,
+ overscrollEffect: OverscrollEffect?,
+ enabled: Boolean,
+ ) {
+ // Disabled.
+ if (!enabled) {
+ delegateNode?.let { undelegate(it) }
+ delegateNode = null
+ return
+ }
+
+ // Disabled => Enabled.
+ val nullableDelegate = delegateNode
+ if (nullableDelegate == null) {
+ delegateNode = create(draggable, orientation, overscrollEffect)
+ return
+ }
+
+ // Enabled => Enabled (update).
+ nullableDelegate.update(draggable, orientation, overscrollEffect)
+ }
+
+ private fun create(
+ draggable: NestedDraggable,
+ orientation: Orientation,
+ overscrollEffect: OverscrollEffect?,
+ ): NestedDraggableNode {
+ return delegate(NestedDraggableNode(draggable, orientation, overscrollEffect))
+ }
+}
+
private class NestedDraggableNode(
private var draggable: NestedDraggable,
override var orientation: Orientation,
private var overscrollEffect: OverscrollEffect?,
- private var enabled: Boolean,
) :
DelegatingNode(),
PointerInputModifierNode,
@@ -169,23 +214,11 @@ private class NestedDraggableNode(
CompositionLocalConsumerModifierNode,
OrientationAware {
private val nestedScrollDispatcher = NestedScrollDispatcher()
- private var trackWheelScroll: SuspendingPointerInputModifierNode? = null
- set(value) {
- field?.let { undelegate(it) }
- field = value?.also { delegate(it) }
- }
-
- private var trackDownPositionDelegate: SuspendingPointerInputModifierNode? = null
- set(value) {
- field?.let { undelegate(it) }
- field = value?.also { delegate(it) }
- }
-
- private var detectDragsDelegate: SuspendingPointerInputModifierNode? = null
- set(value) {
- field?.let { undelegate(it) }
- field = value?.also { delegate(it) }
- }
+ private val trackWheelScroll =
+ delegate(SuspendingPointerInputModifierNode { trackWheelScroll() })
+ private val trackDownPositionDelegate =
+ delegate(SuspendingPointerInputModifierNode { trackDownPosition() })
+ private val detectDragsDelegate = delegate(SuspendingPointerInputModifierNode { detectDrags() })
/** The controller created by the nested scroll logic (and *not* the drag logic). */
private var nestedScrollController: NestedScrollController? = null
@@ -214,26 +247,25 @@ private class NestedDraggableNode(
draggable: NestedDraggable,
orientation: Orientation,
overscrollEffect: OverscrollEffect?,
- enabled: Boolean,
) {
+ if (
+ draggable == this.draggable &&
+ orientation == this.orientation &&
+ overscrollEffect == this.overscrollEffect
+ ) {
+ return
+ }
+
this.draggable = draggable
this.orientation = orientation
this.overscrollEffect = overscrollEffect
- this.enabled = enabled
- trackDownPositionDelegate?.resetPointerInputHandler()
- detectDragsDelegate?.resetPointerInputHandler()
+ trackWheelScroll.resetPointerInputHandler()
+ trackDownPositionDelegate.resetPointerInputHandler()
+ detectDragsDelegate.resetPointerInputHandler()
+
nestedScrollController?.ensureOnDragStoppedIsCalled()
nestedScrollController = null
-
- if (!enabled && trackWheelScroll != null) {
- check(trackDownPositionDelegate != null)
- check(detectDragsDelegate != null)
-
- trackWheelScroll = null
- trackDownPositionDelegate = null
- detectDragsDelegate = null
- }
}
override fun onPointerEvent(
@@ -241,26 +273,15 @@ private class NestedDraggableNode(
pass: PointerEventPass,
bounds: IntSize,
) {
- if (!enabled) return
-
- if (trackWheelScroll == null) {
- check(trackDownPositionDelegate == null)
- check(detectDragsDelegate == null)
-
- trackWheelScroll = SuspendingPointerInputModifierNode { trackWheelScroll() }
- trackDownPositionDelegate = SuspendingPointerInputModifierNode { trackDownPosition() }
- detectDragsDelegate = SuspendingPointerInputModifierNode { detectDrags() }
- }
-
- checkNotNull(trackWheelScroll).onPointerEvent(pointerEvent, pass, bounds)
- checkNotNull(trackDownPositionDelegate).onPointerEvent(pointerEvent, pass, bounds)
- checkNotNull(detectDragsDelegate).onPointerEvent(pointerEvent, pass, bounds)
+ trackWheelScroll.onPointerEvent(pointerEvent, pass, bounds)
+ trackDownPositionDelegate.onPointerEvent(pointerEvent, pass, bounds)
+ detectDragsDelegate.onPointerEvent(pointerEvent, pass, bounds)
}
override fun onCancelPointerInput() {
- trackWheelScroll?.onCancelPointerInput()
- trackDownPositionDelegate?.onCancelPointerInput()
- detectDragsDelegate?.onCancelPointerInput()
+ trackWheelScroll.onCancelPointerInput()
+ trackDownPositionDelegate.onCancelPointerInput()
+ detectDragsDelegate.onCancelPointerInput()
}
/*
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index f9cf495d9d9f..5de0f1221f0f 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -25,11 +25,14 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -37,10 +40,14 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ScrollWheel
+import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performMouseInput
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeDown
@@ -693,6 +700,7 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
}
@Test
+ @Ignore("b/388507816: re-enable this when the crash in HitPath is fixed")
fun pointersDown_clearedWhenDisabled() {
val draggable = TestDraggable()
var enabled by mutableStateOf(true)
@@ -740,6 +748,31 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
assertThat(draggable.onDragStartedCalled).isFalse()
}
+ @Test
+ fun doesNotConsumeGesturesWhenDisabled() {
+ val buttonTag = "button"
+ rule.setContent {
+ Box {
+ var count by remember { mutableStateOf(0) }
+ Button(onClick = { count++ }, Modifier.testTag(buttonTag).align(Alignment.Center)) {
+ Text("Count: $count")
+ }
+
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(remember { TestDraggable() }, orientation, enabled = false)
+ )
+ }
+ }
+
+ rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 0")
+
+ // Click on the root at its center, where the button is located. Clicks should go through
+ // the draggable and reach the button given that it is disabled.
+ repeat(3) { rule.onRoot().performClick() }
+ rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 3")
+ }
+
private fun ComposeContentTestRule.setContentWithTouchSlop(
content: @Composable () -> Unit
): Float {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index b41c55858c75..2ca846424d93 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -429,6 +429,58 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol
}
/**
+ * Finds the best matching [UserActionResult] for the given [swipe] within this [Content].
+ * Prioritizes actions with matching [Swipe.Resolved.fromSource].
+ *
+ * @param swipe The swipe to match against.
+ * @return The best matching [UserActionResult], or `null` if no match is found.
+ */
+ private fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? {
+ if (!areSwipesAllowed()) {
+ return null
+ }
+
+ var bestPoints = Int.MIN_VALUE
+ var bestMatch: UserActionResult? = null
+ userActions.forEach { (actionSwipe, actionResult) ->
+ if (
+ actionSwipe !is Swipe.Resolved ||
+ // The direction must match.
+ actionSwipe.direction != swipe.direction ||
+ // The number of pointers down must match.
+ actionSwipe.pointerCount != swipe.pointerCount ||
+ // The action requires a specific fromSource.
+ (actionSwipe.fromSource != null &&
+ actionSwipe.fromSource != swipe.fromSource) ||
+ // The action requires a specific pointerType.
+ (actionSwipe.pointersType != null &&
+ actionSwipe.pointersType != swipe.pointersType)
+ ) {
+ // This action is not eligible.
+ return@forEach
+ }
+
+ val sameFromSource = actionSwipe.fromSource == swipe.fromSource
+ val samePointerType = actionSwipe.pointersType == swipe.pointersType
+ // Prioritize actions with a perfect match.
+ if (sameFromSource && samePointerType) {
+ return actionResult
+ }
+
+ var points = 0
+ if (sameFromSource) points++
+ if (samePointerType) points++
+
+ // Otherwise, keep track of the best eligible action.
+ if (points > bestPoints) {
+ bestPoints = points
+ bestMatch = actionResult
+ }
+ }
+ return bestMatch
+ }
+
+ /**
* Update the swipes results.
*
* Usually we don't want to update them while doing a drag, because this could change the target
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 3f6bce724b1b..e2212113404d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -37,11 +37,7 @@ internal fun Modifier.swipeToScene(
draggableHandler: DraggableHandlerImpl,
swipeDetector: SwipeDetector,
): Modifier {
- return if (draggableHandler.enabled()) {
- this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
- } else {
- this
- }
+ return then(SwipeToSceneElement(draggableHandler, swipeDetector, draggableHandler.enabled()))
}
private fun DraggableHandlerImpl.enabled(): Boolean {
@@ -61,84 +57,62 @@ internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation }
}
-/**
- * Finds the best matching [UserActionResult] for the given [swipe] within this [Content].
- * Prioritizes actions with matching [Swipe.Resolved.fromSource].
- *
- * @param swipe The swipe to match against.
- * @return The best matching [UserActionResult], or `null` if no match is found.
- */
-internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? {
- if (!areSwipesAllowed()) {
- return null
- }
-
- var bestPoints = Int.MIN_VALUE
- var bestMatch: UserActionResult? = null
- userActions.forEach { (actionSwipe, actionResult) ->
- if (
- actionSwipe !is Swipe.Resolved ||
- // The direction must match.
- actionSwipe.direction != swipe.direction ||
- // The number of pointers down must match.
- actionSwipe.pointerCount != swipe.pointerCount ||
- // The action requires a specific fromSource.
- (actionSwipe.fromSource != null && actionSwipe.fromSource != swipe.fromSource) ||
- // The action requires a specific pointerType.
- (actionSwipe.pointersType != null && actionSwipe.pointersType != swipe.pointersType)
- ) {
- // This action is not eligible.
- return@forEach
- }
-
- val sameFromSource = actionSwipe.fromSource == swipe.fromSource
- val samePointerType = actionSwipe.pointersType == swipe.pointersType
- // Prioritize actions with a perfect match.
- if (sameFromSource && samePointerType) {
- return actionResult
- }
-
- var points = 0
- if (sameFromSource) points++
- if (samePointerType) points++
-
- // Otherwise, keep track of the best eligible action.
- if (points > bestPoints) {
- bestPoints = points
- bestMatch = actionResult
- }
- }
- return bestMatch
-}
-
private data class SwipeToSceneElement(
val draggableHandler: DraggableHandlerImpl,
val swipeDetector: SwipeDetector,
+ val enabled: Boolean,
) : ModifierNodeElement<SwipeToSceneRootNode>() {
override fun create(): SwipeToSceneRootNode =
- SwipeToSceneRootNode(draggableHandler, swipeDetector)
+ SwipeToSceneRootNode(draggableHandler, swipeDetector, enabled)
override fun update(node: SwipeToSceneRootNode) {
- node.update(draggableHandler, swipeDetector)
+ node.update(draggableHandler, swipeDetector, enabled)
}
}
private class SwipeToSceneRootNode(
draggableHandler: DraggableHandlerImpl,
swipeDetector: SwipeDetector,
+ enabled: Boolean,
) : DelegatingNode() {
- private var delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+ private var delegateNode = if (enabled) create(draggableHandler, swipeDetector) else null
+
+ fun update(
+ draggableHandler: DraggableHandlerImpl,
+ swipeDetector: SwipeDetector,
+ enabled: Boolean,
+ ) {
+ // Disabled.
+ if (!enabled) {
+ delegateNode?.let { undelegate(it) }
+ delegateNode = null
+ return
+ }
+
+ // Disabled => Enabled.
+ val nullableDelegate = delegateNode
+ if (nullableDelegate == null) {
+ delegateNode = create(draggableHandler, swipeDetector)
+ return
+ }
- fun update(draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector) {
- if (draggableHandler == delegateNode.draggableHandler) {
+ // Enabled => Enabled (update).
+ if (draggableHandler == nullableDelegate.draggableHandler) {
// Simple update, just update the swipe detector directly and keep the node.
- delegateNode.swipeDetector = swipeDetector
+ nullableDelegate.swipeDetector = swipeDetector
} else {
// The draggableHandler changed, force recreate the underlying SwipeToSceneNode.
- undelegate(delegateNode)
- delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+ undelegate(nullableDelegate)
+ delegateNode = create(draggableHandler, swipeDetector)
}
}
+
+ private fun create(
+ draggableHandler: DraggableHandlerImpl,
+ swipeDetector: SwipeDetector,
+ ): SwipeToSceneNode {
+ return delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+ }
}
private class SwipeToSceneNode(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 9135fdd15b3a..e80805a4e374 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -936,4 +936,45 @@ class SwipeToSceneTest {
assertThat(state.transitionState).isIdle()
assertThat(state.transitionState).hasCurrentScene(SceneC)
}
+
+ @Test
+ fun swipeToSceneNodeIsKeptWhenDisabled() {
+ var hasHorizontalActions by mutableStateOf(false)
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state) {
+ scene(
+ SceneA,
+ userActions =
+ buildList {
+ add(Swipe.Down to SceneB)
+
+ if (hasHorizontalActions) {
+ add(Swipe.Left to SceneC)
+ }
+ }
+ .toMap(),
+ ) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ // Swipe down to start a transition to B.
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop))
+ }
+
+ assertThat(state.transitionState).isSceneTransition()
+
+ // Add new horizontal user actions. This should not stop the current transition, even if a
+ // new horizontal Modifier.swipeToScene() handler is introduced where the vertical one was.
+ hasHorizontalActions = true
+ rule.waitForIdle()
+ assertThat(state.transitionState).isSceneTransition()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
index a11dace0505c..4c329dcf2f2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -18,12 +18,20 @@ package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.content.ContentResolver
+import android.content.applicationContext
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothEventManager
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.settingslib.bluetooth.VolumeControlProfile
+import com.android.settingslib.volume.shared.AudioSharingLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -38,10 +46,16 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -50,8 +64,11 @@ import org.mockito.kotlin.any
class AudioSharingInteractorTest : SysuiTestCase() {
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos()
+
@Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
+
@Mock private lateinit var bluetoothLeBroadcastMetadata: BluetoothLeBroadcastMetadata
+
@Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
private lateinit var underTest: AudioSharingInteractor
@@ -157,13 +174,15 @@ class AudioSharingInteractorTest : SysuiTestCase() {
fun testHandleAudioSourceWhenReady_hasProfileButAudioSharingOff_sourceNotAdded() =
with(kosmos) {
testScope.runTest {
- bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
localBluetoothLeBroadcast
)
val job = launch { underTest.handleAudioSourceWhenReady() }
runCurrent()
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
+ runCurrent()
assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse()
job.cancel()
@@ -174,15 +193,14 @@ class AudioSharingInteractorTest : SysuiTestCase() {
fun testHandleAudioSourceWhenReady_audioSharingOnButNoPlayback_sourceNotAdded() =
with(kosmos) {
testScope.runTest {
- bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
localBluetoothLeBroadcast
)
val job = launch { underTest.handleAudioSourceWhenReady() }
runCurrent()
- verify(localBluetoothLeBroadcast)
- .registerServiceCallBack(any(), callbackCaptor.capture())
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
runCurrent()
assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse()
@@ -194,13 +212,15 @@ class AudioSharingInteractorTest : SysuiTestCase() {
fun testHandleAudioSourceWhenReady_audioSharingOnAndPlaybackStarts_sourceAdded() =
with(kosmos) {
testScope.runTest {
- bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
localBluetoothLeBroadcast
)
val job = launch { underTest.handleAudioSourceWhenReady() }
runCurrent()
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ runCurrent()
verify(localBluetoothLeBroadcast)
.registerServiceCallBack(any(), callbackCaptor.capture())
runCurrent()
@@ -211,4 +231,100 @@ class AudioSharingInteractorTest : SysuiTestCase() {
job.cancel()
}
}
+
+ @Test
+ fun testHandleAudioSourceWhenReady_skipInitialValue_noAudioSharing_sourceNotAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ val (broadcast, repository) = setupRepositoryImpl()
+ val interactor =
+ object :
+ AudioSharingInteractorImpl(
+ applicationContext,
+ localBluetoothManager,
+ repository,
+ testDispatcher,
+ ) {
+ override suspend fun audioSharingAvailable() = true
+ }
+ val job = launch { interactor.handleAudioSourceWhenReady() }
+ runCurrent()
+ // Verify callback registered for onBroadcastStartedOrStopped
+ verify(broadcast).registerServiceCallBack(any(), callbackCaptor.capture())
+ runCurrent()
+ // Verify source is not added
+ verify(repository, never()).addSource()
+ job.cancel()
+ }
+ }
+
+ @Test
+ fun testHandleAudioSourceWhenReady_skipInitialValue_newAudioSharing_sourceAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ val (broadcast, repository) = setupRepositoryImpl()
+ val interactor =
+ object :
+ AudioSharingInteractorImpl(
+ applicationContext,
+ localBluetoothManager,
+ repository,
+ testDispatcher,
+ ) {
+ override suspend fun audioSharingAvailable() = true
+ }
+ val job = launch { interactor.handleAudioSourceWhenReady() }
+ runCurrent()
+ // Verify callback registered for onBroadcastStartedOrStopped
+ verify(broadcast).registerServiceCallBack(any(), callbackCaptor.capture())
+ // Audio sharing started, trigger onBroadcastStarted
+ whenever(broadcast.isEnabled(null)).thenReturn(true)
+ callbackCaptor.value.onBroadcastStarted(0, 0)
+ runCurrent()
+ // Verify callback registered for onBroadcastMetadataChanged
+ verify(broadcast, times(2)).registerServiceCallBack(any(), callbackCaptor.capture())
+ runCurrent()
+ // Trigger onBroadcastMetadataChanged (ready to add source)
+ callbackCaptor.value.onBroadcastMetadataChanged(0, bluetoothLeBroadcastMetadata)
+ runCurrent()
+ // Verify source added
+ verify(repository).addSource()
+ job.cancel()
+ }
+ }
+
+ private fun setupRepositoryImpl(): Pair<LocalBluetoothLeBroadcast, AudioSharingRepositoryImpl> {
+ with(kosmos) {
+ val broadcast =
+ mock<LocalBluetoothLeBroadcast> {
+ on { isProfileReady } doReturn true
+ on { isEnabled(null) } doReturn false
+ }
+ val assistant =
+ mock<LocalBluetoothLeBroadcastAssistant> { on { isProfileReady } doReturn true }
+ val volumeControl = mock<VolumeControlProfile> { on { isProfileReady } doReturn true }
+ val profileManager =
+ mock<LocalBluetoothProfileManager> {
+ on { leAudioBroadcastProfile } doReturn broadcast
+ on { leAudioBroadcastAssistantProfile } doReturn assistant
+ on { volumeControlProfile } doReturn volumeControl
+ }
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(localBluetoothManager.eventManager).thenReturn(mock<BluetoothEventManager> {})
+
+ val repository =
+ AudioSharingRepositoryImpl(
+ localBluetoothManager,
+ com.android.settingslib.volume.data.repository.AudioSharingRepositoryImpl(
+ mock<ContentResolver> {},
+ localBluetoothManager,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ mock<AudioSharingLogger> {},
+ ),
+ testDispatcher,
+ )
+ return Pair(broadcast, spy(repository))
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
index acfe9dd45f75..f0746064f67f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
@@ -111,6 +111,28 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
}
@Test
+ fun testStopAudioSharing() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
+ audioSharingRepository.setAudioSharingAvailable(true)
+ underTest.stopAudioSharing()
+ verify(leAudioBroadcastProfile).stopLatestBroadcast()
+ }
+ }
+
+ @Test
+ fun testStopAudioSharing_flagOff_doNothing() =
+ with(kosmos) {
+ testScope.runTest {
+ audioSharingRepository.setAudioSharingAvailable(false)
+ underTest.stopAudioSharing()
+ verify(leAudioBroadcastProfile, never()).stopLatestBroadcast()
+ }
+ }
+
+ @Test
fun testAddSource_flagOff_doesNothing() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 44f9720cb9e4..ad0337e5ce86 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -15,14 +15,15 @@
*/
package com.android.systemui.bluetooth.qsdialog
-import androidx.test.ext.junit.runners.AndroidJUnit4
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -48,6 +49,7 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
private lateinit var notConnectedDeviceItem: DeviceItem
private lateinit var connectedMediaDeviceItem: DeviceItem
private lateinit var connectedOtherDeviceItem: DeviceItem
+ private lateinit var audioSharingDeviceItem: DeviceItem
@Mock private lateinit var dialog: SystemUIDialog
@Before
@@ -59,7 +61,7 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
- background = null
+ background = null,
)
notConnectedDeviceItem =
DeviceItem(
@@ -68,7 +70,7 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
- background = null
+ background = null,
)
connectedMediaDeviceItem =
DeviceItem(
@@ -77,7 +79,7 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
- background = null
+ background = null,
)
connectedOtherDeviceItem =
DeviceItem(
@@ -86,7 +88,16 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
- background = null
+ background = null,
+ )
+ audioSharingDeviceItem =
+ DeviceItem(
+ type = DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
+ deviceName = DEVICE_NAME,
+ connectionSummary = DEVICE_CONNECTION_SUMMARY,
+ iconWithDescription = null,
+ background = null,
)
actionInteractorImpl = kosmos.deviceItemActionInteractorImpl
}
@@ -135,6 +146,29 @@ class DeviceItemActionInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun onActionIconClick_onIntent() {
+ with(kosmos) {
+ testScope.runTest {
+ var onIntentCalledOnAddress = ""
+ whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
+ actionInteractorImpl.onActionIconClick(connectedMediaDeviceItem) {
+ onIntentCalledOnAddress = connectedMediaDeviceItem.cachedBluetoothDevice.address
+ }
+ assertThat(onIntentCalledOnAddress).isEqualTo(DEVICE_ADDRESS)
+ }
+ }
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun onActionIconClick_audioSharingDeviceType_throwException() {
+ with(kosmos) {
+ testScope.runTest {
+ actionInteractorImpl.onActionIconClick(audioSharingDeviceItem) {}
+ }
+ }
+ }
+
private companion object {
const val DEVICE_NAME = "device"
const val DEVICE_CONNECTION_SUMMARY = "active"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
index eeccbdf20540..79556baed067 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.qs.tiles
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
import android.service.quicksettings.Tile
@@ -24,18 +26,26 @@ import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
+import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.flags.setFlagValue
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
@@ -256,6 +266,41 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() {
verify(wifiStateWorker, times(1)).isWifiEnabled = eq(true)
}
+ @Test
+ @DisableFlags(QsDetailedView.FLAG_NAME)
+ fun click_withQsDetailedViewDisabled() {
+ underTest.click(null)
+ looper.processAllMessages()
+
+ verify(dialogManager, times(1)).create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ null,
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ value = [
+ QsDetailedView.FLAG_NAME,
+ FLAG_SCENE_CONTAINER,
+ KeyguardWmStateRefactor.FLAG_NAME,
+ NotificationThrottleHun.FLAG_NAME,
+ DualShade.FLAG_NAME]
+ )
+ fun click_withQsDetailedViewEnabled() {
+ underTest.click(null)
+ looper.processAllMessages()
+
+ verify(dialogManager, times(0)).create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ null,
+ )
+ }
+
companion object {
const val WIFI_SSID = "test ssid"
val ACTIVE_WIFI =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 20474c842b51..deaf57999b21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -526,7 +526,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mLockscreenUserManager.mLastLockTime
.set(mSensitiveNotifPostTime - TimeUnit.DAYS.toMillis(1));
// Device is not currently locked
- when(mKeyguardManager.isDeviceLocked()).thenReturn(false);
+ mLockscreenUserManager.mLocked.set(false);
// Sensitive Content notifications are always redacted
assertEquals(REDACTION_TYPE_NONE,
@@ -540,7 +540,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mCurrentUser.id);
changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+ mLockscreenUserManager.mLocked.set(true);
// Device was locked after this notification arrived
mLockscreenUserManager.mLastLockTime
.set(mSensitiveNotifPostTime + TimeUnit.DAYS.toMillis(1));
@@ -560,7 +560,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
// Device has been locked for 1 second before the notification came in, which is too short
mLockscreenUserManager.mLastLockTime
.set(mSensitiveNotifPostTime - TimeUnit.SECONDS.toMillis(1));
- when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+ mLockscreenUserManager.mLocked.set(true);
// Sensitive Content notifications are always redacted
assertEquals(REDACTION_TYPE_NONE,
@@ -577,7 +577,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
// Claim the device was last locked 1 day ago
mLockscreenUserManager.mLastLockTime
.set(mSensitiveNotifPostTime - TimeUnit.DAYS.toMillis(1));
- when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+ mLockscreenUserManager.mLocked.set(true);
// Sensitive Content notifications are always redacted
assertEquals(REDACTION_TYPE_NONE,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 2d7dc2e63650..0a0564994e69 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -43,6 +43,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.WallpaperController
import com.android.systemui.util.mockito.eq
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
+import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
import org.junit.Before
@@ -65,6 +66,7 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
+import java.util.Optional
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -82,6 +84,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Mock private lateinit var wallpaperController: WallpaperController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var appZoomOutOptional: Optional<AppZoomOut>
@Mock private lateinit var root: View
@Mock private lateinit var viewRootImpl: ViewRootImpl
@Mock private lateinit var windowToken: IBinder
@@ -128,6 +131,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
ResourcesSplitShadeStateController(),
windowRootViewBlurInteractor,
applicationScope,
+ appZoomOutOptional,
dumpManager,
configurationController,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt
index c9c961791e89..49b95d92129c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderTest.kt
@@ -45,7 +45,7 @@ import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
+import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelTest.kt
new file mode 100644
index 000000000000..72001758d01f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.layout.ui.viewmodel
+
+import android.content.res.Configuration
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.layout.statusBarContentInsetsProvider
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.fake
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class StatusBarContentInsetsViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val configuration = Configuration()
+
+ private val Kosmos.underTest by Kosmos.Fixture { statusBarContentInsetsViewModel }
+
+ @Test
+ fun contentArea_onMaxBoundsChanged_emitsNewValue() =
+ kosmos.runTest {
+ statusBarContentInsetsProvider.start()
+
+ val values by collectValues(underTest.contentArea)
+
+ // WHEN the content area changes
+ configurationController.fake.notifyLayoutDirectionChanged(isRtl = true)
+ configurationController.fake.notifyDensityOrFontScaleChanged()
+
+ // THEN the flow emits the new bounds
+ assertThat(values[0]).isNotEqualTo(values[1])
+ }
+
+ @Test
+ fun contentArea_onDensityOrFontScaleChanged_emitsLastBounds() =
+ kosmos.runTest {
+ configuration.densityDpi = 12
+ statusBarContentInsetsProvider.start()
+
+ val values by collectValues(underTest.contentArea)
+
+ // WHEN a change happens but it doesn't affect content area
+ configuration.densityDpi = 20
+ configurationController.onConfigurationChanged(configuration)
+ configurationController.fake.notifyDensityOrFontScaleChanged()
+
+ // THEN it still has the last bounds
+ assertThat(values).hasSize(1)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 72a91bc12f8d..14bbd38ece2c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -279,7 +279,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase {
notification);
RemoteViews headerRemoteViews;
if (lowPriority) {
- headerRemoteViews = builder.makeLowPriorityContentView(true, false);
+ headerRemoteViews = builder.makeLowPriorityContentView(true);
} else {
headerRemoteViews = builder.makeNotificationGroupHeader();
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index ffd349d744a8..43ad042ecf78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -53,7 +53,7 @@ import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
-import com.android.systemui.statusbar.layout.statusBarContentInsetsProvider
+import com.android.systemui.statusbar.layout.mockStatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
import com.android.systemui.statusbar.policy.BatteryController
@@ -153,7 +153,8 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
shadeViewStateProvider = TestShadeViewStateProvider()
Mockito.`when`(
- kosmos.statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
+ kosmos.mockStatusBarContentInsetsProvider
+ .getStatusBarContentInsetsForCurrentRotation()
)
.thenReturn(Insets.of(0, 0, 0, 0))
@@ -162,7 +163,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(iconManager)
Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay)
- .thenReturn(kosmos.statusBarContentInsetsProvider)
+ .thenReturn(kosmos.mockStatusBarContentInsetsProvider)
allowTestableLooperAsMainThread()
looper.runWithLooper {
keyguardStatusBarView =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index faf736a543dd..6feada1c9769 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -77,6 +77,8 @@ class FakeHomeStatusBarViewModel(
override val iconBlockList: MutableStateFlow<List<String>> = MutableStateFlow(listOf())
+ override val contentArea = MutableStateFlow(Rect(0, 0, 1, 1))
+
val darkRegions = mutableListOf<Rect>()
var darkIconTint = Color.BLACK
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index a70b777a25f2..e95bc3378423 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -26,10 +26,13 @@ import android.content.testableContext
import android.graphics.Rect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.fake
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -85,6 +88,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Before
import org.junit.Test
@@ -104,6 +108,9 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
setUpPackageManagerForMediaProjection(kosmos)
}
+ @Before
+ fun addDisplays() = runBlocking { kosmos.displayRepository.fake.addDisplay(DEFAULT_DISPLAY) }
+
@Test
fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
kosmos.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
index 61c719319de8..824955de83e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
@@ -28,6 +28,7 @@ import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.policy.statusBarConfigurationController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -41,13 +42,18 @@ class StatusBarWindowControllerImplTest : SysuiTestCase() {
private val kosmos =
testKosmos().also { it.statusBarWindowViewInflater = it.fakeStatusBarWindowViewInflater }
- private val underTest = kosmos.statusBarWindowControllerImpl
+ private lateinit var underTest: StatusBarWindowControllerImpl
private val fakeExecutor = kosmos.fakeExecutor
private val fakeWindowManager = kosmos.fakeWindowManager
private val mockFragmentService = kosmos.fragmentService
private val fakeStatusBarWindowViewInflater = kosmos.fakeStatusBarWindowViewInflater
private val statusBarConfigurationController = kosmos.statusBarConfigurationController
+ @Before
+ fun setUp() {
+ underTest = kosmos.statusBarWindowControllerImpl
+ }
+
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun attach_connectedDisplaysFlagEnabled_setsConfigControllerOnWindowView() {
diff --git a/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt b/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt
index d93f7d3093b8..81156d9698d8 100644
--- a/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt
+++ b/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt
@@ -24,6 +24,7 @@ import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
import javax.lang.model.element.PackageElement
import javax.lang.model.element.TypeElement
import javax.lang.model.type.TypeKind
@@ -183,11 +184,17 @@ class ProtectedPluginProcessor : AbstractProcessor() {
// Method implementations
for (method in methods) {
val methodName = method.simpleName
+ if (methods.any { methodName.startsWith("${it.simpleName}\$") }) {
+ continue
+ }
val returnTypeName = method.returnType.toString()
val callArgs = StringBuilder()
var isFirst = true
+ val isStatic = method.modifiers.contains(Modifier.STATIC)
- line("@Override")
+ if (!isStatic) {
+ line("@Override")
+ }
parenBlock("public $returnTypeName $methodName") {
// While copying the method signature for the proxy type, we also
// accumulate arguments for the nested callsite.
@@ -202,7 +209,8 @@ class ProtectedPluginProcessor : AbstractProcessor() {
}
val isVoid = method.returnType.kind == TypeKind.VOID
- val nestedCall = "mInstance.$methodName($callArgs)"
+ val methodContainer = if (isStatic) sourceName else "mInstance"
+ val nestedCall = "$methodContainer.$methodName($callArgs)"
val callStatement =
when {
isVoid -> "$nestedCall;"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 51892aac606a..ff6bcdb150f8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -19,6 +19,7 @@ package com.android.systemui.shared.system;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
+import android.window.TransitionInfo;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -30,7 +31,7 @@ public interface RecentsAnimationListener {
*/
void onAnimationStart(RecentsAnimationControllerCompat controller,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
- Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras);
+ Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras, TransitionInfo info);
/**
* Called when the animation into Recents was canceled. This call is made on the binder thread.
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index f530522fb707..5f79c8cada45 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -100,7 +100,8 @@ public abstract class SystemUIInitializer {
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
.setBackAnimation(mWMComponent.getBackAnimation())
- .setDesktopMode(mWMComponent.getDesktopMode());
+ .setDesktopMode(mWMComponent.getDesktopMode())
+ .setAppZoomOut(mWMComponent.getAppZoomOut());
// Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment
@@ -121,7 +122,8 @@ public abstract class SystemUIInitializer {
.setStartingSurface(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
.setBackAnimation(Optional.ofNullable(null))
- .setDesktopMode(Optional.ofNullable(null));
+ .setDesktopMode(Optional.ofNullable(null))
+ .setAppZoomOut(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
index 4dc2a13480f5..0303048436c9 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -104,6 +104,31 @@ constructor(
}
}
+ override suspend fun onActionIconClick(deviceItem: DeviceItem, onIntent: (Intent) -> Unit) {
+ withContext(backgroundDispatcher) {
+ if (!audioSharingInteractor.audioSharingAvailable()) {
+ return@withContext deviceItemActionInteractorImpl.onActionIconClick(
+ deviceItem,
+ onIntent,
+ )
+ }
+
+ when (deviceItem.type) {
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.CHECK_MARK_ACTION_BUTTON_CLICKED)
+ audioSharingInteractor.stopAudioSharing()
+ }
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.PLUS_ACTION_BUTTON_CLICKED)
+ audioSharingInteractor.startAudioSharing()
+ }
+ else -> {
+ deviceItemActionInteractorImpl.onActionIconClick(deviceItem, onIntent)
+ }
+ }
+ }
+ }
+
private fun inSharingAndDeviceNoSource(
inAudioSharing: Boolean,
deviceItem: DeviceItem,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
index c4f26cd46bf8..116e76c82008 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -29,6 +29,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
@@ -54,6 +55,8 @@ interface AudioSharingInteractor {
suspend fun startAudioSharing()
+ suspend fun stopAudioSharing()
+
suspend fun audioSharingAvailable(): Boolean
suspend fun qsDialogImprovementAvailable(): Boolean
@@ -61,7 +64,7 @@ interface AudioSharingInteractor {
@SysUISingleton
@OptIn(ExperimentalCoroutinesApi::class)
-class AudioSharingInteractorImpl
+open class AudioSharingInteractorImpl
@Inject
constructor(
private val context: Context,
@@ -99,6 +102,9 @@ constructor(
if (audioSharingAvailable()) {
audioSharingRepository.leAudioBroadcastProfile?.let { profile ->
isAudioSharingOn
+ // Skip the default value, we only care about adding source for newly
+ // started audio sharing session
+ .drop(1)
.mapNotNull { audioSharingOn ->
if (audioSharingOn) {
// onBroadcastMetadataChanged could emit multiple times during one
@@ -145,6 +151,13 @@ constructor(
audioSharingRepository.startAudioSharing()
}
+ override suspend fun stopAudioSharing() {
+ if (!audioSharingAvailable()) {
+ return
+ }
+ audioSharingRepository.stopAudioSharing()
+ }
+
// TODO(b/367965193): Move this after flags rollout
override suspend fun audioSharingAvailable(): Boolean {
return audioSharingRepository.audioSharingAvailable()
@@ -181,6 +194,8 @@ class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingIntera
override suspend fun startAudioSharing() {}
+ override suspend fun stopAudioSharing() {}
+
override suspend fun audioSharingAvailable(): Boolean = false
override suspend fun qsDialogImprovementAvailable(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
index b9b8d36d41e6..44f9769f5930 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
@@ -45,6 +45,8 @@ interface AudioSharingRepository {
suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice)
suspend fun startAudioSharing()
+
+ suspend fun stopAudioSharing()
}
@SysUISingleton
@@ -100,6 +102,15 @@ class AudioSharingRepositoryImpl(
leAudioBroadcastProfile?.startPrivateBroadcast()
}
}
+
+ override suspend fun stopAudioSharing() {
+ withContext(backgroundDispatcher) {
+ if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
+ return@withContext
+ }
+ leAudioBroadcastProfile?.stopLatestBroadcast()
+ }
+ }
}
@SysUISingleton
@@ -117,4 +128,6 @@ class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
override suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice) {}
override suspend fun startAudioSharing() {}
+
+ override suspend fun stopAudioSharing() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index b294dd1b0b71..56caddfbd637 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -56,6 +56,13 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
+data class DeviceItemClick(val deviceItem: DeviceItem, val clickedView: View, val target: Target) {
+ enum class Target {
+ ENTIRE_ROW,
+ ACTION_ICON,
+ }
+}
+
/** Dialog for showing active, connected and saved bluetooth devices. */
class BluetoothTileDialogDelegate
@AssistedInject
@@ -80,7 +87,7 @@ internal constructor(
internal val bluetoothAutoOnToggle
get() = mutableBluetoothAutoOnToggle.asStateFlow()
- private val mutableDeviceItemClick: MutableSharedFlow<DeviceItem> =
+ private val mutableDeviceItemClick: MutableSharedFlow<DeviceItemClick> =
MutableSharedFlow(extraBufferCapacity = 1)
internal val deviceItemClick
get() = mutableDeviceItemClick.asSharedFlow()
@@ -90,7 +97,7 @@ internal constructor(
internal val contentHeight
get() = mutableContentHeight.asSharedFlow()
- private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback)
+ private val deviceItemAdapter: Adapter = Adapter()
private var lastUiUpdateMs: Long = -1
@@ -334,8 +341,7 @@ internal constructor(
}
}
- internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) :
- RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
+ internal inner class Adapter : RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
private val diffUtilCallback =
object : DiffUtil.ItemCallback<DeviceItem>() {
@@ -376,7 +382,7 @@ internal constructor(
override fun onBindViewHolder(holder: DeviceItemViewHolder, position: Int) {
val item = getItem(position)
- holder.bind(item, onClickCallback)
+ holder.bind(item)
}
internal fun getItem(position: Int) = asyncListDiffer.currentList[position]
@@ -390,19 +396,18 @@ internal constructor(
private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name)
private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary)
private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon)
- private val iconGear = view.requireViewById<ImageView>(R.id.gear_icon_image)
- private val gearView = view.requireViewById<View>(R.id.gear_icon)
+ private val actionIcon = view.requireViewById<ImageView>(R.id.gear_icon_image)
+ private val actionIconView = view.requireViewById<View>(R.id.gear_icon)
private val divider = view.requireViewById<View>(R.id.divider)
- internal fun bind(
- item: DeviceItem,
- deviceItemOnClickCallback: BluetoothTileDialogCallback,
- ) {
+ internal fun bind(item: DeviceItem) {
container.apply {
isEnabled = item.isEnabled
background = item.background?.let { context.getDrawable(it) }
setOnClickListener {
- mutableDeviceItemClick.tryEmit(item)
+ mutableDeviceItemClick.tryEmit(
+ DeviceItemClick(item, it, DeviceItemClick.Target.ENTIRE_ROW)
+ )
uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
}
@@ -421,7 +426,8 @@ internal constructor(
}
}
- iconGear.apply { drawable?.let { it.mutate()?.setTint(tintColor) } }
+ actionIcon.setImageResource(item.actionIconRes)
+ actionIcon.drawable?.setTint(tintColor)
divider.setBackgroundColor(tintColor)
@@ -454,8 +460,10 @@ internal constructor(
nameView.text = item.deviceName
summaryView.text = item.connectionSummary
- gearView.setOnClickListener {
- deviceItemOnClickCallback.onDeviceItemGearClicked(item, it)
+ actionIconView.setOnClickListener {
+ mutableDeviceItemClick.tryEmit(
+ DeviceItemClick(item, it, DeviceItemClick.Target.ACTION_ICON)
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index aad233fe40ca..7c66ec059e64 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -49,7 +49,7 @@ enum class BluetoothTileDialogUiEvent(val metricId: Int) : UiEventLogger.UiEvent
LAUNCH_SETTINGS_NOT_SHARING_SAVED_LE_DEVICE_CLICKED(1719),
@Deprecated(
"Use case no longer needed",
- ReplaceWith("LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED")
+ ReplaceWith("LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED"),
)
@UiEvent(doc = "Not broadcasting, one of the two connected LE audio devices is clicked")
LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED(1720),
@@ -59,7 +59,11 @@ enum class BluetoothTileDialogUiEvent(val metricId: Int) : UiEventLogger.UiEvent
@UiEvent(doc = "Clicked on switch active button on audio sharing dialog")
AUDIO_SHARING_DIALOG_SWITCH_ACTIVE_CLICKED(1890),
@UiEvent(doc = "Clicked on share audio button on audio sharing dialog")
- AUDIO_SHARING_DIALOG_SHARE_AUDIO_CLICKED(1891);
+ AUDIO_SHARING_DIALOG_SHARE_AUDIO_CLICKED(1891),
+ @UiEvent(doc = "Clicked on plus action button")
+ PLUS_ACTION_BUTTON_CLICKED(2061),
+ @UiEvent(doc = "Clicked on checkmark action button")
+ CHECK_MARK_ACTION_BUTTON_CLICKED(2062);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index 497d8cf2e159..9460e7c2c8d5 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -35,7 +35,6 @@ import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
import com.android.systemui.dagger.SysUISingleton
@@ -227,8 +226,22 @@ constructor(
// deviceItemClick is emitted when user clicked on a device item.
dialogDelegate.deviceItemClick
.onEach {
- deviceItemActionInteractor.onClick(it, dialog)
- logger.logDeviceClick(it.cachedBluetoothDevice.address, it.type)
+ when (it.target) {
+ DeviceItemClick.Target.ENTIRE_ROW -> {
+ deviceItemActionInteractor.onClick(it.deviceItem, dialog)
+ logger.logDeviceClick(
+ it.deviceItem.cachedBluetoothDevice.address,
+ it.deviceItem.type,
+ )
+ }
+
+ DeviceItemClick.Target.ACTION_ICON -> {
+ deviceItemActionInteractor.onActionIconClick(it.deviceItem) { intent
+ ->
+ startSettingsActivity(intent, it.clickedView)
+ }
+ }
+ }
}
.launchIn(this)
@@ -287,20 +300,6 @@ constructor(
)
}
- override fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) {
- uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_GEAR_CLICKED)
- val intent =
- Intent(ACTION_BLUETOOTH_DEVICE_DETAILS).apply {
- putExtra(
- EXTRA_SHOW_FRAGMENT_ARGUMENTS,
- Bundle().apply {
- putString("device_address", deviceItem.cachedBluetoothDevice.address)
- },
- )
- }
- startSettingsActivity(intent, view)
- }
-
override fun onSeeAllClicked(view: View) {
uiEventLogger.log(BluetoothTileDialogUiEvent.SEE_ALL_CLICKED)
startSettingsActivity(Intent(ACTION_PREVIOUSLY_CONNECTED_DEVICE), view)
@@ -382,8 +381,6 @@ constructor(
}
interface BluetoothTileDialogCallback {
- fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
-
fun onSeeAllClicked(view: View)
fun onPairNewDeviceClicked(view: View)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index 2ba4c73a0293..f7af16d99fbf 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -53,5 +53,6 @@ data class DeviceItem(
val background: Int? = null,
var isEnabled: Boolean = true,
var actionAccessibilityLabel: String = "",
- var isActive: Boolean = false
+ var isActive: Boolean = false,
+ val actionIconRes: Int = -1,
)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
index 2b55e1c51f5f..cb4ec37a1a66 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.bluetooth.qsdialog
+import android.content.Intent
+import android.os.Bundle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -25,7 +27,9 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
interface DeviceItemActionInteractor {
- suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {}
+ suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog)
+
+ suspend fun onActionIconClick(deviceItem: DeviceItem, onIntent: (Intent) -> Unit)
}
@SysUISingleton
@@ -67,4 +71,44 @@ constructor(
}
}
}
+
+ override suspend fun onActionIconClick(deviceItem: DeviceItem, onIntent: (Intent) -> Unit) {
+ withContext(backgroundDispatcher) {
+ deviceItem.cachedBluetoothDevice.apply {
+ when (deviceItem.type) {
+ DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
+ DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_GEAR_CLICKED)
+ val intent =
+ Intent(ACTION_BLUETOOTH_DEVICE_DETAILS).apply {
+ putExtra(
+ EXTRA_SHOW_FRAGMENT_ARGUMENTS,
+ Bundle().apply {
+ putString(
+ "device_address",
+ deviceItem.cachedBluetoothDevice.address,
+ )
+ },
+ )
+ }
+ onIntent(intent)
+ }
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ throw IllegalArgumentException("Invalid device type: ${deviceItem.type}")
+ // Throw exception. Should already be handled in
+ // AudioSharingDeviceItemActionInteractor.
+ }
+ }
+ }
+ }
+ }
+
+ private companion object {
+ const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
+ const val ACTION_BLUETOOTH_DEVICE_DETAILS =
+ "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 92f05803f7cf..095e6e741584 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -30,6 +30,8 @@ private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off
private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
private val connected = R.string.quick_settings_bluetooth_device_connected
private val audioSharing = R.string.quick_settings_bluetooth_device_audio_sharing
+private val audioSharingAddIcon = R.drawable.ic_add
+private val audioSharingOnGoingIcon = R.drawable.ic_check
private val saved = R.string.quick_settings_bluetooth_device_saved
private val actionAccessibilityLabelActivate =
R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate
@@ -63,6 +65,7 @@ abstract class DeviceItemFactory {
background: Int,
actionAccessibilityLabel: String,
isActive: Boolean,
+ actionIconRes: Int = R.drawable.ic_settings_24dp,
): DeviceItem {
return DeviceItem(
type = type,
@@ -75,6 +78,7 @@ abstract class DeviceItemFactory {
isEnabled = !cachedDevice.isBusy,
actionAccessibilityLabel = actionAccessibilityLabel,
isActive = isActive,
+ actionIconRes = actionIconRes,
)
}
}
@@ -125,6 +129,7 @@ internal class AudioSharingMediaDeviceItemFactory(
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
"",
isActive = !cachedDevice.isBusy,
+ actionIconRes = audioSharingOnGoingIcon,
)
}
}
@@ -156,6 +161,7 @@ internal class AvailableAudioSharingMediaDeviceItemFactory(
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
"",
isActive = false,
+ actionIconRes = audioSharingAddIcon,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 00eead6eb7fc..555fe6ef157d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.phone.ConfigurationForwarder;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.wm.shell.appzoomout.AppZoomOut;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
@@ -115,6 +116,9 @@ public interface SysUIComponent {
@BindsInstance
Builder setDesktopMode(Optional<DesktopMode> d);
+ @BindsInstance
+ Builder setAppZoomOut(Optional<AppZoomOut> a);
+
SysUIComponent build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2bcd3fcfed17..10b726b90894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -92,6 +92,7 @@ import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Inject;
@@ -297,6 +298,9 @@ public class NotificationLockscreenUserManagerImpl implements
// The last lock time. Uses currentTimeMillis
@VisibleForTesting
protected final AtomicLong mLastLockTime = new AtomicLong(-1);
+ // Whether or not the device is locked
+ @VisibleForTesting
+ protected final AtomicBoolean mLocked = new AtomicBoolean(true);
protected int mCurrentUserId = 0;
@@ -369,6 +373,7 @@ public class NotificationLockscreenUserManagerImpl implements
if (!unlocked) {
mLastLockTime.set(System.currentTimeMillis());
}
+ mLocked.set(!unlocked);
}));
}
}
@@ -737,7 +742,7 @@ public class NotificationLockscreenUserManagerImpl implements
return false;
}
- if (!mKeyguardManager.isDeviceLocked()) {
+ if (!mLocked.get()) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 75117936c090..38f7c39203f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -34,6 +34,7 @@ import androidx.dynamicanimation.animation.SpringForce
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.TrackTracer
import com.android.systemui.Dumpable
+import com.android.systemui.Flags.spatialModelAppPushback
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -52,7 +53,9 @@ import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.WallpaperController
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.systemui.window.flag.WindowBlurFlag
+import com.android.wm.shell.appzoomout.AppZoomOut
import java.io.PrintWriter
+import java.util.Optional
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.sign
@@ -79,6 +82,7 @@ constructor(
private val splitShadeStateController: SplitShadeStateController,
private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
@Application private val applicationScope: CoroutineScope,
+ private val appZoomOutOptional: Optional<AppZoomOut>,
dumpManager: DumpManager,
configurationController: ConfigurationController,
) : ShadeExpansionListener, Dumpable {
@@ -271,6 +275,13 @@ constructor(
private fun onBlurApplied(appliedBlurRadius: Int, zoomOutFromShadeRadius: Float) {
lastAppliedBlur = appliedBlurRadius
wallpaperController.setNotificationShadeZoom(zoomOutFromShadeRadius)
+ if (spatialModelAppPushback()) {
+ appZoomOutOptional.ifPresent { appZoomOut ->
+ appZoomOut.setProgress(
+ zoomOutFromShadeRadius
+ )
+ }
+ }
listeners.forEach {
it.onWallpaperZoomOutChanged(zoomOutFromShadeRadius)
it.onBlurRadiusChanged(appliedBlurRadius)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index 69ef09d8bf5e..b0fa9d842480 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -25,6 +25,7 @@ import android.widget.DateTimeView
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
+import androidx.annotation.UiThread
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
@@ -38,24 +39,24 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.Notificati
/** Binder for ongoing activity chip views. */
object OngoingActivityChipBinder {
/** Binds the given [chipModel] data to the given [chipView]. */
- fun bind(chipModel: OngoingActivityChipModel, chipView: View, iconViewStore: IconViewStore?) {
- val chipContext = chipView.context
- val chipDefaultIconView: ImageView =
- chipView.requireViewById(R.id.ongoing_activity_chip_icon)
- val chipTimeView: ChipChronometer =
- chipView.requireViewById(R.id.ongoing_activity_chip_time)
- val chipTextView: TextView = chipView.requireViewById(R.id.ongoing_activity_chip_text)
- val chipShortTimeDeltaView: DateTimeView =
- chipView.requireViewById(R.id.ongoing_activity_chip_short_time_delta)
- val chipBackgroundView: ChipBackgroundContainer =
- chipView.requireViewById(R.id.ongoing_activity_chip_background)
+ fun bind(
+ chipModel: OngoingActivityChipModel,
+ viewBinding: OngoingActivityChipViewBinding,
+ iconViewStore: IconViewStore?,
+ ) {
+ val chipContext = viewBinding.rootView.context
+ val chipDefaultIconView = viewBinding.defaultIconView
+ val chipTimeView = viewBinding.timeView
+ val chipTextView = viewBinding.textView
+ val chipShortTimeDeltaView = viewBinding.shortTimeDeltaView
+ val chipBackgroundView = viewBinding.backgroundView
when (chipModel) {
is OngoingActivityChipModel.Shown -> {
// Data
setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore)
setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView)
- chipView.setOnClickListener(chipModel.onClickListener)
+ viewBinding.rootView.setOnClickListener(chipModel.onClickListener)
updateChipPadding(
chipModel,
chipBackgroundView,
@@ -65,7 +66,7 @@ object OngoingActivityChipBinder {
)
// Accessibility
- setChipAccessibility(chipModel, chipView, chipBackgroundView)
+ setChipAccessibility(chipModel, viewBinding.rootView, chipBackgroundView)
// Colors
val textColor = chipModel.colors.text(chipContext)
@@ -83,6 +84,85 @@ object OngoingActivityChipBinder {
}
}
+ /** Stores [rootView] and relevant child views in an object for easy reference. */
+ fun createBinding(rootView: View): OngoingActivityChipViewBinding {
+ return OngoingActivityChipViewBinding(
+ rootView = rootView,
+ timeView = rootView.requireViewById(R.id.ongoing_activity_chip_time),
+ textView = rootView.requireViewById(R.id.ongoing_activity_chip_text),
+ shortTimeDeltaView =
+ rootView.requireViewById(R.id.ongoing_activity_chip_short_time_delta),
+ defaultIconView = rootView.requireViewById(R.id.ongoing_activity_chip_icon),
+ backgroundView = rootView.requireViewById(R.id.ongoing_activity_chip_background),
+ )
+ }
+
+ /**
+ * Resets any width restrictions that were placed on the primary chip's contents.
+ *
+ * Should be used when the user's screen bounds changed because there may now be more room in
+ * the status bar to show additional content.
+ */
+ fun resetPrimaryChipWidthRestrictions(
+ primaryChipViewBinding: OngoingActivityChipViewBinding,
+ currentPrimaryChipViewModel: OngoingActivityChipModel,
+ ) {
+ if (currentPrimaryChipViewModel is OngoingActivityChipModel.Hidden) {
+ return
+ }
+ resetChipMainContentWidthRestrictions(
+ primaryChipViewBinding,
+ currentPrimaryChipViewModel as OngoingActivityChipModel.Shown,
+ )
+ }
+
+ /**
+ * Resets any width restrictions that were placed on the secondary chip and its contents.
+ *
+ * Should be used when the user's screen bounds changed because there may now be more room in
+ * the status bar to show additional content.
+ */
+ fun resetSecondaryChipWidthRestrictions(
+ secondaryChipViewBinding: OngoingActivityChipViewBinding,
+ currentSecondaryChipModel: OngoingActivityChipModel,
+ ) {
+ if (currentSecondaryChipModel is OngoingActivityChipModel.Hidden) {
+ return
+ }
+ secondaryChipViewBinding.rootView.resetWidthRestriction()
+ resetChipMainContentWidthRestrictions(
+ secondaryChipViewBinding,
+ currentSecondaryChipModel as OngoingActivityChipModel.Shown,
+ )
+ }
+
+ private fun resetChipMainContentWidthRestrictions(
+ viewBinding: OngoingActivityChipViewBinding,
+ model: OngoingActivityChipModel.Shown,
+ ) {
+ when (model) {
+ is OngoingActivityChipModel.Shown.Text -> viewBinding.textView.resetWidthRestriction()
+ is OngoingActivityChipModel.Shown.Timer -> viewBinding.timeView.resetWidthRestriction()
+ is OngoingActivityChipModel.Shown.ShortTimeDelta ->
+ viewBinding.shortTimeDeltaView.resetWidthRestriction()
+ is OngoingActivityChipModel.Shown.IconOnly,
+ is OngoingActivityChipModel.Shown.Countdown -> {}
+ }
+ }
+
+ /**
+ * Resets any width restrictions that were placed on the given view.
+ *
+ * Should be used when the user's screen bounds changed because there may now be more room in
+ * the status bar to show additional content.
+ */
+ @UiThread
+ fun View.resetWidthRestriction() {
+ // View needs to be visible in order to be re-measured
+ visibility = View.VISIBLE
+ forceLayout()
+ }
+
private fun setChipIcon(
chipModel: OngoingActivityChipModel.Shown,
backgroundView: ChipBackgroundContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipViewBinding.kt
new file mode 100644
index 000000000000..1814b7430330
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipViewBinding.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.binder
+
+import android.view.View
+import android.widget.ImageView
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.statusbar.chips.ui.view.ChipDateTimeView
+import com.android.systemui.statusbar.chips.ui.view.ChipTextView
+
+/** Stores bound views for a given chip. */
+data class OngoingActivityChipViewBinding(
+ val rootView: View,
+ val timeView: ChipChronometer,
+ val textView: ChipTextView,
+ val shortTimeDeltaView: ChipDateTimeView,
+ val defaultIconView: ImageView,
+ val backgroundView: ChipBackgroundContainer,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
index ff3061e850d9..7b4b79d7c852 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
@@ -33,10 +33,8 @@ import androidx.annotation.UiThread
* that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to
* 1:00:00), but never smaller.
* 2) Hiding the text if the time gets too long for the space available. Once the text has been
- * hidden, it remains hidden for the duration of the activity.
- *
- * Note that if the text was too big in portrait mode, resulting in the text being hidden, then the
- * text will also be hidden in landscape (even if there is enough space for it in landscape).
+ * hidden, it remains hidden for the duration of the activity (or until [resetWidthRestriction]
+ * is called).
*/
class ChipChronometer
@JvmOverloads
@@ -51,12 +49,23 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
private var shouldHideText: Boolean = false
override fun setBase(base: Long) {
- // These variables may have changed during the previous activity, so re-set them before the
- // new activity starts.
+ resetWidthRestriction()
+ super.setBase(base)
+ }
+
+ /**
+ * Resets any width restrictions that were placed on the chronometer.
+ *
+ * Should be used when the user's screen bounds changed because there may now be more room in
+ * the status bar to show additional content.
+ */
+ @UiThread
+ fun resetWidthRestriction() {
minimumTextWidth = 0
shouldHideText = false
+ // View needs to be visible in order to be re-measured
visibility = VISIBLE
- super.setBase(base)
+ forceLayout()
}
/** Sets whether this view should hide its text or not. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index eff959d0f83b..351cdc8e7f36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.data.repository.LightBarControllerStore
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProviderImpl
+import com.android.systemui.statusbar.layout.ui.viewmodel.StatusBarContentInsetsViewModel
import com.android.systemui.statusbar.phone.AutoHideController
import com.android.systemui.statusbar.phone.AutoHideControllerImpl
import com.android.systemui.statusbar.phone.LightBarController
@@ -39,6 +40,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.ui.StatusBarUiLayerModule
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore
import com.android.systemui.statusbar.window.SingleDisplayStatusBarWindowControllerStore
@@ -60,7 +62,14 @@ import dagger.multibindings.IntoMap
* ([com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule],
* [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
*/
-@Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
+@Module(
+ includes =
+ [
+ StatusBarDataLayerModule::class,
+ StatusBarUiLayerModule::class,
+ SystemBarUtilsProxyImpl.Module::class,
+ ]
+)
interface StatusBarModule {
@Binds
@@ -169,5 +178,13 @@ interface StatusBarModule {
): StatusBarContentInsetsProvider {
return factory.create(context, configurationController, sysUICutoutProvider)
}
+
+ @Provides
+ @SysUISingleton
+ fun contentInsetsViewModel(
+ insetsProvider: StatusBarContentInsetsProvider
+ ): StatusBarContentInsetsViewModel {
+ return StatusBarContentInsetsViewModel(insetsProvider)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModel.kt
new file mode 100644
index 000000000000..03c07480ecea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.layout.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.layout.StatusBarContentInsetsChangedListener
+import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
+
+/** A recommended architecture version of [StatusBarContentInsetsProvider]. */
+class StatusBarContentInsetsViewModel(
+ private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider
+) {
+ /** Emits the status bar content area for the given rotation in absolute bounds. */
+ val contentArea: Flow<Rect> =
+ conflatedCallbackFlow {
+ val listener =
+ object : StatusBarContentInsetsChangedListener {
+ override fun onStatusBarContentInsetsChanged() {
+ trySend(
+ statusBarContentInsetsProvider
+ .getStatusBarContentAreaForCurrentRotation()
+ )
+ }
+ }
+ statusBarContentInsetsProvider.addCallback(listener)
+ awaitClose { statusBarContentInsetsProvider.removeCallback(listener) }
+ }
+ .onStart {
+ emit(statusBarContentInsetsProvider.getStatusBarContentAreaForCurrentRotation())
+ }
+ .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
new file mode 100644
index 000000000000..d2dccc49ffd7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.layout.ui.viewmodel
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.display.data.repository.SingleDisplayStore
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per-display instances of [StatusBarContentInsetsViewModel]. */
+interface StatusBarContentInsetsViewModelStore : PerDisplayStore<StatusBarContentInsetsViewModel>
+
+@SysUISingleton
+class MultiDisplayStatusBarContentInsetsViewModelStore
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+) :
+ StatusBarContentInsetsViewModelStore,
+ PerDisplayStoreImpl<StatusBarContentInsetsViewModel>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ override fun createInstanceForDisplay(displayId: Int): StatusBarContentInsetsViewModel? {
+ val insetsProvider =
+ statusBarContentInsetsProviderStore.forDisplay(displayId) ?: return null
+ return StatusBarContentInsetsViewModel(insetsProvider)
+ }
+
+ override val instanceClass = StatusBarContentInsetsViewModel::class.java
+}
+
+@SysUISingleton
+class SingleDisplayStatusBarContentInsetsViewModelStore
+@Inject
+constructor(statusBarContentInsetsViewModel: StatusBarContentInsetsViewModel) :
+ StatusBarContentInsetsViewModelStore,
+ PerDisplayStore<StatusBarContentInsetsViewModel> by SingleDisplayStore(
+ defaultInstance = statusBarContentInsetsViewModel
+ )
+
+@Module
+object StatusBarContentInsetsViewModelStoreModule {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(StatusBarContentInsetsViewModelStore::class)
+ fun storeAsCoreStartable(
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarContentInsetsViewModelStore>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ return multiDisplayLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun store(
+ singleDisplayLazy: Lazy<SingleDisplayStatusBarContentInsetsViewModelStore>,
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarContentInsetsViewModelStore>,
+ ): StatusBarContentInsetsViewModelStore {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ singleDisplayLazy.get()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
new file mode 100644
index 000000000000..e27ff7d6746b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+
+/**
+ * A background style for smarter-smart-actions.
+ *
+ * TODO(b/383567383) implement final UX
+ */
+class MagicActionBackgroundDrawable(context: Context) : Drawable() {
+
+ private var _alpha: Int = 255
+ private var _colorFilter: ColorFilter? = null
+ private val paint =
+ Paint().apply {
+ color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+ }
+
+ override fun draw(canvas: Canvas) {
+ canvas.drawRect(bounds, paint)
+ }
+
+ override fun setAlpha(alpha: Int) {
+ _alpha = alpha
+ invalidateSelf()
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ _colorFilter = colorFilter
+ invalidateSelf()
+ }
+
+ override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 7c44eae6c0b8..70e27a981b49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import static android.app.Flags.notificationsRedesignTemplates;
-
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
@@ -481,16 +479,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder
logger.logAsyncTaskProgress(entryForLogging,
"creating low-priority group summary remote view");
result.mNewMinimizedGroupHeaderView =
- builder.makeLowPriorityContentView(/* useRegularSubtext = */ true,
- /* highlightExpander = */ notificationsRedesignTemplates());
+ builder.makeLowPriorityContentView(true /* useRegularSubtext */);
}
}
setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
result.packageContext = packageContext;
result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(
- /* showingPublic = */ false);
+ false /* showingPublic */);
result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
- /* showingPublic = */ true);
+ true /* showingPublic */);
return result;
});
@@ -1139,8 +1136,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private static RemoteViews createContentView(Notification.Builder builder,
boolean isMinimized, boolean useLarge) {
if (isMinimized) {
- return builder.makeLowPriorityContentView(/* useRegularSubtext = */ false,
- /* highlightExpander = */ false);
+ return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
}
return builder.createContentView(useLarge);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index ae9b69c8f6bf..c619b17f1ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.row
import android.annotation.SuppressLint
-import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
import android.app.Notification.MessagingStyle
import android.content.Context
@@ -888,10 +887,7 @@ constructor(
entryForLogging,
"creating low-priority group summary remote view",
)
- builder.makeLowPriorityContentView(
- /* useRegularSubtext = */ true,
- /* highlightExpander = */ notificationsRedesignTemplates(),
- )
+ builder.makeLowPriorityContentView(true /* useRegularSubtext */)
} else null
NewRemoteViews(
contracted = contracted,
@@ -1661,10 +1657,7 @@ constructor(
useLarge: Boolean,
): RemoteViews {
return if (isMinimized) {
- builder.makeLowPriorityContentView(
- /* useRegularSubtext = */ false,
- /* highlightExpander = */ false,
- )
+ builder.makeLowPriorityContentView(false /* useRegularSubtext */)
} else builder.createContentView(useLarge)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index e477c7430262..8e48065d9d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -568,8 +568,7 @@ public class NotificationChildrenContainer extends ViewGroup
builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
}
- header = builder.makeLowPriorityContentView(true /* useRegularSubtext */,
- notificationsRedesignTemplates() /* highlightExpander */);
+ header = builder.makeLowPriorityContentView(true /* useRegularSubtext */);
if (mMinimizedGroupHeader == null) {
mMinimizedGroupHeader = (NotificationHeaderView) header.apply(getContext(),
this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 2541d84a5a97..31d6d86d1b37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernizat
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import javax.inject.Inject
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
/**
@@ -120,22 +121,26 @@ constructor(
!StatusBarNotifChips.isEnabled &&
!StatusBarChipsModernization.isEnabled
) {
- val primaryChipView: View =
- view.requireViewById(R.id.ongoing_activity_chip_primary)
+ val primaryChipViewBinding =
+ OngoingActivityChipBinder.createBinding(
+ view.requireViewById(R.id.ongoing_activity_chip_primary)
+ )
launch {
viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
OngoingActivityChipBinder.bind(
primaryChipModel,
- primaryChipView,
+ primaryChipViewBinding,
iconViewStore,
)
if (StatusBarRootModernization.isEnabled) {
when (primaryChipModel) {
is OngoingActivityChipModel.Shown ->
- primaryChipView.show(shouldAnimateChange = true)
+ primaryChipViewBinding.rootView.show(
+ shouldAnimateChange = true
+ )
is OngoingActivityChipModel.Hidden ->
- primaryChipView.hide(
+ primaryChipViewBinding.rootView.hide(
state = View.GONE,
shouldAnimateChange = primaryChipModel.shouldAnimate,
)
@@ -166,28 +171,34 @@ constructor(
StatusBarNotifChips.isEnabled &&
!StatusBarChipsModernization.isEnabled
) {
- val primaryChipView: View =
- view.requireViewById(R.id.ongoing_activity_chip_primary)
- val secondaryChipView: View =
- view.requireViewById(R.id.ongoing_activity_chip_secondary)
+ // Create view bindings here so we don't keep re-fetching child views each time
+ // the chip model changes.
+ val primaryChipViewBinding =
+ OngoingActivityChipBinder.createBinding(
+ view.requireViewById(R.id.ongoing_activity_chip_primary)
+ )
+ val secondaryChipViewBinding =
+ OngoingActivityChipBinder.createBinding(
+ view.requireViewById(R.id.ongoing_activity_chip_secondary)
+ )
launch {
- viewModel.ongoingActivityChips.collect { chips ->
+ viewModel.ongoingActivityChips.collectLatest { chips ->
OngoingActivityChipBinder.bind(
chips.primary,
- primaryChipView,
+ primaryChipViewBinding,
iconViewStore,
)
- // TODO(b/364653005): Don't show the secondary chip if there isn't
- // enough space for it.
OngoingActivityChipBinder.bind(
chips.secondary,
- secondaryChipView,
+ secondaryChipViewBinding,
iconViewStore,
)
if (StatusBarRootModernization.isEnabled) {
- primaryChipView.adjustVisibility(chips.primary.toVisibilityModel())
- secondaryChipView.adjustVisibility(
+ primaryChipViewBinding.rootView.adjustVisibility(
+ chips.primary.toVisibilityModel()
+ )
+ secondaryChipViewBinding.rootView.adjustVisibility(
chips.secondary.toVisibilityModel()
)
} else {
@@ -200,6 +211,18 @@ constructor(
shouldAnimate = true,
)
}
+
+ viewModel.contentArea.collect { _ ->
+ OngoingActivityChipBinder.resetPrimaryChipWidthRestrictions(
+ primaryChipViewBinding,
+ viewModel.ongoingActivityChips.value.primary,
+ )
+ OngoingActivityChipBinder.resetSecondaryChipWidthRestrictions(
+ secondaryChipViewBinding,
+ viewModel.ongoingActivityChips.value.secondary,
+ )
+ view.requestLayout()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 3f701fc56ab4..d731752ad5d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationSt
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipsViewModel
+import com.android.systemui.statusbar.layout.ui.viewmodel.StatusBarContentInsetsViewModelStore
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
@@ -130,6 +131,9 @@ interface HomeStatusBarViewModel {
/** Which icons to block from the home status bar */
val iconBlockList: Flow<List<String>>
+ /** This status bar's current content area for the given rotation in absolute bounds. */
+ val contentArea: Flow<Rect>
+
/**
* Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where
* status bar and navigation icons dim. In this mode, a notification dot appears where the
@@ -185,6 +189,7 @@ constructor(
ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel,
animations: SystemStatusEventAnimationInteractor,
+ statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
@Application coroutineScope: CoroutineScope,
) : HomeStatusBarViewModel {
override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
@@ -363,6 +368,10 @@ constructor(
override val iconBlockList: Flow<List<String>> =
homeStatusBarIconBlockListInteractor.iconBlockList
+ override val contentArea: Flow<Rect> =
+ statusBarContentInsetsViewModelStore.forDisplay(thisDisplayId)?.contentArea
+ ?: flowOf(Rect(0, 0, 0, 0))
+
@View.Visibility
private fun Boolean.toVisibleOrGone(): Int {
return if (this) View.VISIBLE else View.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index 56c9e9abbc36..cb26679434ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -41,6 +41,7 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.Button
+import com.android.systemui.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.shared.system.ActivityManagerWrapper
@@ -52,6 +53,7 @@ import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.row.MagicActionBackgroundDrawable
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions
import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
@@ -400,6 +402,15 @@ constructor(
.apply {
text = action.title
+ if (Flags.notificationMagicActionsTreatment()) {
+ if (
+ smartActions.fromAssistant &&
+ action.extras.getBoolean(Notification.Action.EXTRA_IS_MAGIC, false)
+ ) {
+ background = MagicActionBackgroundDrawable(parent.context)
+ }
+ }
+
// We received the Icon from the application - so use the Context of the application
// to
// reference icon resources.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/StatusBarUiLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/StatusBarUiLayerModule.kt
new file mode 100644
index 000000000000..8e81d78d60f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/StatusBarUiLayerModule.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.ui
+
+import com.android.systemui.statusbar.layout.ui.viewmodel.StatusBarContentInsetsViewModelStoreModule
+import dagger.Module
+
+@Module(includes = [StatusBarContentInsetsViewModelStoreModule::class])
+object StatusBarUiLayerModule
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
index 5d622eaeb1aa..e61acc4e1d0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -224,6 +225,30 @@ class AudioSharingDeviceItemActionInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun testOnActionIconClick_audioSharingMediaDevice_stopBroadcast() {
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ actionInteractorImpl.onActionIconClick(inAudioSharingMediaDeviceItem) {}
+ assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
+ .isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun testOnActionIconClick_availableAudioSharingMediaDevice_startBroadcast() {
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ actionInteractorImpl.onActionIconClick(connectedAudioSharingMediaDeviceItem) {}
+ assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
+ .isEqualTo(true)
+ }
+ }
+ }
+
private companion object {
const val DEVICE_NAME = "device"
const val DEVICE_CONNECTION_SUMMARY = "active"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 6bfd08025833..4396b0a42ae6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -32,6 +32,9 @@ import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.model.SysUiState
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
@@ -43,9 +46,8 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
@@ -93,7 +95,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
private val fakeSystemClock = FakeSystemClock()
- private lateinit var scheduler: TestCoroutineScheduler
private lateinit var dispatcher: CoroutineDispatcher
private lateinit var testScope: TestScope
private lateinit var icon: Pair<Drawable, String>
@@ -104,9 +105,8 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
@Before
fun setUp() {
- scheduler = TestCoroutineScheduler()
- dispatcher = UnconfinedTestDispatcher(scheduler)
- testScope = TestScope(dispatcher)
+ dispatcher = kosmos.testDispatcher
+ testScope = kosmos.testScope
whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
@@ -124,23 +124,19 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
kosmos.shadeDialogContextInteractor,
)
- whenever(
- sysuiDialogFactory.create(
- any(SystemUIDialog.Delegate::class.java),
- any()
- )
- ).thenAnswer {
- SystemUIDialog(
- mContext,
- 0,
- SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
- dialogManager,
- sysuiState,
- fakeBroadcastDispatcher,
- dialogTransitionAnimator,
- it.getArgument(0),
- )
- }
+ whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java), any()))
+ .thenAnswer {
+ SystemUIDialog(
+ mContext,
+ 0,
+ SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ dialogManager,
+ sysuiState,
+ fakeBroadcastDispatcher,
+ dialogTransitionAnimator,
+ it.getArgument(0),
+ )
+ }
icon = Pair(drawable, DEVICE_NAME)
deviceItem =
@@ -194,20 +190,29 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
@Test
fun testDeviceItemViewHolder_cachedDeviceNotBusy() {
- deviceItem.isEnabled = true
-
- val view =
- LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
- val viewHolder =
- mBluetoothTileDialogDelegate
- .Adapter(bluetoothTileDialogCallback)
- .DeviceItemViewHolder(view)
- viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
- val container = view.requireViewById<View>(R.id.bluetooth_device_row)
-
- assertThat(container).isNotNull()
- assertThat(container.isEnabled).isTrue()
- assertThat(container.hasOnClickListeners()).isTrue()
+ testScope.runTest {
+ deviceItem.isEnabled = true
+
+ val view =
+ LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
+ val viewHolder = mBluetoothTileDialogDelegate.Adapter().DeviceItemViewHolder(view)
+ viewHolder.bind(deviceItem)
+ val container = view.requireViewById<View>(R.id.bluetooth_device_row)
+
+ assertThat(container).isNotNull()
+ assertThat(container.isEnabled).isTrue()
+ assertThat(container.hasOnClickListeners()).isTrue()
+ val value by collectLastValue(mBluetoothTileDialogDelegate.deviceItemClick)
+ runCurrent()
+ container.performClick()
+ runCurrent()
+ assertThat(value).isNotNull()
+ value?.let {
+ assertThat(it.target).isEqualTo(DeviceItemClick.Target.ENTIRE_ROW)
+ assertThat(it.clickedView).isEqualTo(container)
+ assertThat(it.deviceItem).isEqualTo(deviceItem)
+ }
+ }
}
@Test
@@ -229,9 +234,9 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
sysuiDialogFactory,
kosmos.shadeDialogContextInteractor,
)
- .Adapter(bluetoothTileDialogCallback)
+ .Adapter()
.DeviceItemViewHolder(view)
- viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
+ viewHolder.bind(deviceItem)
val container = view.requireViewById<View>(R.id.bluetooth_device_row)
assertThat(container).isNotNull()
@@ -240,6 +245,32 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
}
@Test
+ fun testDeviceItemViewHolder_clickActionIcon() {
+ testScope.runTest {
+ deviceItem.isEnabled = true
+
+ val view =
+ LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
+ val viewHolder = mBluetoothTileDialogDelegate.Adapter().DeviceItemViewHolder(view)
+ viewHolder.bind(deviceItem)
+ val actionIconView = view.requireViewById<View>(R.id.gear_icon)
+
+ assertThat(actionIconView).isNotNull()
+ assertThat(actionIconView.hasOnClickListeners()).isTrue()
+ val value by collectLastValue(mBluetoothTileDialogDelegate.deviceItemClick)
+ runCurrent()
+ actionIconView.performClick()
+ runCurrent()
+ assertThat(value).isNotNull()
+ value?.let {
+ assertThat(it.target).isEqualTo(DeviceItemClick.Target.ACTION_ICON)
+ assertThat(it.clickedView).isEqualTo(actionIconView)
+ assertThat(it.deviceItem).isEqualTo(deviceItem)
+ }
+ }
+ }
+
+ @Test
fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
testScope.runTest {
val dialog = mBluetoothTileDialogDelegate.createDialog()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 5bf15137b834..0aa5199cb20e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -118,6 +118,7 @@ class DeviceItemFactoryTest : SysuiTestCase() {
.isEqualTo(DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
assertThat(deviceItem.cachedBluetoothDevice).isEqualTo(cachedDevice)
assertThat(deviceItem.deviceName).isEqualTo(DEVICE_NAME)
+ assertThat(deviceItem.actionIconRes).isEqualTo(R.drawable.ic_add)
assertThat(deviceItem.isActive).isFalse()
assertThat(deviceItem.connectionSummary)
.isEqualTo(
@@ -292,6 +293,7 @@ class DeviceItemFactoryTest : SysuiTestCase() {
assertThat(deviceItem.cachedBluetoothDevice).isEqualTo(cachedDevice)
assertThat(deviceItem.deviceName).isEqualTo(DEVICE_NAME)
assertThat(deviceItem.connectionSummary).isEqualTo(CONNECTION_SUMMARY)
+ assertThat(deviceItem.actionIconRes).isEqualTo(R.drawable.ic_settings_24dp)
}
companion object {
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
index 25d1c377ecbd..7ed736158a53 100644
--- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -435,6 +435,8 @@ class FakeStatusBarService : IStatusBarService.Stub() {
override fun unbundleNotification(key: String) {}
+ override fun rebundleNotification(key: String) {}
+
companion object {
const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
const val SECONDARY_DISPLAY_ID = 2
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
index a839f17aad82..c744eacfa3f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
@@ -33,6 +33,9 @@ class FakeAudioSharingRepository : AudioSharingRepository {
var sourceAdded: Boolean = false
private set
+ var audioSharingStarted: Boolean = false
+ private set
+
private var profile: LocalBluetoothLeBroadcast? = null
override val leAudioBroadcastProfile: LocalBluetoothLeBroadcast?
@@ -50,7 +53,13 @@ class FakeAudioSharingRepository : AudioSharingRepository {
override suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice) {}
- override suspend fun startAudioSharing() {}
+ override suspend fun startAudioSharing() {
+ audioSharingStarted = true
+ }
+
+ override suspend fun stopAudioSharing() {
+ audioSharingStarted = false
+ }
fun setAudioSharingAvailable(available: Boolean) {
mutableAvailable = available
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 3fc60e339543..a64fc2413246 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -115,3 +115,6 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
interface FakeDisplayRepositoryModule {
@Binds fun bindFake(fake: FakeDisplayRepository): DisplayRepository
}
+
+val DisplayRepository.fake
+ get() = this as FakeDisplayRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index afe48214832f..439df543b9fb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -54,8 +54,8 @@ var Kosmos.brightnessWarningToast: BrightnessWarningToast by
* Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
* that Kosmos instance
*/
-fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) {
- testScope.runTestWithSnapshots testBody@{ this@runTest.testBody() }
+fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) = let { kosmos ->
+ testScope.runTestWithSnapshots { kosmos.testBody() }
}
fun Kosmos.runCurrent() = testScope.runCurrent()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt
index 69e215dcba6a..90897faaa6f8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProviderKosmos.kt
@@ -16,13 +16,28 @@
package com.android.systemui.statusbar.layout
+import android.content.applicationContext
+import com.android.systemui.SysUICutoutProvider
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.fake
import org.mockito.kotlin.mock
val Kosmos.mockStatusBarContentInsetsProvider by
Kosmos.Fixture { mock<StatusBarContentInsetsProvider>() }
-var Kosmos.statusBarContentInsetsProvider by Kosmos.Fixture { mockStatusBarContentInsetsProvider }
+val Kosmos.statusBarContentInsetsProvider by
+ Kosmos.Fixture {
+ StatusBarContentInsetsProviderImpl(
+ applicationContext,
+ configurationController.fake,
+ dumpManager,
+ commandRegistry,
+ mock<SysUICutoutProvider>(),
+ )
+ }
val Kosmos.fakeStatusBarContentInsetsProviderFactory by
Kosmos.Fixture { FakeStatusBarContentInsetsProviderFactory() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelKosmos.kt
new file mode 100644
index 000000000000..889d469489f2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.layout.ui.viewmodel
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.multiDisplayStatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.layout.statusBarContentInsetsProvider
+
+val Kosmos.statusBarContentInsetsViewModel by
+ Kosmos.Fixture { StatusBarContentInsetsViewModel(statusBarContentInsetsProvider) }
+
+val Kosmos.multiDisplayStatusBarContentInsetsViewModelStore by
+ Kosmos.Fixture {
+ MultiDisplayStatusBarContentInsetsViewModelStore(
+ applicationCoroutineScope,
+ displayRepository,
+ multiDisplayStatusBarContentInsetsProviderStore,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index 5db0d5a25d83..db7e31bb2cb6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -27,6 +27,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModel
+import com.android.systemui.statusbar.layout.ui.viewmodel.multiDisplayStatusBarContentInsetsViewModelStore
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.phone.domain.interactor.darkIconInteractor
@@ -53,6 +54,7 @@ var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
ongoingActivityChipsViewModel,
statusBarPopupChipsViewModel,
systemStatusEventAnimationInteractor,
+ multiDisplayStatusBarContentInsetsViewModelStore,
applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 282f5947636c..1e4701333857 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -25,3 +25,6 @@ val Kosmos.fakeConfigurationController: FakeConfigurationController by
Kosmos.Fixture { FakeConfigurationController() }
val Kosmos.statusBarConfigurationController: StatusBarConfigurationController by
Kosmos.Fixture { fakeConfigurationController }
+
+val ConfigurationController.fake
+ get() = this as FakeConfigurationController
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt
index 1ba5ddbf0337..fc0c92e974f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryKosmos.kt
@@ -20,5 +20,5 @@ import com.android.settingslib.notification.data.repository.FakeZenModeRepositor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.zenModeRepository by Fixture { fakeZenModeRepository }
+var Kosmos.zenModeRepository by Fixture { fakeZenModeRepository }
val Kosmos.fakeZenModeRepository by Fixture { FakeZenModeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index ed5322ed098e..db19d6ee9077 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -39,7 +39,7 @@ val Kosmos.localMediaRepositoryFactory by
val Kosmos.mediaOutputActionsInteractor by
Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogManager) }
-val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
+var Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
val Kosmos.mediaOutputInteractor by
Kosmos.Fixture {
MediaOutputInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
index 712ec41bbf2d..3f2b47948c1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/AudioRepositoryKosmos.kt
@@ -19,4 +19,4 @@ package com.android.systemui.volume.data.repository
import com.android.systemui.kosmos.Kosmos
val Kosmos.fakeAudioRepository by Kosmos.Fixture { FakeAudioRepository() }
-val Kosmos.audioRepository by Kosmos.Fixture { fakeAudioRepository }
+var Kosmos.audioRepository by Kosmos.Fixture { fakeAudioRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/VolumeDialogKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/VolumeDialogKosmos.kt
new file mode 100644
index 000000000000..e2431934bc40
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/VolumeDialogKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.dialog
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.dagger.volumeDialogComponentFactory
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+
+val Kosmos.volumeDialog by
+ Kosmos.Fixture {
+ VolumeDialog(
+ context = applicationContext,
+ visibilityInteractor = volumeDialogVisibilityInteractor,
+ componentFactory = volumeDialogComponentFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponentKosmos.kt
new file mode 100644
index 000000000000..73e5d8d40985
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponentKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.dialog.dagger
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
+import com.android.systemui.volume.dialog.sliders.dagger.volumeDialogSliderComponentFactory
+import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
+import com.android.systemui.volume.dialog.ui.binder.volumeDialogViewBinder
+import kotlinx.coroutines.CoroutineScope
+
+val Kosmos.volumeDialogComponentFactory by
+ Kosmos.Fixture {
+ object : VolumeDialogComponent.Factory {
+ override fun create(scope: CoroutineScope): VolumeDialogComponent =
+ volumeDialogComponent
+ }
+ }
+val Kosmos.volumeDialogComponent by
+ Kosmos.Fixture {
+ object : VolumeDialogComponent {
+ override fun volumeDialogViewBinder(): VolumeDialogViewBinder = volumeDialogViewBinder
+
+ override fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory =
+ volumeDialogSliderComponentFactory
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt
index 291dfc0430e2..3d5698b193e1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt
@@ -19,4 +19,4 @@ package com.android.systemui.volume.dialog.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.volume.dialog.data.VolumeDialogVisibilityRepository
-val Kosmos.volumeDialogVisibilityRepository by Kosmos.Fixture { VolumeDialogVisibilityRepository() }
+var Kosmos.volumeDialogVisibilityRepository by Kosmos.Fixture { VolumeDialogVisibilityRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
index db9c48d9be6f..8f122b57e9d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorKosmos.kt
@@ -16,8 +16,6 @@
package com.android.systemui.volume.dialog.domain.interactor
-import android.os.Handler
-import android.os.looper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
@@ -27,6 +25,6 @@ val Kosmos.volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor by
VolumeDialogCallbacksInteractor(
volumeDialogController = volumeDialogController,
coroutineScope = applicationCoroutineScope,
- bgHandler = Handler(looper),
+ bgHandler = null,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/VolumeDialogRingerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/VolumeDialogRingerViewBinderKosmos.kt
new file mode 100644
index 000000000000..7cbdc3d9f6ee
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/VolumeDialogRingerViewBinderKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.dialog.ringer
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.volumeDialogRingerDrawerViewModel
+
+val Kosmos.volumeDialogRingerViewBinder by
+ Kosmos.Fixture { VolumeDialogRingerViewBinder(volumeDialogRingerDrawerViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
index 44371b4615df..cf357b498621 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
@@ -20,5 +20,5 @@ import com.android.systemui.kosmos.Kosmos
val Kosmos.fakeVolumeDialogRingerFeedbackRepository by
Kosmos.Fixture { FakeVolumeDialogRingerFeedbackRepository() }
-val Kosmos.volumeDialogRingerFeedbackRepository by
+var Kosmos.volumeDialogRingerFeedbackRepository: VolumeDialogRingerFeedbackRepository by
Kosmos.Fixture { fakeVolumeDialogRingerFeedbackRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
index a494d04ec741..4bebf8911613 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
@@ -21,7 +21,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
import com.android.systemui.volume.data.repository.audioSystemRepository
import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
-import com.android.systemui.volume.dialog.ringer.data.repository.fakeVolumeDialogRingerFeedbackRepository
+import com.android.systemui.volume.dialog.ringer.data.repository.volumeDialogRingerFeedbackRepository
val Kosmos.volumeDialogRingerInteractor by
Kosmos.Fixture {
@@ -30,6 +30,6 @@ val Kosmos.volumeDialogRingerInteractor by
volumeDialogStateInteractor = volumeDialogStateInteractor,
controller = volumeDialogController,
audioSystemRepository = audioSystemRepository,
- ringerFeedbackRepository = fakeVolumeDialogRingerFeedbackRepository,
+ ringerFeedbackRepository = volumeDialogRingerFeedbackRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractorKosmos.kt
new file mode 100644
index 000000000000..26b8bca6344b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.settings.domain
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor
+
+val Kosmos.volumeDialogSettingsButtonInteractor by
+ Kosmos.Fixture {
+ VolumeDialogSettingsButtonInteractor(
+ applicationCoroutineScope,
+ deviceProvisionedController,
+ volumePanelGlobalStateInteractor,
+ volumeDialogVisibilityInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinderKosmos.kt
new file mode 100644
index 000000000000..f9e128ddd810
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinderKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.dialog.settings.ui.binder
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.settings.ui.viewmodel.volumeDialogSettingsButtonViewModel
+
+val Kosmos.volumeDialogSettingsButtonViewBinder by
+ Kosmos.Fixture { VolumeDialogSettingsButtonViewBinder(volumeDialogSettingsButtonViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModelKosmos.kt
new file mode 100644
index 000000000000..0ae3b037b50a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.dialog.settings.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.dialog.settings.domain.volumeDialogSettingsButtonInteractor
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+
+val Kosmos.volumeDialogSettingsButtonViewModel by
+ Kosmos.Fixture {
+ VolumeDialogSettingsButtonViewModel(
+ applicationContext,
+ testScope.testScheduler,
+ applicationCoroutineScope,
+ mediaOutputInteractor,
+ mediaDeviceSessionInteractor,
+ volumeDialogSettingsButtonInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
new file mode 100644
index 000000000000..4f79f7b4b41a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.dialog.sliders.dagger
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.volumeDialogOverscrollViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderHapticsViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderViewBinder
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor
+
+private val Kosmos.mutableSliderComponentKosmoses: MutableMap<VolumeDialogSliderType, Kosmos> by
+ Kosmos.Fixture { mutableMapOf() }
+
+val Kosmos.volumeDialogSliderComponentFactory by
+ Kosmos.Fixture {
+ object : VolumeDialogSliderComponent.Factory {
+ override fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderComponent =
+ volumeDialogSliderComponent(sliderType)
+ }
+ }
+
+fun Kosmos.volumeDialogSliderComponent(type: VolumeDialogSliderType): VolumeDialogSliderComponent {
+ return object : VolumeDialogSliderComponent {
+
+ private val localKosmos
+ get() =
+ mutableSliderComponentKosmoses.getOrPut(type) {
+ Kosmos().also {
+ it.setupVolumeDialogSliderComponent(this@volumeDialogSliderComponent, type)
+ }
+ }
+
+ override fun sliderViewBinder(): VolumeDialogSliderViewBinder =
+ localKosmos.volumeDialogSliderViewBinder
+
+ override fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder =
+ localKosmos.volumeDialogSliderHapticsViewBinder
+
+ override fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder =
+ localKosmos.volumeDialogOverscrollViewBinder
+ }
+}
+
+private fun Kosmos.setupVolumeDialogSliderComponent(
+ parentKosmos: Kosmos,
+ type: VolumeDialogSliderType,
+) {
+ volumeDialogSliderType = type
+ applicationContext = parentKosmos.applicationContext
+ testScope = parentKosmos.testScope
+
+ volumeDialogController = parentKosmos.volumeDialogController
+ mediaControllerInteractor = parentKosmos.mediaControllerInteractor
+ mediaControllerRepository = parentKosmos.mediaControllerRepository
+ zenModeRepository = parentKosmos.zenModeRepository
+ volumeDialogVisibilityRepository = parentKosmos.volumeDialogVisibilityRepository
+ audioRepository = parentKosmos.audioRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinderKosmos.kt
new file mode 100644
index 000000000000..13d6ca9732d1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinderKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.dialog.sliders.ui
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogOverscrollViewModel
+
+val Kosmos.volumeDialogOverscrollViewBinder by
+ Kosmos.Fixture { VolumeDialogOverscrollViewBinder(volumeDialogOverscrollViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt
new file mode 100644
index 000000000000..d6845b1ff7e3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui
+
+import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.systemClock
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel
+
+val Kosmos.volumeDialogSliderHapticsViewBinder by
+ Kosmos.Fixture {
+ VolumeDialogSliderHapticsViewBinder(
+ volumeDialogSliderInputEventsViewModel,
+ vibratorHelper,
+ msdlPlayer,
+ systemClock,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt
new file mode 100644
index 000000000000..c6db717e004f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.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.dialog.sliders.ui
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderViewModel
+
+val Kosmos.volumeDialogSliderViewBinder by
+ Kosmos.Fixture {
+ VolumeDialogSliderViewBinder(
+ volumeDialogSliderViewModel,
+ volumeDialogSliderInputEventsViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinderKosmos.kt
new file mode 100644
index 000000000000..83527d994e70
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinderKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.dialog.sliders.ui
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSlidersViewModel
+
+val Kosmos.volumeDialogSlidersViewBinder by
+ Kosmos.Fixture { VolumeDialogSlidersViewBinder(volumeDialogSlidersViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModelKosmos.kt
new file mode 100644
index 000000000000..fe2f3d806b6a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModelKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor
+
+val Kosmos.volumeDialogOverscrollViewModel by
+ Kosmos.Fixture {
+ VolumeDialogOverscrollViewModel(applicationContext, volumeDialogSliderInputEventsInteractor)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
new file mode 100644
index 000000000000..09f9f1c6362e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.dialog.sliders.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
+
+val Kosmos.volumeDialogSliderIconProvider by
+ Kosmos.Fixture {
+ VolumeDialogSliderIconProvider(
+ context = applicationContext,
+ audioVolumeInteractor = audioVolumeInteractor,
+ zenModeInteractor = zenModeInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt
new file mode 100644
index 000000000000..2de0e8f76a4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.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.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor
+
+val Kosmos.volumeDialogSliderInputEventsViewModel by
+ Kosmos.Fixture {
+ VolumeDialogSliderInputEventsViewModel(
+ applicationCoroutineScope,
+ volumeDialogSliderInputEventsInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
new file mode 100644
index 000000000000..63cd440a8633
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.time.systemClock
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInteractor
+
+val Kosmos.volumeDialogSliderViewModel by
+ Kosmos.Fixture {
+ VolumeDialogSliderViewModel(
+ interactor = volumeDialogSliderInteractor,
+ visibilityInteractor = volumeDialogVisibilityInteractor,
+ coroutineScope = applicationCoroutineScope,
+ volumeDialogSliderIconProvider = volumeDialogSliderIconProvider,
+ systemClock = systemClock,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
new file mode 100644
index 000000000000..5531f7608b69
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.dialog.sliders.dagger.volumeDialogSliderComponentFactory
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSlidersInteractor
+
+val Kosmos.volumeDialogSlidersViewModel by
+ Kosmos.Fixture {
+ VolumeDialogSlidersViewModel(
+ applicationCoroutineScope,
+ volumeDialogSlidersInteractor,
+ volumeDialogSliderComponentFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
new file mode 100644
index 000000000000..dc09e3233b1e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinderKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.dialog.ui.binder
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.ringer.volumeDialogRingerViewBinder
+import com.android.systemui.volume.dialog.settings.ui.binder.volumeDialogSettingsButtonViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSlidersViewBinder
+import com.android.systemui.volume.dialog.ui.utils.jankListenerFactory
+import com.android.systemui.volume.dialog.ui.viewmodel.volumeDialogViewModel
+import com.android.systemui.volume.dialog.utils.volumeTracer
+
+val Kosmos.volumeDialogViewBinder by
+ Kosmos.Fixture {
+ VolumeDialogViewBinder(
+ applicationContext.resources,
+ volumeDialogViewModel,
+ jankListenerFactory,
+ volumeTracer,
+ volumeDialogRingerViewBinder,
+ volumeDialogSlidersViewBinder,
+ volumeDialogSettingsButtonViewBinder,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactoryKosmos.kt
new file mode 100644
index 000000000000..35ec5d3cc9af
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.dialog.ui.utils
+
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.jankListenerFactory by Kosmos.Fixture { JankListenerFactory(interactionJankMonitor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModelKosmos.kt
new file mode 100644
index 000000000000..05ef462d4998
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.dialog.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.devicePostureController
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSlidersInteractor
+
+val Kosmos.volumeDialogViewModel by
+ Kosmos.Fixture {
+ VolumeDialogViewModel(
+ applicationContext,
+ volumeDialogVisibilityInteractor,
+ volumeDialogSlidersInteractor,
+ volumeDialogStateInteractor,
+ devicePostureController,
+ configurationController,
+ )
+ }
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index 1370b0678cc5..97574e6e35e3 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -39,9 +39,7 @@ java_library {
name: "connectivity-utils-service-vcn-internal",
sdk_version: "module_current",
min_sdk_version: "30",
- srcs: [
- ":framework-connectivity-shared-srcs",
- ],
+ srcs: ["service-utils/**/*.java"],
libs: [
"framework-annotations-lib",
"unsupportedappusage",
diff --git a/packages/Vcn/service-b/service-utils/android/util/LocalLog.java b/packages/Vcn/service-b/service-utils/android/util/LocalLog.java
new file mode 100644
index 000000000000..5955d930aab1
--- /dev/null
+++ b/packages/Vcn/service-b/service-utils/android/util/LocalLog.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.SystemClock;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+/**
+ * @hide
+ */
+// Exported to Mainline modules; cannot use annotations
+// @android.ravenwood.annotation.RavenwoodKeepWholeClass
+// TODO: b/374174952 This is an exact copy of frameworks/base/core/java/android/util/LocalLog.java.
+// This file is only used in "service-connectivity-b-platform" before the VCN modularization flag
+// is fully ramped. When the flag is fully ramped and the development is finalized, this file can
+// be removed.
+public final class LocalLog {
+
+ private final Deque<String> mLog;
+ private final int mMaxLines;
+
+ /**
+ * {@code true} to use log timestamps expressed in local date/time, {@code false} to use log
+ * timestamped expressed with the elapsed realtime clock and UTC system clock. {@code false} is
+ * useful when logging behavior that modifies device time zone or system clock.
+ */
+ private final boolean mUseLocalTimestamps;
+
+ @UnsupportedAppUsage
+ public LocalLog(int maxLines) {
+ this(maxLines, true /* useLocalTimestamps */);
+ }
+
+ public LocalLog(int maxLines, boolean useLocalTimestamps) {
+ mMaxLines = Math.max(0, maxLines);
+ mLog = new ArrayDeque<>(mMaxLines);
+ mUseLocalTimestamps = useLocalTimestamps;
+ }
+
+ @UnsupportedAppUsage
+ public void log(String msg) {
+ if (mMaxLines <= 0) {
+ return;
+ }
+ final String logLine;
+ if (mUseLocalTimestamps) {
+ logLine = LocalDateTime.now() + " - " + msg;
+ } else {
+ logLine = Duration.ofMillis(SystemClock.elapsedRealtime())
+ + " / " + Instant.now() + " - " + msg;
+ }
+ append(logLine);
+ }
+
+ private synchronized void append(String logLine) {
+ while (mLog.size() >= mMaxLines) {
+ mLog.remove();
+ }
+ mLog.add(logLine);
+ }
+
+ @UnsupportedAppUsage
+ public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ dump(pw);
+ }
+
+ public synchronized void dump(PrintWriter pw) {
+ dump("", pw);
+ }
+
+ /**
+ * Dumps the content of local log to print writer with each log entry predeced with indent
+ *
+ * @param indent indent that precedes each log entry
+ * @param pw printer writer to write into
+ */
+ public synchronized void dump(String indent, PrintWriter pw) {
+ Iterator<String> itr = mLog.iterator();
+ while (itr.hasNext()) {
+ pw.printf("%s%s\n", indent, itr.next());
+ }
+ }
+
+ public synchronized void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ reverseDump(pw);
+ }
+
+ public synchronized void reverseDump(PrintWriter pw) {
+ Iterator<String> itr = mLog.descendingIterator();
+ while (itr.hasNext()) {
+ pw.println(itr.next());
+ }
+ }
+
+ // @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public synchronized void clear() {
+ mLog.clear();
+ }
+
+ public static class ReadOnlyLocalLog {
+ private final LocalLog mLog;
+ ReadOnlyLocalLog(LocalLog log) {
+ mLog = log;
+ }
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mLog.dump(pw);
+ }
+ public void dump(PrintWriter pw) {
+ mLog.dump(pw);
+ }
+ public void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mLog.reverseDump(pw);
+ }
+ public void reverseDump(PrintWriter pw) {
+ mLog.reverseDump(pw);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public ReadOnlyLocalLog readOnlyLocalLog() {
+ return new ReadOnlyLocalLog(this);
+ }
+} \ No newline at end of file
diff --git a/packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java b/packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java
new file mode 100644
index 000000000000..7db62f8e9ffc
--- /dev/null
+++ b/packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+ /**
+ * An AlarmListener that sends the specified message to a Handler and keeps the system awake until
+ * the message is processed.
+ *
+ * This is useful when using the AlarmManager direct callback interface to wake up the system and
+ * request that an object whose API consists of messages (such as a StateMachine) perform some
+ * action.
+ *
+ * In this situation, using AlarmManager.onAlarmListener by itself will wake up the system to send
+ * the message, but does not guarantee that the system will be awake until the target object has
+ * processed it. This is because as soon as the onAlarmListener sends the message and returns, the
+ * AlarmManager releases its wakelock and the system is free to go to sleep again.
+ */
+// TODO: b/374174952 This is an exact copy of
+// frameworks/base/core/java/com/android/internal/util/WakeupMessage.java.
+// This file is only used in "service-connectivity-b-platform" before the VCN modularization flag
+// is fully ramped. When the flag is fully ramped and the development is finalized, this file can
+// be removed.
+public class WakeupMessage implements AlarmManager.OnAlarmListener {
+ private final AlarmManager mAlarmManager;
+
+ @VisibleForTesting
+ protected final Handler mHandler;
+ @VisibleForTesting
+ protected final String mCmdName;
+ @VisibleForTesting
+ protected final int mCmd, mArg1, mArg2;
+ @VisibleForTesting
+ protected final Object mObj;
+ private final Runnable mRunnable;
+ private boolean mScheduled;
+
+ public WakeupMessage(Context context, Handler handler,
+ String cmdName, int cmd, int arg1, int arg2, Object obj) {
+ mAlarmManager = getAlarmManager(context);
+ mHandler = handler;
+ mCmdName = cmdName;
+ mCmd = cmd;
+ mArg1 = arg1;
+ mArg2 = arg2;
+ mObj = obj;
+ mRunnable = null;
+ }
+
+ public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
+ this(context, handler, cmdName, cmd, arg1, 0, null);
+ }
+
+ public WakeupMessage(Context context, Handler handler,
+ String cmdName, int cmd, int arg1, int arg2) {
+ this(context, handler, cmdName, cmd, arg1, arg2, null);
+ }
+
+ public WakeupMessage(Context context, Handler handler, String cmdName, int cmd) {
+ this(context, handler, cmdName, cmd, 0, 0, null);
+ }
+
+ public WakeupMessage(Context context, Handler handler, String cmdName, Runnable runnable) {
+ mAlarmManager = getAlarmManager(context);
+ mHandler = handler;
+ mCmdName = cmdName;
+ mCmd = 0;
+ mArg1 = 0;
+ mArg2 = 0;
+ mObj = null;
+ mRunnable = runnable;
+ }
+
+ private static AlarmManager getAlarmManager(Context context) {
+ return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ }
+
+ /**
+ * Schedule the message to be delivered at the time in milliseconds of the
+ * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
+ * the device when it goes off. If schedule is called multiple times without the message being
+ * dispatched then the alarm is rescheduled to the new time.
+ */
+ public synchronized void schedule(long when) {
+ mAlarmManager.setExact(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP, when, mCmdName, this, mHandler);
+ mScheduled = true;
+ }
+
+ /**
+ * Cancel all pending messages. This includes alarms that may have been fired, but have not been
+ * run on the handler yet.
+ */
+ public synchronized void cancel() {
+ if (mScheduled) {
+ mAlarmManager.cancel(this);
+ mScheduled = false;
+ }
+ }
+
+ @Override
+ public void onAlarm() {
+ // Once this method is called the alarm has already been fired and removed from
+ // AlarmManager (it is still partially tracked, but only for statistics). The alarm can now
+ // be marked as unscheduled so that it can be rescheduled in the message handler.
+ final boolean stillScheduled;
+ synchronized (this) {
+ stillScheduled = mScheduled;
+ mScheduled = false;
+ }
+ if (stillScheduled) {
+ Message msg;
+ if (mRunnable == null) {
+ msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+ } else {
+ msg = Message.obtain(mHandler, mRunnable);
+ }
+ mHandler.dispatchMessage(msg);
+ msg.recycle();
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt b/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt
index 36307277b4b9..6ec39d953266 100644
--- a/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt
+++ b/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt
@@ -1,5 +1,2 @@
-rule android.util.IndentingPrintWriter android.net.vcn.module.repackaged.android.util.IndentingPrintWriter
rule android.util.LocalLog android.net.vcn.module.repackaged.android.util.LocalLog
-rule com.android.internal.util.IndentingPrintWriter android.net.vcn.module.repackaged.com.android.internal.util.IndentingPrintWriter
-rule com.android.internal.util.MessageUtils android.net.vcn.module.repackaged.com.android.internal.util.MessageUtils
rule com.android.internal.util.WakeupMessage android.net.vcn.module.repackaged.com.android.internal.util.WakeupMessage \ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index aeb2f5e9be84..40726b4331e2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -767,7 +767,7 @@ public class VirtualDeviceManagerService extends SystemService {
params,
/* activityListener= */ null,
/* soundEffectListener= */ null);
- return new VirtualDeviceManager.VirtualDevice(mImpl, getContext(), virtualDevice);
+ return new VirtualDeviceManager.VirtualDevice(getContext(), virtualDevice);
}
@Override
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3f6484f0f58e..00d23cc6d298 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7214,7 +7214,7 @@ public class AudioService extends IAudioService.Stub
final int pid = Binder.getCallingPid();
final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
.append(") from u/pid:").append(uid).append("/")
- .append(pid).toString();
+ .append(pid).append(" src:AudioService.setBtA2dpOn").toString();
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
+ MediaMetrics.SEPARATOR + "setBluetoothA2dpOn")
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 286238e7888c..0d0cdd83cc73 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -438,9 +438,9 @@ public class LockSettingsService extends ILockSettings.Stub {
}
LockscreenCredential credential =
LockscreenCredential.createUnifiedProfilePassword(newPassword);
- Arrays.fill(newPasswordChars, '\u0000');
- Arrays.fill(newPassword, (byte) 0);
- Arrays.fill(randomLockSeed, (byte) 0);
+ LockPatternUtils.zeroize(newPasswordChars);
+ LockPatternUtils.zeroize(newPassword);
+ LockPatternUtils.zeroize(randomLockSeed);
return credential;
}
@@ -1537,7 +1537,7 @@ public class LockSettingsService extends ILockSettings.Stub {
+ userId);
}
} finally {
- Arrays.fill(password, (byte) 0);
+ LockPatternUtils.zeroize(password);
}
}
@@ -1570,7 +1570,7 @@ public class LockSettingsService extends ILockSettings.Stub {
decryptionResult = cipher.doFinal(encryptedPassword);
LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword(
decryptionResult);
- Arrays.fill(decryptionResult, (byte) 0);
+ LockPatternUtils.zeroize(decryptionResult);
try {
long parentSid = getGateKeeperService().getSecureUserId(
mUserManager.getProfileParent(userId).id);
@@ -2263,7 +2263,7 @@ public class LockSettingsService extends ILockSettings.Stub {
} catch (RemoteException e) {
Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId);
} finally {
- Arrays.fill(secret, (byte) 0);
+ LockPatternUtils.zeroize(secret);
}
}
diff --git a/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
index 21caf76d30d0..3d64f1890073 100644
--- a/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
@@ -26,6 +26,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import java.security.GeneralSecurityException;
@@ -154,7 +155,7 @@ public class UnifiedProfilePasswordCache {
}
LockscreenCredential result =
LockscreenCredential.createUnifiedProfilePassword(credential);
- Arrays.fill(credential, (byte) 0);
+ LockPatternUtils.zeroize(credential);
return result;
}
}
@@ -175,7 +176,7 @@ public class UnifiedProfilePasswordCache {
Slog.d(TAG, "Cannot delete key", e);
}
if (mEncryptedPasswords.contains(userId)) {
- Arrays.fill(mEncryptedPasswords.get(userId), (byte) 0);
+ LockPatternUtils.zeroize(mEncryptedPasswords.get(userId));
mEncryptedPasswords.remove(userId);
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index bf1b3c3f0b35..85dc811a7811 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -162,7 +162,7 @@ public class KeySyncTask implements Runnable {
Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
} finally {
if (mCredential != null) {
- Arrays.fill(mCredential, (byte) 0); // no longer needed.
+ LockPatternUtils.zeroize(mCredential); // no longer needed.
}
}
}
@@ -506,7 +506,7 @@ public class KeySyncTask implements Runnable {
try {
byte[] hash = MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
- Arrays.fill(bytes, (byte) 0);
+ LockPatternUtils.zeroize(bytes);
return hash;
} catch (NoSuchAlgorithmException e) {
// Impossible, SHA-256 must be supported on Android.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 54303c01890a..7d8300a8148a 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -1082,7 +1082,7 @@ public class RecoverableKeyStoreManager {
int keyguardCredentialsType = lockPatternUtilsToKeyguardType(savedCredentialType);
try (LockscreenCredential credential =
createLockscreenCredential(keyguardCredentialsType, decryptedCredentials)) {
- Arrays.fill(decryptedCredentials, (byte) 0);
+ LockPatternUtils.zeroize(decryptedCredentials);
decryptedCredentials = null;
VerifyCredentialResponse verifyResponse =
lockSettingsService.verifyCredential(credential, userId, 0);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
index 0e66746f4160..f1ef333d223a 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -19,8 +19,9 @@ package com.android.server.locksettings.recoverablekeystore.storage;
import android.annotation.Nullable;
import android.util.SparseArray;
+import com.android.internal.widget.LockPatternUtils;
+
import java.util.ArrayList;
-import java.util.Arrays;
import javax.security.auth.Destroyable;
@@ -187,8 +188,8 @@ public class RecoverySessionStorage implements Destroyable {
*/
@Override
public void destroy() {
- Arrays.fill(mLskfHash, (byte) 0);
- Arrays.fill(mKeyClaimant, (byte) 0);
+ LockPatternUtils.zeroize(mLskfHash);
+ LockPatternUtils.zeroize(mKeyClaimant);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 68e195d7f079..35bb19943a24 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -302,7 +302,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub
final long token = Binder.clearCallingIdentity();
try {
- mAudioService.setBluetoothA2dpOn(on);
+ if (!Flags.disableSetBluetoothAd2pOnCalls()) {
+ mAudioService.setBluetoothA2dpOn(on);
+ }
} catch (RemoteException ex) {
Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn. on=" + on);
} finally {
@@ -677,7 +679,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub
if (DEBUG) {
Slog.d(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
}
- mAudioService.setBluetoothA2dpOn(a2dpOn);
+ if (!Flags.disableSetBluetoothAd2pOnCalls()) {
+ mAudioService.setBluetoothA2dpOn(a2dpOn);
+ }
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 4b41696a4390..e47f8ae9d3a5 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -583,6 +583,15 @@ public class GroupHelper {
final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(
record.getUserId(), pkgName, sectioner);
+ // The notification was part of a different section => trigger regrouping
+ final FullyQualifiedGroupKey prevSectionKey = getPreviousValidSectionKey(record);
+ if (prevSectionKey != null && !fullAggregateGroupKey.equals(prevSectionKey)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Section changed for: " + record);
+ }
+ maybeUngroupOnSectionChanged(record, prevSectionKey);
+ }
+
// This notification is already aggregated
if (record.getGroupKey().equals(fullAggregateGroupKey.toString())) {
return false;
@@ -652,10 +661,33 @@ public class GroupHelper {
}
/**
+ * A notification was added that was previously part of a different section and needs to trigger
+ * GH state cleanup.
+ */
+ private void maybeUngroupOnSectionChanged(NotificationRecord record,
+ FullyQualifiedGroupKey prevSectionKey) {
+ maybeUngroupWithSections(record, prevSectionKey);
+ if (record.getGroupKey().equals(prevSectionKey.toString())) {
+ record.setOverrideGroupKey(null);
+ }
+ }
+
+ /**
* A notification was added that is app-grouped.
*/
private void maybeUngroupOnAppGrouped(NotificationRecord record) {
- maybeUngroupWithSections(record, getSectionGroupKeyWithFallback(record));
+ FullyQualifiedGroupKey currentSectionKey = getSectionGroupKeyWithFallback(record);
+
+ // The notification was part of a different section => trigger regrouping
+ final FullyQualifiedGroupKey prevSectionKey = getPreviousValidSectionKey(record);
+ if (prevSectionKey != null && !prevSectionKey.equals(currentSectionKey)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Section changed for: " + record);
+ }
+ currentSectionKey = prevSectionKey;
+ }
+
+ maybeUngroupWithSections(record, currentSectionKey);
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 7cbbe2938fd5..5a425057ea89 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -107,4 +107,9 @@ public interface NotificationDelegate {
* @param key the notification key
*/
void unbundleNotification(String key);
+ /**
+ * Called when the notification should be rebundled.
+ * @param key the notification key
+ */
+ void rebundleNotification(String key);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index dd9741ce9ca1..341038f878d9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1888,6 +1888,36 @@ public class NotificationManagerService extends SystemService {
}
}
}
+
+ @Override
+ public void rebundleNotification(String key) {
+ if (!(notificationClassification() && notificationRegroupOnClassification())) {
+ return;
+ }
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r == null) {
+ return;
+ }
+
+ if (DBG) {
+ Slog.v(TAG, "rebundleNotification: " + r);
+ }
+
+ if (r.getBundleType() != Adjustment.TYPE_OTHER) {
+ final Bundle classifBundle = new Bundle();
+ classifBundle.putInt(KEY_TYPE, r.getBundleType());
+ Adjustment adj = new Adjustment(r.getSbn().getPackageName(), r.getKey(),
+ classifBundle, "rebundle", r.getUserId());
+ applyAdjustmentLocked(r, adj, /* isPosted= */ true);
+ mRankingHandler.requestSort();
+ } else {
+ if (DBG) {
+ Slog.w(TAG, "Can't rebundle. No valid bundle type for: " + r);
+ }
+ }
+ }
+ }
};
NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
@@ -7134,6 +7164,7 @@ public class NotificationManagerService extends SystemService {
adjustments.putParcelable(KEY_TYPE, newChannel);
logClassificationChannelAdjustmentReceived(r, isPosted, classification);
+ r.setBundleType(classification);
}
}
r.addAdjustment(adjustment);
@@ -9537,7 +9568,8 @@ public class NotificationManagerService extends SystemService {
|| !Objects.equals(oldSbn.getNotification().getGroup(),
n.getNotification().getGroup())
|| oldSbn.getNotification().flags
- != n.getNotification().flags) {
+ != n.getNotification().flags
+ || !old.getChannel().getId().equals(r.getChannel().getId())) {
synchronized (mNotificationLock) {
final String autogroupName =
notificationForceGrouping() ?
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0bb3c6a067e3..81af0d8a6d80 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -222,6 +222,9 @@ public final class NotificationRecord {
// lifetime extended.
private boolean mCanceledAfterLifetimeExtension = false;
+ // type of the bundle if the notification was classified
+ private @Adjustment.Types int mBundleType = Adjustment.TYPE_OTHER;
+
public NotificationRecord(Context context, StatusBarNotification sbn,
NotificationChannel channel) {
this.sbn = sbn;
@@ -467,6 +470,10 @@ public final class NotificationRecord {
}
}
+ if (android.service.notification.Flags.notificationClassification()) {
+ mBundleType = previous.mBundleType;
+ }
+
// Don't copy importance information or mGlobalSortKey, recompute them.
}
@@ -1629,6 +1636,14 @@ public final class NotificationRecord {
mCanceledAfterLifetimeExtension = canceledAfterLifetimeExtension;
}
+ public @Adjustment.Types int getBundleType() {
+ return mBundleType;
+ }
+
+ public void setBundleType(@Adjustment.Types int bundleType) {
+ mBundleType = bundleType;
+ }
+
/**
* Whether this notification is a conversation notification.
*/
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4c70d2347fb7..4cb776924ca4 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3060,6 +3060,8 @@ final class InstallPackageHelper {
}
if (succeeded) {
+ Slog.i(TAG, "installation completed:" + packageName);
+
if (Flags.aslInApkAppMetadataSource()
&& pkgSetting.getAppMetadataSource() == APP_METADATA_SOURCE_APK) {
if (!extractAppMetadataFromApk(request.getPkg(),
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
index 17739712d65a..a75d110e3cd1 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -88,5 +88,6 @@ public class ResourcesManagerShellCommand extends ShellCommand {
out.println(" Print this help text.");
out.println(" dump <PROCESS>");
out.println(" Dump the Resources objects in use as well as the history of Resources");
+
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 4ed5f90f2852..a19a3422af06 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2199,6 +2199,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
});
}
+ /**
+ * Called when the notification should be rebundled.
+ * @param key the notification key
+ */
+ @Override
+ public void rebundleNotification(String key) {
+ enforceStatusBarService();
+ enforceValidCallingUser();
+ Binder.withCleanCallingIdentity(() -> {
+ mNotificationDelegate.rebundleNotification(key);
+ });
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3d53078de3c3..1fe61590a531 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -232,6 +232,7 @@ import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
+import static com.android.server.wm.StartingData.AFTER_TRANSITION_FINISH;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
@@ -2814,9 +2815,27 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
attachStartingSurfaceToAssociatedTask();
}
+ /**
+ * If the device is locked and the app does not request showWhenLocked,
+ * defer removing the starting window until the transition is complete.
+ * This prevents briefly appearing the app context and causing secure concern.
+ */
+ void deferStartingWindowRemovalForKeyguardUnoccluding() {
+ if (mStartingData.mRemoveAfterTransaction != AFTER_TRANSITION_FINISH
+ && isKeyguardLocked() && !canShowWhenLockedInner(this) && !isVisibleRequested()
+ && mTransitionController.inTransition(this)) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSITION_FINISH;
+ }
+ }
+
void removeStartingWindow() {
boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
+ if (mStartingData != null
+ && mStartingData.mRemoveAfterTransaction == AFTER_TRANSITION_FINISH) {
+ return;
+ }
+
if (transferSplashScreenIfNeeded()) {
return;
}
@@ -4655,6 +4674,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
tStartingWindow.mToken = this;
tStartingWindow.mActivityRecord = this;
+ if (mStartingData.mRemoveAfterTransaction == AFTER_TRANSITION_FINISH) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
+ }
if (mStartingData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
// The removal of starting window should wait for window drawn of current
// activity.
@@ -8125,10 +8147,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (task != null && requestedOrientation == SCREEN_ORIENTATION_BEHIND) {
// We use Task here because we want to be consistent with what happens in
// multi-window mode where other tasks orientations are ignored.
- final ActivityRecord belowCandidate = task.getActivity(
- a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
- this /* boundary */, false /* includeBoundary */,
- true /* traverseTopToBottom */);
+ final ActivityRecord belowCandidate = task.getActivityBelowForDefiningOrientation(this);
if (belowCandidate != null) {
return belowCandidate.getRequestedConfigurationOrientation(forDisplay);
}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index d49a507c9e11..5bec4424269a 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -25,6 +25,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.window.DisplayAreaOrganizer.FEATURE_APP_ZOOM_OUT;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
@@ -151,6 +153,12 @@ public abstract class DisplayAreaPolicy {
.all()
.except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL,
TYPE_SECURE_SYSTEM_OVERLAY)
+ .build())
+ .addFeature(new Feature.Builder(wmService.mPolicy, "AppZoomOut",
+ FEATURE_APP_ZOOM_OUT)
+ .all()
+ .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL,
+ TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE, TYPE_WALLPAPER)
.build());
}
rootHierarchy
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index dd23f577e05b..acd47dad83a1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1822,7 +1822,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
private void applyFixedRotationForNonTopVisibleActivityIfNeeded(@NonNull ActivityRecord ar,
@ActivityInfo.ScreenOrientation int topOrientation) {
- final int orientation = ar.getRequestedOrientation();
+ int orientation = ar.getRequestedOrientation();
+ if (orientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+ final ActivityRecord nextCandidate = getActivityBelowForDefiningOrientation(ar);
+ if (nextCandidate != null) {
+ orientation = nextCandidate.getRequestedOrientation();
+ }
+ }
if (orientation == topOrientation || ar.inMultiWindowMode()
|| ar.getRequestedConfigurationOrientation() == ORIENTATION_UNDEFINED) {
return;
@@ -1864,9 +1870,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return ROTATION_UNDEFINED;
}
if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
- final ActivityRecord nextCandidate = getActivity(
- a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
- r /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
+ final ActivityRecord nextCandidate = getActivityBelowForDefiningOrientation(r);
if (nextCandidate != null) {
r = nextCandidate;
activityOrientation = r.getOverrideOrientation();
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 3a0e41a5f9f8..b3e9244d108d 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -45,6 +45,7 @@ import android.annotation.Nullable;
import android.content.ClipData;
import android.content.ClipDescription;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Build;
@@ -70,6 +71,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.internal.view.IDragAndDropPermissions;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
@@ -542,10 +544,26 @@ class DragState {
}
}
ClipDescription description = data != null ? data.getDescription() : mDataDescription;
+
+ // Note this can be negative numbers if touch coords are left or top of the window.
+ PointF relativeToWindowCoords = new PointF(newWin.translateToWindowX(touchX),
+ newWin.translateToWindowY(touchY));
+ if (Flags.enableConnectedDisplaysDnd()
+ && mDisplayContent.getDisplayId() != newWin.getDisplayId()) {
+ // Currently DRAG_STARTED coords are sent relative to the window target in **px**
+ // coordinates. However, this cannot be extended to connected displays scenario,
+ // as there's only global **dp** coordinates and no global **px** coordinates.
+ // Hence, the coords sent here will only try to indicate that drag started outside
+ // this window display, but relative distance should not be calculated or depended
+ // on.
+ relativeToWindowCoords = new PointF(-newWin.getBounds().left - 1,
+ -newWin.getBounds().top - 1);
+ }
+
DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
- newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
- description, data, false /* includeDragSurface */,
- true /* includeDragFlags */, null /* dragAndDropPermission */);
+ relativeToWindowCoords.x, relativeToWindowCoords.y, description, data,
+ false /* includeDragSurface */, true /* includeDragFlags */,
+ null /* dragAndDropPermission */);
try {
newWin.mClient.dispatchDragEvent(event);
// track each window that we've notified that the drag is starting
diff --git a/services/core/java/com/android/server/wm/PersisterQueue.java b/services/core/java/com/android/server/wm/PersisterQueue.java
index 9dc3d6a81338..bc16a566bfef 100644
--- a/services/core/java/com/android/server/wm/PersisterQueue.java
+++ b/services/core/java/com/android/server/wm/PersisterQueue.java
@@ -86,6 +86,34 @@ class PersisterQueue {
mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
}
+ /**
+ * Busy wait until {@link #mLazyTaskWriterThread} is in {@link Thread.State#WAITING}, or
+ * times out. This indicates the thread is waiting for new tasks to appear. If the wait
+ * succeeds, this queue waits at least {@link #mPreTaskDelayMs} milliseconds before running the
+ * next task.
+ *
+ * <p>This is for testing purposes only.
+ *
+ * @param timeoutMillis the maximum time of waiting in milliseconds
+ * @return {@code true} if the thread is in {@link Thread.State#WAITING} at return
+ */
+ @VisibleForTesting
+ boolean waitUntilWritingThreadIsWaiting(long timeoutMillis) {
+ final long timeoutTime = SystemClock.uptimeMillis() + timeoutMillis;
+ do {
+ Thread.State state;
+ synchronized (this) {
+ state = mLazyTaskWriterThread.getState();
+ }
+ if (state == Thread.State.WAITING) {
+ return true;
+ }
+ Thread.yield();
+ } while (SystemClock.uptimeMillis() < timeoutTime);
+
+ return false;
+ }
+
synchronized void startPersisting() {
if (!mLazyTaskWriterThread.isAlive()) {
mLazyTaskWriterThread.start();
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index a5454546341b..3eb13c52cca6 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -407,10 +407,8 @@ class SnapshotPersistQueue {
bitmap.recycle();
final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
- try {
- FileOutputStream fos = new FileOutputStream(file);
+ try (FileOutputStream fos = new FileOutputStream(file)) {
swBitmap.compress(JPEG, COMPRESS_QUALITY, fos);
- fos.close();
} catch (IOException e) {
Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
return false;
@@ -428,10 +426,8 @@ class SnapshotPersistQueue {
swBitmap.recycle();
final File lowResFile = mPersistInfoProvider.getLowResolutionBitmapFile(mId, mUserId);
- try {
- FileOutputStream lowResFos = new FileOutputStream(lowResFile);
+ try (FileOutputStream lowResFos = new FileOutputStream(lowResFile)) {
lowResBitmap.compress(JPEG, COMPRESS_QUALITY, lowResFos);
- lowResFos.close();
} catch (IOException e) {
Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
return false;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 7349224ddcd8..1a7a6196cf85 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -31,11 +31,18 @@ public abstract class StartingData {
static final int AFTER_TRANSACTION_REMOVE_DIRECTLY = 1;
/** Do copy splash screen to client after transaction done. */
static final int AFTER_TRANSACTION_COPY_TO_CLIENT = 2;
+ /**
+ * Remove the starting window after transition finish.
+ * Used when activity doesn't request show when locked, so the app window should never show to
+ * the user if device is locked.
+ **/
+ static final int AFTER_TRANSITION_FINISH = 3;
@IntDef(prefix = { "AFTER_TRANSACTION" }, value = {
AFTER_TRANSACTION_IDLE,
AFTER_TRANSACTION_REMOVE_DIRECTLY,
AFTER_TRANSACTION_COPY_TO_CLIENT,
+ AFTER_TRANSITION_FINISH,
})
@interface AfterTransaction {}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a9646783b92d..f4a455a9c2dd 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -70,6 +70,8 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
+import static com.android.server.wm.StartingData.AFTER_TRANSITION_FINISH;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
@@ -1374,6 +1376,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
enterAutoPip = true;
}
}
+
+ if (ar.mStartingData != null && ar.mStartingData.mRemoveAfterTransaction
+ == AFTER_TRANSITION_FINISH
+ && (!ar.isVisible() || !ar.mTransitionController.inTransition(ar))) {
+ ar.mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
+ ar.removeStartingWindow();
+ }
final ChangeInfo changeInfo = mChanges.get(ar);
// Due to transient-hide, there may be some activities here which weren't in the
// transition.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index aa60f939f9aa..54a3d4179e3d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1659,6 +1659,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return ORIENTATION_UNDEFINED;
}
+ @Nullable
+ ActivityRecord getActivityBelowForDefiningOrientation(ActivityRecord from) {
+ return getActivity(ActivityRecord::canDefineOrientationForActivitiesAbove,
+ from /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
+ }
+
/**
* Calls {@link #setOrientation(int, WindowContainer)} with {@code null} to the last 2
* parameters.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3a2a1ea419d4..d69b06ad71ea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -126,6 +126,7 @@ import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.MoveAnimationSpecProto.DURATION_MS;
import static com.android.server.wm.MoveAnimationSpecProto.FROM;
import static com.android.server.wm.MoveAnimationSpecProto.TO;
+import static com.android.server.wm.StartingData.AFTER_TRANSITION_FINISH;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
@@ -1920,6 +1921,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
final ActivityRecord atoken = mActivityRecord;
if (atoken != null) {
+ if (atoken.mStartingData != null && mAttrs.type != TYPE_APPLICATION_STARTING
+ && atoken.mStartingData.mRemoveAfterTransaction
+ == AFTER_TRANSITION_FINISH) {
+ // Preventing app window from visible during un-occluding animation playing due to
+ // alpha blending.
+ return false;
+ }
final boolean isVisible = isStartingWindowAssociatedToTask()
? mStartingData.mAssociatedTask.isVisible() : atoken.isVisible();
return ((!isParentWindowHidden() && isVisible)
@@ -2925,7 +2933,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final int mask = FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD
| FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
WindowManager.LayoutParams sa = mActivityRecord.mStartingWindow.mAttrs;
+ final boolean wasShowWhenLocked = (sa.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
+ final boolean removeShowWhenLocked = (mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) == 0;
sa.flags = (sa.flags & ~mask) | (mAttrs.flags & mask);
+ if (Flags.keepAppWindowHideWhileLocked() && wasShowWhenLocked && removeShowWhenLocked) {
+ // Trigger unoccluding animation if needed.
+ mActivityRecord.checkKeyguardFlagsChanged();
+ mActivityRecord.deferStartingWindowRemovalForKeyguardUnoccluding();
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b5777103aac8..d2d388401e23 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -14673,7 +14673,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled,
PersistableBundle options) {
- if (Flags.secondaryLockscreenApiEnabled()) {
+ if (Flags.secondaryLockscreenApiEnabled() && mSupervisionManagerInternal != null) {
final CallerIdentity caller = getCallerIdentity();
final boolean isRoleHolder = isCallerSystemSupervisionRoleHolder(caller);
synchronized (getLockObject()) {
@@ -14684,16 +14684,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getUserId());
}
- if (mSupervisionManagerInternal != null) {
- mSupervisionManagerInternal.setSupervisionLockscreenEnabledForUser(
- caller.getUserId(), enabled, options);
- } else {
- synchronized (getLockObject()) {
- DevicePolicyData policy = getUserData(caller.getUserId());
- policy.mSecondaryLockscreenEnabled = enabled;
- saveSettingsLocked(caller.getUserId());
- }
- }
+ mSupervisionManagerInternal.setSupervisionLockscreenEnabledForUser(
+ caller.getUserId(), enabled, options);
} else {
Objects.requireNonNull(who, "ComponentName is null");
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index d6ca10a23fb9..07b18db59960 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -27,6 +27,7 @@ android_test {
"servicestests-utils",
"platform-test-annotations",
"flag-junit",
+ "apct-perftests-utils",
],
libs: [
@@ -64,10 +65,12 @@ android_ravenwood_test {
"ravenwood-junit",
"truth",
"androidx.annotation_annotation",
+ "androidx.test.ext.junit",
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
"modules-utils-binary-xml",
"flag-junit",
+ "apct-perftests-utils",
],
srcs: [
"src/com/android/server/power/stats/*.java",
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTraceTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTraceTest.java
new file mode 100644
index 000000000000..cc75e9e3114f
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTraceTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.ParcelFileDescriptor;
+import android.perftests.utils.TraceMarkParser;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+@android.platform.test.annotations.DisabledOnRavenwood(reason = "Atrace event test")
+public class BatteryStatsHistoryTraceTest {
+ private static final String ATRACE_START = "atrace --async_start -b 1024 -c ss";
+ private static final String ATRACE_STOP = "atrace --async_stop";
+ private static final String ATRACE_DUMP = "atrace --async_dump";
+
+ @Before
+ public void before() throws Exception {
+ runShellCommand(ATRACE_START);
+ }
+
+ @After
+ public void after() throws Exception {
+ runShellCommand(ATRACE_STOP);
+ }
+
+ @Test
+ public void dumpsys() throws Exception {
+ runShellCommand("dumpsys batterystats --history");
+
+ Set<String> slices = readAtraceSlices();
+ assertThat(slices).contains("BatteryStatsHistory.copy");
+ assertThat(slices).contains("BatteryStatsHistory.iterate");
+ }
+
+ @Test
+ public void getBatteryUsageStats() throws Exception {
+ BatteryStatsManager batteryStatsManager =
+ getInstrumentation().getTargetContext().getSystemService(BatteryStatsManager.class);
+ BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
+ .includeBatteryHistory().build();
+ BatteryUsageStats batteryUsageStats = batteryStatsManager.getBatteryUsageStats(query);
+ assertThat(batteryUsageStats).isNotNull();
+
+ Set<String> slices = readAtraceSlices();
+ assertThat(slices).contains("BatteryStatsHistory.copy");
+ assertThat(slices).contains("BatteryStatsHistory.iterate");
+ assertThat(slices).contains("BatteryStatsHistory.writeToParcel");
+ }
+
+ private String runShellCommand(String cmd) throws Exception {
+ return UiDevice.getInstance(getInstrumentation()).executeShellCommand(cmd);
+ }
+
+ private Set<String> readAtraceSlices() throws Exception {
+ Set<String> keys = new HashSet<>();
+
+ TraceMarkParser parser = new TraceMarkParser(
+ line -> line.name.startsWith("BatteryStatsHistory."));
+ ParcelFileDescriptor pfd =
+ getInstrumentation().getUiAutomation().executeShellCommand(ATRACE_DUMP);
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(pfd)))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ parser.visit(line);
+ }
+ }
+ parser.forAllSlices((key, slices) -> keys.add(key));
+ return keys;
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 6cb24293a7d5..fa733e85c89c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -2509,6 +2509,134 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testRepostWithNewChannel_afterAutogrouping_isRegrouped() {
+ final String pkg = "package";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ // Post ungrouped notifications => will be autogrouped
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = getNotificationRecord(pkg, i + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, null, false);
+ notificationList.add(notification);
+ mGroupHelper.onNotificationPosted(notification, false);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Post ungrouped notifications to a different section, below autogroup limit
+ Mockito.reset(mCallback);
+ // Post ungrouped notifications => will be autogrouped
+ final NotificationChannel silentChannel = new NotificationChannel("TEST_CHANNEL_ID1",
+ "TEST_CHANNEL_ID1", IMPORTANCE_LOW);
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord notification = getNotificationRecord(pkg, i + 4242,
+ String.valueOf(i + 4242), UserHandle.SYSTEM, null, false, silentChannel);
+ notificationList.add(notification);
+ mGroupHelper.onNotificationPosted(notification, false);
+ }
+
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+
+ // Update a notification to a different channel that moves it to a different section
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ final NotificationSectioner initialSection = GroupHelper.getSection(notifToInvalidate);
+ final NotificationChannel updatedChannel = new NotificationChannel("TEST_CHANNEL_ID2",
+ "TEST_CHANNEL_ID2", IMPORTANCE_LOW);
+ notifToInvalidate.updateNotificationChannel(updatedChannel);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNotEqualTo(initialSection);
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, false);
+ assertThat(needsAutogrouping).isTrue();
+
+ // Check that the silent section was autogrouped
+ final String silentSectionGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(silentSectionGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(silentSectionGroupKey), eq(true));
+ verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testRepostWithNewChannel_afterForceGrouping_isRegrouped() {
+ final String pkg = "package";
+ final String groupName = "testGroup";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post valid section summary notifications without children => force group
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = getNotificationRecord(pkg, i + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, groupName, false);
+ notificationList.add(notification);
+ mGroupHelper.onNotificationPostedWithDelay(notification, notificationList,
+ summaryByGroup);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Update a notification to a different channel that moves it to a different section
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ final NotificationSectioner initialSection = GroupHelper.getSection(notifToInvalidate);
+ final NotificationChannel updatedChannel = new NotificationChannel("TEST_CHANNEL_ID2",
+ "TEST_CHANNEL_ID2", IMPORTANCE_LOW);
+ notifToInvalidate.updateNotificationChannel(updatedChannel);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNotEqualTo(initialSection);
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, false);
+
+ mGroupHelper.onNotificationPostedWithDelay(notifToInvalidate, notificationList,
+ summaryByGroup);
+
+ // Check that the updated notification is removed from the autogroup
+ assertThat(needsAutogrouping).isFalse();
+ verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+
+ // Post child notifications for the silent sectin => will be autogrouped
+ Mockito.reset(mCallback);
+ final NotificationChannel silentChannel = new NotificationChannel("TEST_CHANNEL_ID1",
+ "TEST_CHANNEL_ID1", IMPORTANCE_LOW);
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord notification = getNotificationRecord(pkg, i + 4242,
+ String.valueOf(i + 4242), UserHandle.SYSTEM, "aGroup", false, silentChannel);
+ notificationList.add(notification);
+ needsAutogrouping = mGroupHelper.onNotificationPosted(notification, false);
+ assertThat(needsAutogrouping).isFalse();
+ mGroupHelper.onNotificationPostedWithDelay(notification, notificationList,
+ summaryByGroup);
+ }
+
+ // Check that the silent section was autogrouped
+ final String silentSectionGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(silentSectionGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(silentSectionGroupKey), eq(true));
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testMoveAggregateGroups_updateChannel() {
final String pkg = "package";
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 7885c9b902e2..e43b28bb9404 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6341,6 +6341,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testOnlyAutogroupIfNeeded_channelChanged_ghUpdate() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+ "testOnlyAutogroupIfNeeded_channelChanged_ghUpdate", null, false);
+ mService.addNotification(r);
+
+ NotificationRecord update = generateNotificationRecord(mSilentChannel, 0,
+ "testOnlyAutogroupIfNeeded_channelChanged_ghUpdate", null, false);
+ mService.addEnqueuedNotification(update);
+
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(update.getKey(),
+ update.getSbn().getPackageName(), update.getUid(),
+ mPostNotificationTrackerFactory.newTracker(null));
+ runnable.run();
+ waitForIdle();
+
+ verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+ }
+
+ @Test
public void testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate() {
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
"testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
@@ -17901,4 +17921,63 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary));
}
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testRebundleNotification_restoresBundleChannel() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+
+ // Post a single notification
+ final boolean hasOriginalSummary = false;
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ final String keyToUnbundle = r.getKey();
+ mService.addNotification(r);
+
+ // Classify notification into the NEWS bundle
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+ Adjustment adjustment = new Adjustment(
+ r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ // Check that the NotificationRecord channel is updated
+ assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+ assertThat(r.getBundleType()).isEqualTo(Adjustment.TYPE_NEWS);
+
+ // Unbundle the notification
+ mService.mNotificationDelegate.unbundleNotification(keyToUnbundle);
+
+ // Check that the original channel was restored
+ assertThat(r.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID);
+ assertThat(r.getBundleType()).isEqualTo(Adjustment.TYPE_NEWS);
+ verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r), eq(hasOriginalSummary));
+
+ Mockito.reset(mRankingHandler);
+ Mockito.reset(mGroupHelper);
+
+ // Rebundle the notification
+ mService.mNotificationDelegate.rebundleNotification(keyToUnbundle);
+
+ // Actually apply the adjustments
+ doAnswer(invocationOnMock -> {
+ ((NotificationRecord) invocationOnMock.getArguments()[0]).applyAdjustments();
+ ((NotificationRecord) invocationOnMock.getArguments()[0]).calculateImportance();
+ return null;
+ }).when(mRankingHelper).extractSignals(any(NotificationRecord.class));
+ mService.handleRankingSort();
+ verify(handler, times(1)).scheduleSendRankingUpdate();
+
+ // Check that the bundle channel was restored
+ verify(mRankingHandler, times(1)).requestSort();
+ assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+ }
+
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 5486aa34b5fa..dfd10ec86a20 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1183,6 +1183,18 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(prev, mDisplayContent.getLastOrientationSource());
// The top will use the rotation from "prev" with fixed rotation.
assertTrue(top.hasFixedRotationTransform());
+
+ mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
+ assertFalse(top.hasFixedRotationTransform());
+
+ // Assume that the requested orientation of "prev" is landscape. And the display is also
+ // rotated to landscape. The activities from bottom to top are TaskB{"prev, "behindTop"},
+ // TaskB{"top"}. Then "behindTop" should also get landscape according to ORIENTATION_BEHIND
+ // instead of resolving as undefined which causes to unexpected fixed portrait rotation.
+ final ActivityRecord behindTop = new ActivityBuilder(mAtm).setTask(prev.getTask())
+ .setOnTop(false).setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
+ mDisplayContent.applyFixedRotationForNonTopVisibleActivityIfNeeded(behindTop);
+ assertFalse(behindTop.hasFixedRotationTransform());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index de4b6fac7abf..23dcb65eb30f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -34,6 +34,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTENER_TIMEOUT;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -46,12 +47,14 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ShortcutServiceInternal;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -60,6 +63,7 @@ import android.os.Message;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.DragEvent;
import android.view.InputChannel;
@@ -74,6 +78,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
+import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.AfterClass;
@@ -141,17 +146,28 @@ public class DragDropControllerTests extends WindowTestsBase {
}
}
+ private WindowState createDropTargetWindow(String name) {
+ return createDropTargetWindow(name, null /* targetDisplay */);
+ }
+
/**
* Creates a window state which can be used as a drop target.
*/
- private WindowState createDropTargetWindow(String name, int ownerId) {
- final Task task = new TaskBuilder(mSupervisor).setUserId(ownerId).build();
- final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).setUseProcess(
- mProcess).build();
+ private WindowState createDropTargetWindow(String name,
+ @Nullable DisplayContent targetDisplay) {
+ final WindowState window;
+ if (targetDisplay == null) {
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).setUseProcess(
+ mProcess).build();
+ window = newWindowBuilder(name, TYPE_BASE_APPLICATION).setWindowToken(
+ activity).setClientWindow(new TestIWindow()).build();
+ } else {
+ window = newWindowBuilder(name, TYPE_BASE_APPLICATION).setDisplay(
+ targetDisplay).setClientWindow(new TestIWindow()).build();
+ }
// Use a new TestIWindow so we don't collect events for other windows
- final WindowState window = newWindowBuilder(name, TYPE_BASE_APPLICATION).setWindowToken(
- activity).setOwnerId(ownerId).setClientWindow(new TestIWindow()).build();
InputChannel channel = new InputChannel();
window.openInputChannel(channel);
window.mHasSurface = true;
@@ -174,7 +190,7 @@ public class DragDropControllerTests extends WindowTestsBase {
public void setUp() throws Exception {
mTarget = new TestDragDropController(mWm, mWm.mH.getLooper());
mProcess = mSystemServicesTestRule.addProcess(TEST_PACKAGE, "testProc", TEST_PID, TEST_UID);
- mWindow = createDropTargetWindow("Drag test window", 0);
+ mWindow = createDropTargetWindow("Drag test window");
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
when(mWm.mInputManager.startDragAndDrop(any(IBinder.class), any(IBinder.class))).thenReturn(
true);
@@ -263,8 +279,8 @@ public class DragDropControllerTests extends WindowTestsBase {
@Test
public void testPrivateInterceptGlobalDragDropIgnoresNonLocalWindows() {
- WindowState nonLocalWindow = createDropTargetWindow("App drag test window", 0);
- WindowState globalInterceptWindow = createDropTargetWindow("Global drag test window", 0);
+ WindowState nonLocalWindow = createDropTargetWindow("App drag test window");
+ WindowState globalInterceptWindow = createDropTargetWindow("Global drag test window");
globalInterceptWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
// Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
@@ -347,6 +363,120 @@ public class DragDropControllerTests extends WindowTestsBase {
});
}
+ @Test
+ public void testDragEventCoordinates() {
+ int dragStartX = mWindow.getBounds().centerX();
+ int dragStartY = mWindow.getBounds().centerY();
+ int startOffsetPx = 10;
+ int dropCoordsPx = 15;
+ WindowState window2 = createDropTargetWindow("App drag test window");
+ Rect bounds = new Rect(dragStartX + startOffsetPx, dragStartY + startOffsetPx,
+ mWindow.getBounds().right, mWindow.getBounds().bottom);
+ window2.setBounds(bounds);
+ window2.getFrame().set(bounds);
+
+ // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
+ // immediately after dispatching, which is a problem when using mockito arguments captor
+ // because it returns and modifies the same drag event.
+ TestIWindow iwindow = (TestIWindow) mWindow.mClient;
+ final ArrayList<DragEvent> dragEvents = new ArrayList<>();
+ iwindow.setDragEventJournal(dragEvents);
+ TestIWindow iwindow2 = (TestIWindow) window2.mClient;
+ final ArrayList<DragEvent> dragEvents2 = new ArrayList<>();
+ iwindow2.setDragEventJournal(dragEvents2);
+
+ startDrag(dragStartX, dragStartY, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ,
+ ClipData.newPlainText("label", "text"), () -> {
+ // Verify the start-drag event is sent as-is for the drag origin window.
+ final DragEvent dragEvent = dragEvents.get(0);
+ assertEquals(ACTION_DRAG_STARTED, dragEvent.getAction());
+ assertEquals(dragStartX, dragEvent.getX(), 0.0 /* delta */);
+ assertEquals(dragStartY, dragEvent.getY(), 0.0 /* delta */);
+ // Verify the start-drag event is sent relative to the window top-left.
+ final DragEvent dragEvent2 = dragEvents2.get(0);
+ assertEquals(ACTION_DRAG_STARTED, dragEvent2.getAction());
+ assertEquals(-startOffsetPx, dragEvent2.getX(), 0.0 /* delta */);
+ assertEquals(-startOffsetPx, dragEvent2.getY(), 0.0 /* delta */);
+
+ try {
+ mTarget.mDeferDragStateClosed = true;
+ // x, y is window-local coordinate.
+ mTarget.reportDropWindow(window2.mInputChannelToken, dropCoordsPx,
+ dropCoordsPx);
+ mTarget.handleMotionEvent(false, window2.getDisplayId(), dropCoordsPx,
+ dropCoordsPx);
+ mToken = window2.mClient.asBinder();
+ // Verify only window2 received the DROP event and coords are sent as-is.
+ assertEquals(1, dragEvents.size());
+ assertEquals(2, dragEvents2.size());
+ final DragEvent dropEvent = last(dragEvents2);
+ assertEquals(ACTION_DROP, dropEvent.getAction());
+ assertEquals(dropCoordsPx, dropEvent.getX(), 0.0 /* delta */);
+ assertEquals(dropCoordsPx, dropEvent.getY(), 0.0 /* delta */);
+
+ mTarget.reportDropResult(iwindow2, true);
+ } finally {
+ mTarget.mDeferDragStateClosed = false;
+ }
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND)
+ public void testDragEventConnectedDisplaysCoordinates() {
+ final DisplayContent testDisplay = createMockSimulatedDisplay();
+ int dragStartX = mWindow.getBounds().centerX();
+ int dragStartY = mWindow.getBounds().centerY();
+ int dropCoordsPx = 15;
+ WindowState window2 = createDropTargetWindow("App drag test window", testDisplay);
+
+ // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
+ // immediately after dispatching, which is a problem when using mockito arguments captor
+ // because it returns and modifies the same drag event.
+ TestIWindow iwindow = (TestIWindow) mWindow.mClient;
+ final ArrayList<DragEvent> dragEvents = new ArrayList<>();
+ iwindow.setDragEventJournal(dragEvents);
+ TestIWindow iwindow2 = (TestIWindow) window2.mClient;
+ final ArrayList<DragEvent> dragEvents2 = new ArrayList<>();
+ iwindow2.setDragEventJournal(dragEvents2);
+
+ startDrag(dragStartX, dragStartY, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ,
+ ClipData.newPlainText("label", "text"), () -> {
+ // Verify the start-drag event is sent as-is for the drag origin window.
+ final DragEvent dragEvent = dragEvents.get(0);
+ assertEquals(ACTION_DRAG_STARTED, dragEvent.getAction());
+ assertEquals(dragStartX, dragEvent.getX(), 0.0 /* delta */);
+ assertEquals(dragStartY, dragEvent.getY(), 0.0 /* delta */);
+ // Verify the start-drag event from different display is sent out of display
+ // bounds.
+ final DragEvent dragEvent2 = dragEvents2.get(0);
+ assertEquals(ACTION_DRAG_STARTED, dragEvent2.getAction());
+ assertEquals(-window2.getBounds().left - 1, dragEvent2.getX(), 0.0 /* delta */);
+ assertEquals(-window2.getBounds().top - 1, dragEvent2.getY(), 0.0 /* delta */);
+
+ try {
+ mTarget.mDeferDragStateClosed = true;
+ // x, y is window-local coordinate.
+ mTarget.reportDropWindow(window2.mInputChannelToken, dropCoordsPx,
+ dropCoordsPx);
+ mTarget.handleMotionEvent(false, window2.getDisplayId(), dropCoordsPx,
+ dropCoordsPx);
+ mToken = window2.mClient.asBinder();
+ // Verify only window2 received the DROP event and coords are sent as-is
+ assertEquals(1, dragEvents.size());
+ assertEquals(2, dragEvents2.size());
+ final DragEvent dropEvent = last(dragEvents2);
+ assertEquals(ACTION_DROP, dropEvent.getAction());
+ assertEquals(dropCoordsPx, dropEvent.getX(), 0.0 /* delta */);
+ assertEquals(dropCoordsPx, dropEvent.getY(), 0.0 /* delta */);
+
+ mTarget.reportDropResult(iwindow2, true);
+ } finally {
+ mTarget.mDeferDragStateClosed = false;
+ }
+ });
+ }
+
private DragEvent last(ArrayList<DragEvent> list) {
return list.get(list.size() - 1);
}
@@ -503,7 +633,7 @@ public class DragDropControllerTests extends WindowTestsBase {
@Test
public void testRequestSurfaceForReturnAnimationFlag_dropSuccessful() {
- WindowState otherWindow = createDropTargetWindow("App drag test window", 0);
+ WindowState otherWindow = createDropTargetWindow("App drag test window");
TestIWindow otherIWindow = (TestIWindow) otherWindow.mClient;
// Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
@@ -534,7 +664,7 @@ public class DragDropControllerTests extends WindowTestsBase {
@Test
public void testRequestSurfaceForReturnAnimationFlag_dropUnsuccessful() {
- WindowState otherWindow = createDropTargetWindow("App drag test window", 0);
+ WindowState otherWindow = createDropTargetWindow("App drag test window");
TestIWindow otherIWindow = (TestIWindow) otherWindow.mClient;
// Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
@@ -687,6 +817,14 @@ public class DragDropControllerTests extends WindowTestsBase {
* Starts a drag with the given parameters, calls Runnable `r` after drag is started.
*/
private void startDrag(int flag, ClipData data, Runnable r) {
+ startDrag(0, 0, flag, data, r);
+ }
+
+ /**
+ * Starts a drag with the given parameters, calls Runnable `r` after drag is started.
+ */
+ private void startDrag(float startInWindowX, float startInWindowY, int flag, ClipData data,
+ Runnable r) {
final SurfaceSession appSession = new SurfaceSession();
try {
final SurfaceControl surface = new SurfaceControl.Builder(appSession).setName(
@@ -694,8 +832,8 @@ public class DragDropControllerTests extends WindowTestsBase {
PixelFormat.TRANSLUCENT).build();
assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new Binder()));
- mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
- 0, 0, data);
+ mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient, flag, surface, 0, 0, 0,
+ startInWindowX, startInWindowY, 0, 0, data);
assertNotNull(mToken);
r.run();
diff --git a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
index 3e87f1f96fcd..ee9673f5ee77 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
@@ -177,15 +177,16 @@ public class PersisterQueueTests {
assertTrue("Target didn't call callback enough times.",
mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
+ // Wait until writing thread is waiting, which indicates the thread is waiting for new tasks
+ // to appear.
+ assertTrue("Failed to wait until the writing thread is waiting.",
+ mTarget.waitUntilWritingThreadIsWaiting(TIMEOUT_ALLOWANCE));
+
// Second item
mFactory.setExpectedProcessedItemNumber(1);
mListener.setExpectedOnPreProcessItemCallbackTimes(1);
dispatchTime = SystemClock.uptimeMillis();
- // Synchronize on the instance to make sure we schedule the item after it starts to wait for
- // task indefinitely.
- synchronized (mTarget) {
- mTarget.addItem(mFactory.createItem(), false);
- }
+ mTarget.addItem(mFactory.createItem(), false);
assertTrue("Target didn't process item enough times.",
mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE));
assertEquals("Target didn't process all items.", 2, mFactory.getTotalProcessedItemCount());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index d1f5d157560b..be79160c3a09 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -76,6 +76,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.IApplicationThread;
import android.content.pm.ActivityInfo;
@@ -1522,7 +1523,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
@EnableFlags(Flags.FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE)
public void setConfigurationChangeSettingsForUser_createsFromParcel_callsSettingImpl()
throws Settings.SettingNotFoundException {
- final int userId = 0;
+ final int currentUserId = ActivityManager.getCurrentUser();
final int forcedDensity = 400;
final float forcedFontScaleFactor = 1.15f;
final Parcelable.Creator<ConfigurationChangeSetting> creator =
@@ -1536,10 +1537,10 @@ public class WindowManagerServiceTests extends WindowTestsBase {
mWm.setConfigurationChangeSettingsForUser(settings, UserHandle.USER_CURRENT);
- verify(mDisplayContent).setForcedDensity(forcedDensity, userId);
+ verify(mDisplayContent).setForcedDensity(forcedDensity, currentUserId);
assertEquals(forcedFontScaleFactor, Settings.System.getFloat(
mContext.getContentResolver(), Settings.System.FONT_SCALE), 0.1f /* delta */);
- verify(mAtm).updateFontScaleIfNeeded(userId);
+ verify(mAtm).updateFontScaleIfNeeded(currentUserId);
}
@Test
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java
index 060133df0a40..e7e3d10c958b 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java
@@ -81,7 +81,8 @@ public final class PlatformCompatPermissionsTest {
thrown.expect(SecurityException.class);
final String packageName = mContext.getPackageName();
- mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.reportChange(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -90,7 +91,8 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE);
final String packageName = mContext.getPackageName();
- mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.reportChange(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -99,7 +101,7 @@ public final class PlatformCompatPermissionsTest {
thrown.expect(SecurityException.class);
final String packageName = mContext.getPackageName();
- mPlatformCompat.reportChangeByPackageName(1, packageName, 0);
+ mPlatformCompat.reportChangeByPackageName(1, packageName, Process.myUid());
}
@Test
@@ -108,7 +110,7 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE);
final String packageName = mContext.getPackageName();
- mPlatformCompat.reportChangeByPackageName(1, packageName, 0);
+ mPlatformCompat.reportChangeByPackageName(1, packageName, Process.myUid());
}
@Test
@@ -133,7 +135,8 @@ public final class PlatformCompatPermissionsTest {
thrown.expect(SecurityException.class);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.isChangeEnabled(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -143,7 +146,8 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.isChangeEnabled(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -152,7 +156,8 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0));
+ mPlatformCompat.isChangeEnabled(1,
+ mPackageManager.getApplicationInfo(packageName, Process.myUid()));
}
@Test
@@ -161,7 +166,7 @@ public final class PlatformCompatPermissionsTest {
thrown.expect(SecurityException.class);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0);
+ mPlatformCompat.isChangeEnabledByPackageName(1, packageName, Process.myUid());
}
@Test
@@ -171,7 +176,7 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0);
+ mPlatformCompat.isChangeEnabledByPackageName(1, packageName, Process.myUid());
}
@Test
@@ -180,7 +185,7 @@ public final class PlatformCompatPermissionsTest {
mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE);
final String packageName = mContext.getPackageName();
- mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0);
+ mPlatformCompat.isChangeEnabledByPackageName(1, packageName, Process.myUid());
}
@Test