summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java278
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java6
-rw-r--r--core/java/android/app/ApplicationPackageManager.java23
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java32
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig8
-rw-r--r--core/java/android/content/pm/PackageManager.java31
-rw-r--r--core/java/android/os/BaseBundle.java11
-rw-r--r--core/java/android/os/Bundle.java36
-rw-r--r--core/java/android/os/Parcel.java65
-rw-r--r--core/java/android/os/health/SystemHealthManager.java30
-rw-r--r--core/java/android/view/RoundScrollbarRenderer.java43
-rw-r--r--core/java/android/view/ViewConfiguration.java231
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl12
-rw-r--r--core/java/com/android/internal/policy/DecorView.java85
-rw-r--r--core/res/res/values-w225dp/dimens.xml20
-rw-r--r--core/res/res/values/config.xml45
-rw-r--r--core/res/res/values/dimens.xml3
-rw-r--r--core/res/res/values/symbols.xml13
-rw-r--r--core/tests/coretests/src/android/content/IntentTest.java39
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt78
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java40
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt86
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt191
-rw-r--r--libs/hwui/OWNERS1
-rw-r--r--packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt18
-rw-r--r--packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt2
-rw-r--r--packages/SystemUI/Android.bp10
-rw-r--r--packages/SystemUI/compose/core/Android.bp5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt43
-rw-r--r--packages/SystemUI/compose/scene/Android.bp5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt121
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt60
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt138
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java10
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/res/values/styles.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java234
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/GlanceableHubBlurProvider.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentCompat.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifier.kt175
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt8
-rw-r--r--packages/SystemUI/tests/res/layout/custom_view_flipper.xml14
-rw-r--r--packages/SystemUI/tests/res/layout/custom_view_flipper_image.xml5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierFlagDisabledTest.java57
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java285
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentNotificationBuilder.kt94
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorKosmos.kt2
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java174
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java13
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java15
-rw-r--r--services/core/java/com/android/server/am/UserController.java38
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java17
-rw-r--r--services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java13
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java12
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java22
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java44
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterImpl.java5
-rw-r--r--services/core/java/com/android/server/wm/AppWarnings.java4
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java56
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java64
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java33
105 files changed, 2715 insertions, 1432 deletions
diff --git a/Android.bp b/Android.bp
index 9d3b64d7335b..303fa2cd18da 100644
--- a/Android.bp
+++ b/Android.bp
@@ -583,6 +583,7 @@ java_library {
"documents-ui-compat-config",
"calendar-provider-compat-config",
"contacts-provider-platform-compat-config",
+ "SystemUI-core-compat-config",
] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
"true": [],
default: [
diff --git a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
index 8e3ed6d9931c..7a7250b9e910 100644
--- a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
@@ -19,27 +19,24 @@ package android.view;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.content.Context;
-import android.perftests.utils.BenchmarkState;
-import android.perftests.utils.PerfStatusReporter;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
+import androidx.test.filters.SmallTest;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.RunWith;
-@LargeTest
-@RunWith(AndroidJUnit4.class)
+@SmallTest
public class ViewConfigurationPerfTest {
@Rule
- public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+ public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
private final Context mContext = getInstrumentation().getTargetContext();
@Test
public void testGet_newViewConfiguration() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
state.pauseTiming();
@@ -53,7 +50,7 @@ public class ViewConfigurationPerfTest {
@Test
public void testGet_cachedViewConfiguration() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
// Do `get` once to make sure there's something cached.
ViewConfiguration.get(mContext);
@@ -61,265 +58,4 @@ public class ViewConfigurationPerfTest {
ViewConfiguration.get(mContext);
}
}
-
- @Test
- public void testGetPressedStateDuration_unCached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- state.pauseTiming();
- // Reset any caches.
- ViewConfiguration.resetCacheForTesting();
- state.resumeTiming();
-
- ViewConfiguration.getPressedStateDuration();
- }
- }
-
- @Test
- public void testGetPressedStateDuration_cached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- // Do `get` once to make sure the value gets cached.
- ViewConfiguration.getPressedStateDuration();
-
- while (state.keepRunning()) {
- ViewConfiguration.getPressedStateDuration();
- }
- }
-
- @Test
- public void testGetTapTimeout_unCached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- state.pauseTiming();
- // Reset any caches.
- ViewConfiguration.resetCacheForTesting();
- state.resumeTiming();
-
- ViewConfiguration.getTapTimeout();
- }
- }
-
- @Test
- public void testGetTapTimeout_cached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- // Do `get` once to make sure the value gets cached.
- ViewConfiguration.getTapTimeout();
-
- while (state.keepRunning()) {
- ViewConfiguration.getTapTimeout();
- }
- }
-
- @Test
- public void testGetJumpTapTimeout_unCached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- state.pauseTiming();
- // Reset any caches.
- ViewConfiguration.resetCacheForTesting();
- state.resumeTiming();
-
- ViewConfiguration.getJumpTapTimeout();
- }
- }
-
- @Test
- public void testGetJumpTapTimeout_cached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- // Do `get` once to make sure the value gets cached.
- ViewConfiguration.getJumpTapTimeout();
-
- while (state.keepRunning()) {
- ViewConfiguration.getJumpTapTimeout();
- }
- }
-
- @Test
- public void testGetDoubleTapTimeout_unCached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- state.pauseTiming();
- // Reset any caches.
- ViewConfiguration.resetCacheForTesting();
- state.resumeTiming();
-
- ViewConfiguration.getDoubleTapTimeout();
- }
- }
-
- @Test
- public void testGetDoubleTapTimeout_cached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- // Do `get` once to make sure the value gets cached.
- ViewConfiguration.getDoubleTapTimeout();
-
- while (state.keepRunning()) {
- ViewConfiguration.getDoubleTapTimeout();
- }
- }
-
- @Test
- public void testGetDoubleTapMinTime_unCached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- state.pauseTiming();
- // Reset any caches.
- ViewConfiguration.resetCacheForTesting();
- state.resumeTiming();
-
- ViewConfiguration.getDoubleTapMinTime();
- }
- }
-
- @Test
- public void testGetDoubleTapMinTime_cached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- // Do `get` once to make sure the value gets cached.
- ViewConfiguration.getDoubleTapMinTime();
-
- while (state.keepRunning()) {
- ViewConfiguration.getDoubleTapMinTime();
- }
- }
-
- @Test
- public void testGetZoomControlsTimeout_unCached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- state.pauseTiming();
- // Reset any caches.
- ViewConfiguration.resetCacheForTesting();
- state.resumeTiming();
-
- ViewConfiguration.getZoomControlsTimeout();
- }
- }
-
- @Test
- public void testGetZoomControlsTimeout_cached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- // Do `get` once to make sure the value gets cached.
- ViewConfiguration.getZoomControlsTimeout();
-
- while (state.keepRunning()) {
- ViewConfiguration.getZoomControlsTimeout();
- }
- }
-
- @Test
- public void testGetLongPressTimeout() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- ViewConfiguration.getLongPressTimeout();
- }
- }
-
- @Test
- public void testGetMultiPressTimeout() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- ViewConfiguration.getMultiPressTimeout();
- }
- }
-
- @Test
- public void testGetKeyRepeatTimeout() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- ViewConfiguration.getKeyRepeatTimeout();
- }
- }
-
- @Test
- public void testGetKeyRepeatDelay() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- ViewConfiguration.getKeyRepeatDelay();
- }
- }
-
- @Test
- public void testGetHoverTapSlop_unCached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- state.pauseTiming();
- // Reset any caches.
- ViewConfiguration.resetCacheForTesting();
- state.resumeTiming();
-
- ViewConfiguration.getHoverTapSlop();
- }
- }
-
- @Test
- public void testGetHoverTapSlop_cached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- // Do `get` once to make sure the value gets cached.
- ViewConfiguration.getHoverTapSlop();
-
- while (state.keepRunning()) {
- ViewConfiguration.getHoverTapSlop();
- }
- }
-
- @Test
- public void testGetScrollFriction_unCached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- state.pauseTiming();
- // Reset any caches.
- ViewConfiguration.resetCacheForTesting();
- state.resumeTiming();
-
- ViewConfiguration.getScrollFriction();
- }
- }
-
- @Test
- public void testGetScrollFriction_cached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- // Do `get` once to make sure the value gets cached.
- ViewConfiguration.getScrollFriction();
-
- while (state.keepRunning()) {
- ViewConfiguration.getScrollFriction();
- }
- }
-
- @Test
- public void testGetDefaultActionModeHideDuration_unCached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-
- while (state.keepRunning()) {
- state.pauseTiming();
- // Reset any caches.
- ViewConfiguration.resetCacheForTesting();
- state.resumeTiming();
-
- ViewConfiguration.getDefaultActionModeHideDuration();
- }
- }
-
- @Test
- public void testGetDefaultActionModeHideDuration_cached() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- // Do `get` once to make sure the value gets cached.
- ViewConfiguration.getDefaultActionModeHideDuration();
-
- while (state.keepRunning()) {
- ViewConfiguration.getDefaultActionModeHideDuration();
- }
- }
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 9871d713178e..ab8131ba5126 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -264,6 +264,8 @@ public class AppStandbyController
@GuardedBy("mCarrierPrivilegedLock")
private boolean mHaveCarrierPrivilegedApps;
+ private final boolean mHasFeatureTelephonySubscription;
+
/** List of carrier-privileged apps that should be excluded from standby */
@GuardedBy("mCarrierPrivilegedLock")
private List<String> mCarrierPrivilegedApps;
@@ -603,6 +605,8 @@ public class AppStandbyController
mContext = mInjector.getContext();
mHandler = new AppStandbyHandler(mInjector.getLooper());
mPackageManager = mContext.getPackageManager();
+ mHasFeatureTelephonySubscription = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver();
IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
@@ -1515,7 +1519,7 @@ public class AppStandbyController
}
// Check this last, as it can be the most expensive check
- if (isCarrierApp(packageName)) {
+ if (mHasFeatureTelephonySubscription && isCarrierApp(packageName)) {
return STANDBY_BUCKET_EXEMPTED;
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 2dead565fa85..f2e7e8513116 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -17,7 +17,6 @@
package android.app;
import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
-import static android.app.PropertyInvalidatedCache.createSystemCacheKey;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -1146,12 +1145,16 @@ public class ApplicationPackageManager extends PackageManager {
}
}
- private static final String CACHE_KEY_PACKAGES_FOR_UID_PROPERTY =
- createSystemCacheKey("get_packages_for_uid");
- private static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult>
- mGetPackagesForUidCache =
- new PropertyInvalidatedCache<Integer, GetPackagesForUidResult>(
- 1024, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) {
+ private static final String CACHE_KEY_PACKAGES_FOR_UID_API = "get_packages_for_uid";
+
+ /** @hide */
+ @VisibleForTesting
+ public static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult>
+ sGetPackagesForUidCache = new PropertyInvalidatedCache<>(
+ new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
+ .maxEntries(1024).api(CACHE_KEY_PACKAGES_FOR_UID_API).cacheNulls(true),
+ CACHE_KEY_PACKAGES_FOR_UID_API, null) {
+
@Override
public GetPackagesForUidResult recompute(Integer uid) {
try {
@@ -1170,17 +1173,17 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public String[] getPackagesForUid(int uid) {
- return mGetPackagesForUidCache.query(uid).value();
+ return sGetPackagesForUidCache.query(uid).value();
}
/** @hide */
public static void disableGetPackagesForUidCache() {
- mGetPackagesForUidCache.disableLocal();
+ sGetPackagesForUidCache.disableLocal();
}
/** @hide */
public static void invalidateGetPackagesForUidCache() {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY_PACKAGES_FOR_UID_PROPERTY);
+ sGetPackagesForUidCache.invalidateCache();
}
@Override
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index bfb33f2b7cb1..c573161f30cc 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1163,6 +1163,17 @@ public class PropertyInvalidatedCache<Query, Result> {
}
/**
+ * Return the current cache nonce.
+ * @hide
+ */
+ @VisibleForTesting
+ public long getNonce() {
+ synchronized (mLock) {
+ return mNonce.getNonce();
+ }
+ }
+
+ /**
* Complete key prefixes.
*/
private static final String PREFIX_TEST = CACHE_KEY_PREFIX + "." + MODULE_TEST + ".";
@@ -1314,7 +1325,7 @@ public class PropertyInvalidatedCache<Query, Result> {
/**
* Burst a property name into module and api. Throw if the key is invalid. This method is
- * used in to transition legacy cache constructors to the args constructor.
+ * used to transition legacy cache constructors to the Args constructor.
*/
private static Args argsFromProperty(@NonNull String name) {
throwIfInvalidCacheKey(name);
@@ -1327,6 +1338,15 @@ public class PropertyInvalidatedCache<Query, Result> {
}
/**
+ * Return the API porting of a legacy property. This method is used to transition caches to
+ * the Args constructor.
+ * @hide
+ */
+ public static String apiFromProperty(@NonNull String name) {
+ return argsFromProperty(name).mApi;
+ }
+
+ /**
* Make a new property invalidated cache. This constructor names the cache after the
* property name. New clients should prefer the constructor that takes an explicit
* cache name.
@@ -2036,11 +2056,11 @@ public class PropertyInvalidatedCache<Query, Result> {
}
/**
- * Disable all caches in the local process. This is primarily useful for testing when
- * the test needs to bypass the cache or when the test is for a server, and the test
- * process does not have privileges to write SystemProperties. Once disabled it is not
- * possible to re-enable caching in the current process. If a client wants to
- * temporarily disable caching, use the corking mechanism.
+ * Disable all caches in the local process. This is primarily useful for testing when the
+ * test needs to bypass the cache or when the test is for a server, and the test process does
+ * not have privileges to write the nonce. Once disabled it is not possible to re-enable
+ * caching in the current process. See {@link #testPropertyName} for a more focused way to
+ * bypass caches when the test is for a server.
* @hide
*/
public static void disableForTestMode() {
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index c3dc257e6535..fcdb02ab5da2 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -125,11 +125,3 @@ flag {
description: "Show virtual devices in Settings"
bug: "338974320"
}
-
-flag {
- name: "migrate_viewconfiguration_constants_to_resources"
- namespace: "virtual_devices"
- description: "Use resources instead of constants in ViewConfiguration"
- is_fixed_read_only: true
- bug: "370928384"
-}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0369b7d9bc28..6ae2df2cd7a2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
import static android.content.pm.SigningInfo.AppSigningSchemeVersion;
import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY;
@@ -11659,11 +11660,22 @@ public abstract class PackageManager {
}
}
- private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
- sApplicationInfoCache =
- new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
- 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE,
- "getApplicationInfo") {
+ private static String packageInfoApi() {
+ return PropertyInvalidatedCache.apiFromProperty(
+ PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE);
+ }
+
+ // The maximum number of entries to keep in the packageInfo and applicationInfo caches.
+ private final static int MAX_INFO_CACHE_ENTRIES = 2048;
+
+ /** @hide */
+ @VisibleForTesting
+ public static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
+ sApplicationInfoCache = new PropertyInvalidatedCache<>(
+ new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
+ .maxEntries(MAX_INFO_CACHE_ENTRIES).api(packageInfoApi()).cacheNulls(true),
+ "getApplicationInfo", null) {
+
@Override
public ApplicationInfo recompute(ApplicationInfoQuery query) {
return getApplicationInfoAsUserUncached(
@@ -11749,10 +11761,11 @@ public abstract class PackageManager {
}
private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
- sPackageInfoCache =
- new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
- 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE,
- "getPackageInfo") {
+ sPackageInfoCache = new PropertyInvalidatedCache<>(
+ new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
+ .maxEntries(MAX_INFO_CACHE_ENTRIES).api(packageInfoApi()).cacheNulls(true),
+ "getPackageInfo", null) {
+
@Override
public PackageInfo recompute(PackageInfoQuery query) {
return getPackageInfoAsUserUncached(
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index e79b2e7becce..26044545b8d1 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -45,7 +45,8 @@ import java.util.function.BiFunction;
* {@link PersistableBundle} subclass.
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
-public class BaseBundle {
+@SuppressWarnings("HiddenSuperclass")
+public class BaseBundle implements Parcel.ClassLoaderProvider {
/** @hide */
protected static final String TAG = "Bundle";
static final boolean DEBUG = false;
@@ -311,8 +312,9 @@ public class BaseBundle {
/**
* Return the ClassLoader currently associated with this Bundle.
+ * @hide
*/
- ClassLoader getClassLoader() {
+ public ClassLoader getClassLoader() {
return mClassLoader;
}
@@ -426,6 +428,9 @@ public class BaseBundle {
if ((mFlags & Bundle.FLAG_VERIFY_TOKENS_PRESENT) != 0) {
Intent.maybeMarkAsMissingCreatorToken(object);
}
+ } else if (object instanceof Bundle) {
+ Bundle bundle = (Bundle) object;
+ bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this);
}
return (clazz != null) ? clazz.cast(object) : (T) object;
}
@@ -499,7 +504,7 @@ public class BaseBundle {
int[] numLazyValues = new int[]{0};
try {
parcelledData.readArrayMap(map, count, !parcelledByNative,
- /* lazy */ ownsParcel, mClassLoader, numLazyValues);
+ /* lazy */ ownsParcel, this, numLazyValues);
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index a24dc5739b7e..c0591e6899b6 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -141,6 +141,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
STRIPPED.putInt("STRIPPED", 1);
}
+ private boolean isFirstRetrievedFromABundle = false;
+
/**
* Constructs a new, empty Bundle.
*/
@@ -382,7 +384,15 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
bundle.unparcel();
mOwnsLazyValues = false;
bundle.mOwnsLazyValues = false;
- mMap.putAll(bundle.mMap);
+ int N = bundle.mMap.size();
+ for (int i = 0; i < N; i++) {
+ String key = bundle.mMap.keyAt(i);
+ Object value = bundle.mMap.valueAt(i);
+ if (value instanceof Bundle) {
+ ((Bundle) value).isFirstRetrievedFromABundle = true;
+ }
+ mMap.put(key, value);
+ }
// FD and Binders state is now known if and only if both bundles already knew
if ((bundle.mFlags & FLAG_HAS_FDS) != 0) {
@@ -592,6 +602,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
if (intentClass != null && intentClass.isInstance(value)) {
setHasIntent(true);
+ } else if (value instanceof Bundle) {
+ ((Bundle) value).isFirstRetrievedFromABundle = true;
}
}
@@ -793,6 +805,9 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
*/
public void putBundle(@Nullable String key, @Nullable Bundle value) {
unparcel();
+ if (value != null) {
+ value.isFirstRetrievedFromABundle = true;
+ }
mMap.put(key, value);
}
@@ -1020,7 +1035,9 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
return null;
}
try {
- return (Bundle) o;
+ Bundle bundle = (Bundle) o;
+ bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this);
+ return bundle;
} catch (ClassCastException e) {
typeWarning(key, o, "Bundle", e);
return null;
@@ -1028,6 +1045,21 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
+ * Set the ClassLoader of a bundle to its container bundle. This is necessary so that when a
+ * bundle's ClassLoader is changed, it can be propagated to its children. Do this only when it
+ * is retrieved from the container bundle first time though. Once it is accessed outside of its
+ * container, its ClassLoader should no longer be changed by its container anymore.
+ *
+ * @param containerBundle the bundle this bundle is retrieved from.
+ */
+ void setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(BaseBundle containerBundle) {
+ if (!isFirstRetrievedFromABundle) {
+ setClassLoader(containerBundle.getClassLoader());
+ isFirstRetrievedFromABundle = true;
+ }
+ }
+
+ /**
* Returns the value associated with the given key, or {@code null} if
* no mapping of the desired type exists for the given key or a {@code null}
* value is explicitly associated with the key.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e58934746c14..49d3f06eb80f 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -46,6 +46,7 @@ import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import dalvik.annotation.optimization.CriticalNative;
@@ -4661,7 +4662,7 @@ public final class Parcel {
* @hide
*/
@Nullable
- public Object readLazyValue(@Nullable ClassLoader loader) {
+ private Object readLazyValue(@Nullable ClassLoaderProvider loaderProvider) {
int start = dataPosition();
int type = readInt();
if (isLengthPrefixed(type)) {
@@ -4672,12 +4673,17 @@ public final class Parcel {
int end = MathUtils.addOrThrow(dataPosition(), objectLength);
int valueLength = end - start;
setDataPosition(end);
- return new LazyValue(this, start, valueLength, type, loader);
+ return new LazyValue(this, start, valueLength, type, loaderProvider);
} else {
- return readValue(type, loader, /* clazz */ null);
+ return readValue(type, getClassLoader(loaderProvider), /* clazz */ null);
}
}
+ @Nullable
+ private static ClassLoader getClassLoader(@Nullable ClassLoaderProvider loaderProvider) {
+ return loaderProvider == null ? null : loaderProvider.getClassLoader();
+ }
+
private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> {
/**
@@ -4691,7 +4697,12 @@ public final class Parcel {
private final int mPosition;
private final int mLength;
private final int mType;
- @Nullable private final ClassLoader mLoader;
+ // this member is set when a bundle that includes a LazyValue is unparceled. But it is used
+ // when apply method is called. Between these 2 events, the bundle's ClassLoader could have
+ // changed. Let the bundle be a ClassLoaderProvider allows the bundle provides its current
+ // ClassLoader at the time apply method is called.
+ @NonNull
+ private final ClassLoaderProvider mLoaderProvider;
@Nullable private Object mObject;
/**
@@ -4702,12 +4713,13 @@ public final class Parcel {
*/
@Nullable private volatile Parcel mSource;
- LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) {
+ LazyValue(Parcel source, int position, int length, int type,
+ @NonNull ClassLoaderProvider loaderProvider) {
mSource = requireNonNull(source);
mPosition = position;
mLength = length;
mType = type;
- mLoader = loader;
+ mLoaderProvider = loaderProvider;
}
@Override
@@ -4720,7 +4732,8 @@ public final class Parcel {
int restore = source.dataPosition();
try {
source.setDataPosition(mPosition);
- mObject = source.readValue(mLoader, clazz, itemTypes);
+ mObject = source.readValue(mLoaderProvider.getClassLoader(), clazz,
+ itemTypes);
} finally {
source.setDataPosition(restore);
}
@@ -4758,6 +4771,12 @@ public final class Parcel {
return Parcel.hasFileDescriptors(mObject);
}
+ /** @hide */
+ @VisibleForTesting
+ public ClassLoader getClassLoader() {
+ return mLoaderProvider.getClassLoader();
+ }
+
@Override
public String toString() {
return (mSource != null)
@@ -4793,7 +4812,8 @@ public final class Parcel {
return Objects.equals(mObject, value.mObject);
}
// Better safely fail here since this could mean we get different objects.
- if (!Objects.equals(mLoader, value.mLoader)) {
+ if (!Objects.equals(mLoaderProvider.getClassLoader(),
+ value.mLoaderProvider.getClassLoader())) {
return false;
}
// Otherwise compare metadata prior to comparing payload.
@@ -4807,10 +4827,24 @@ public final class Parcel {
@Override
public int hashCode() {
// Accessing mSource first to provide memory barrier for mObject
- return Objects.hash(mSource == null, mObject, mLoader, mType, mLength);
+ return Objects.hash(mSource == null, mObject, mLoaderProvider.getClassLoader(), mType,
+ mLength);
}
}
+ /**
+ * Provides a ClassLoader.
+ * @hide
+ */
+ public interface ClassLoaderProvider {
+ /**
+ * Returns a ClassLoader.
+ *
+ * @return ClassLoader
+ */
+ ClassLoader getClassLoader();
+ }
+
/** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */
private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
// Avoids allocating Class[0] array
@@ -5551,8 +5585,8 @@ public final class Parcel {
}
private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
- int size, @Nullable ClassLoader loader) {
- readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader, null);
+ int size, @Nullable ClassLoaderProvider loaderProvider) {
+ readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider, null);
}
/**
@@ -5566,11 +5600,12 @@ public final class Parcel {
* @hide
*/
void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
- boolean lazy, @Nullable ClassLoader loader, int[] lazyValueCount) {
+ boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) {
ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, size);
while (size > 0) {
String key = readString();
- Object value = (lazy) ? readLazyValue(loader) : readValue(loader);
+ Object value = (lazy) ? readLazyValue(loaderProvider) : readValue(
+ getClassLoader(loaderProvider));
if (value instanceof LazyValue) {
lazyValueCount[0]++;
}
@@ -5591,12 +5626,12 @@ public final class Parcel {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal,
- @Nullable ClassLoader loader) {
+ @Nullable ClassLoaderProvider loaderProvider) {
final int N = readInt();
if (N < 0) {
return;
}
- readArrayMapInternal(outVal, N, loader);
+ readArrayMapInternal(outVal, N, loaderProvider);
}
/**
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index 9d0e221bd9e7..a8a22f675e08 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -473,17 +473,31 @@ public class SystemHealthManager {
}
}
- final HealthStats[] results = new HealthStats[uids.length];
- if (result.bundle != null) {
- HealthStatsParceler[] parcelers = result.bundle.getParcelableArray(
- IBatteryStats.KEY_UID_SNAPSHOTS, HealthStatsParceler.class);
- if (parcelers != null && parcelers.length == uids.length) {
- for (int i = 0; i < parcelers.length; i++) {
- results[i] = parcelers[i].getHealthStats();
+ switch (result.resultCode) {
+ case IBatteryStats.RESULT_OK: {
+ final HealthStats[] results = new HealthStats[uids.length];
+ if (result.bundle != null) {
+ HealthStatsParceler[] parcelers = result.bundle.getParcelableArray(
+ IBatteryStats.KEY_UID_SNAPSHOTS, HealthStatsParceler.class);
+ if (parcelers != null && parcelers.length == uids.length) {
+ for (int i = 0; i < parcelers.length; i++) {
+ results[i] = parcelers[i].getHealthStats();
+ }
+ }
}
+ return results;
+ }
+ case IBatteryStats.RESULT_SECURITY_EXCEPTION: {
+ throw new SecurityException(result.bundle != null
+ ? result.bundle.getString(IBatteryStats.KEY_EXCEPTION_MESSAGE) : null);
+ }
+ case IBatteryStats.RESULT_RUNTIME_EXCEPTION: {
+ throw new RuntimeException(result.bundle != null
+ ? result.bundle.getString(IBatteryStats.KEY_EXCEPTION_MESSAGE) : null);
}
+ default:
+ throw new RuntimeException("Error code: " + result.resultCode);
}
- return results;
}
/**
diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java
index 5e1eadae0953..331e34526ae8 100644
--- a/core/java/android/view/RoundScrollbarRenderer.java
+++ b/core/java/android/view/RoundScrollbarRenderer.java
@@ -20,6 +20,7 @@ import static android.util.MathUtils.acos;
import static java.lang.Math.sin;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -40,9 +41,9 @@ public class RoundScrollbarRenderer {
// The range of the scrollbar position represented as an angle in degrees.
private static final float SCROLLBAR_ANGLE_RANGE = 28.8f;
- private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 26.3f; // 90%
- private static final float MIN_SCROLLBAR_ANGLE_SWIPE = 3.1f; // 10%
- private static final float THUMB_WIDTH_DP = 4f;
+ private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 0.7f * SCROLLBAR_ANGLE_RANGE;
+ private static final float MIN_SCROLLBAR_ANGLE_SWIPE = 0.3f * SCROLLBAR_ANGLE_RANGE;
+ private static final float GAP_BETWEEN_TRACK_AND_THUMB_DP = 3f;
private static final float OUTER_PADDING_DP = 2f;
private static final int DEFAULT_THUMB_COLOR = 0xFFFFFFFF;
private static final int DEFAULT_TRACK_COLOR = 0x4CFFFFFF;
@@ -57,14 +58,16 @@ public class RoundScrollbarRenderer {
private final RectF mRect = new RectF();
private final View mParent;
private final float mInset;
+ private final float mGapBetweenThumbAndTrackPx;
+ private final boolean mUseRefactoredRoundScrollbar;
private float mPreviousMaxScroll = 0;
private float mMaxScrollDiff = 0;
private float mPreviousCurrentScroll = 0;
private float mCurrentScrollDiff = 0;
private float mThumbStrokeWidthAsDegrees = 0;
+ private float mGapBetweenTrackAndThumbAsDegrees = 0;
private boolean mDrawToLeft;
- private boolean mUseRefactoredRoundScrollbar;
public RoundScrollbarRenderer(View parent) {
// Paints for the round scrollbar.
@@ -80,16 +83,17 @@ public class RoundScrollbarRenderer {
mParent = parent;
+ Resources resources = parent.getContext().getResources();
// Fetch the resource indicating the thickness of CircularDisplayMask, rounding in the same
// way WindowManagerService.showCircularMask does. The scroll bar is inset by this amount so
// that it doesn't get clipped.
int maskThickness =
- parent.getContext()
- .getResources()
- .getDimensionPixelSize(
- com.android.internal.R.dimen.circular_display_mask_thickness);
+ resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.circular_display_mask_thickness);
- float thumbWidth = dpToPx(THUMB_WIDTH_DP);
+ float thumbWidth =
+ resources.getDimensionPixelSize(com.android.internal.R.dimen.round_scrollbar_width);
+ mGapBetweenThumbAndTrackPx = dpToPx(GAP_BETWEEN_TRACK_AND_THUMB_DP);
mThumbPaint.setStrokeWidth(thumbWidth);
mTrackPaint.setStrokeWidth(thumbWidth);
mInset = thumbWidth / 2 + maskThickness;
@@ -175,7 +179,6 @@ public class RoundScrollbarRenderer {
}
}
- /** Returns true if horizontal bounds are updated */
private void updateBounds(Rect bounds) {
mRect.set(
bounds.left + mInset,
@@ -184,6 +187,8 @@ public class RoundScrollbarRenderer {
bounds.bottom - mInset);
mThumbStrokeWidthAsDegrees =
getVertexAngle((mRect.right - mRect.left) / 2f, mThumbPaint.getStrokeWidth() / 2f);
+ mGapBetweenTrackAndThumbAsDegrees =
+ getVertexAngle((mRect.right - mRect.left) / 2f, mGapBetweenThumbAndTrackPx);
}
private float computeSweepAngle(float scrollExtent, float maxScroll) {
@@ -262,20 +267,22 @@ public class RoundScrollbarRenderer {
// The highest point of the top track on a vertical scale. Here the thumb width is
// reduced to account for the arc formed by ROUND stroke style
-SCROLLBAR_ANGLE_RANGE / 2f - mThumbStrokeWidthAsDegrees,
- // The lowest point of the top track on a vertical scale. Here the thumb width is
- // reduced twice to (a) account for the arc formed by ROUND stroke style (b) gap
- // between thumb and top track
- thumbStartAngle - mThumbStrokeWidthAsDegrees * 2,
+ // The lowest point of the top track on a vertical scale. It's reduced by
+ // (a) angular distance for the arc formed by ROUND stroke style
+ // (b) gap between thumb and top track
+ thumbStartAngle - mThumbStrokeWidthAsDegrees - mGapBetweenTrackAndThumbAsDegrees,
alpha);
// Draws the thumb
drawArc(canvas, thumbStartAngle, thumbSweepAngle, mThumbPaint);
// Draws the bottom arc
drawTrack(
canvas,
- // The highest point of the bottom track on a vertical scale. Here the thumb width
- // is added twice to (a) account for the arc formed by ROUND stroke style (b) gap
- // between thumb and bottom track
- (thumbStartAngle + thumbSweepAngle) + mThumbStrokeWidthAsDegrees * 2,
+ // The highest point of the bottom track on a vertical scale. Following added to it
+ // (a) angular distance for the arc formed by ROUND stroke style
+ // (b) gap between thumb and top track
+ (thumbStartAngle + thumbSweepAngle)
+ + mThumbStrokeWidthAsDegrees
+ + mGapBetweenTrackAndThumbAsDegrees,
// The lowest point of the top track on a vertical scale. Here the thumb width is
// added to account for the arc formed by ROUND stroke style
SCROLLBAR_ANGLE_RANGE / 2f + mThumbStrokeWidthAsDegrees,
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 2895bf3f846a..9e97a8eb58aa 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -21,9 +21,7 @@ import android.annotation.NonNull;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.app.Activity;
-import android.app.ActivityThread;
import android.app.AppGlobals;
-import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
@@ -41,13 +39,14 @@ import android.util.SparseArray;
import android.util.TypedValue;
import android.view.flags.Flags;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
/**
* Contains methods to standard constants used in the UI for timeouts, sizes, and distances.
*/
public class ViewConfiguration {
+ private static final String TAG = "ViewConfiguration";
+
/**
* Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
* dips
@@ -350,8 +349,6 @@ public class ViewConfiguration {
*/
private static final int SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND = 500;
- private static ResourceCache sResourceCache = new ResourceCache();
-
private final boolean mConstructedWithContext;
private final int mEdgeSlop;
private final int mFadingEdgeLength;
@@ -377,6 +374,7 @@ public class ViewConfiguration {
private final int mOverscrollDistance;
private final int mOverflingDistance;
private final boolean mViewTouchScreenHapticScrollFeedbackEnabled;
+ @UnsupportedAppUsage
private final boolean mFadingMarqueeEnabled;
private final long mGlobalActionsKeyTimeout;
private final float mVerticalScrollFactor;
@@ -470,12 +468,14 @@ public class ViewConfiguration {
mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);
mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f);
- mScrollbarSize = res.getDimensionPixelSize(R.dimen.config_scrollbarSize);
+ mScrollbarSize = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_scrollbarSize);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
final TypedValue multiplierValue = new TypedValue();
- res.getValue(R.dimen.config_ambiguousGestureMultiplier,
+ res.getValue(
+ com.android.internal.R.dimen.config_ambiguousGestureMultiplier,
multiplierValue,
true /*resolveRefs*/);
mAmbiguousGestureMultiplier = Math.max(1.0f, multiplierValue.getFloat());
@@ -488,7 +488,8 @@ public class ViewConfiguration {
mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
if (!sHasPermanentMenuKeySet) {
- final int configVal = res.getInteger(R.integer.config_overrideHasPermanentMenuKey);
+ final int configVal = res.getInteger(
+ com.android.internal.R.integer.config_overrideHasPermanentMenuKey);
switch (configVal) {
default:
@@ -515,27 +516,32 @@ public class ViewConfiguration {
}
}
- mFadingMarqueeEnabled = res.getBoolean(R.bool.config_ui_enableFadingMarquee);
- mTouchSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationTouchSlop);
+ mFadingMarqueeEnabled = res.getBoolean(
+ com.android.internal.R.bool.config_ui_enableFadingMarquee);
+ mTouchSlop = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
mHandwritingSlop = res.getDimensionPixelSize(
- R.dimen.config_viewConfigurationHandwritingSlop);
- mHoverSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationHoverSlop);
+ com.android.internal.R.dimen.config_viewConfigurationHandwritingSlop);
+ mHoverSlop = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewConfigurationHoverSlop);
mMinScrollbarTouchTarget = res.getDimensionPixelSize(
- R.dimen.config_minScrollbarTouchTarget);
+ com.android.internal.R.dimen.config_minScrollbarTouchTarget);
mPagingTouchSlop = mTouchSlop * 2;
mDoubleTapTouchSlop = mTouchSlop;
mHandwritingGestureLineMargin = res.getDimensionPixelSize(
- R.dimen.config_viewConfigurationHandwritingGestureLineMargin);
+ com.android.internal.R.dimen.config_viewConfigurationHandwritingGestureLineMargin);
- mMinimumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMinFlingVelocity);
- mMaximumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMaxFlingVelocity);
+ mMinimumFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMinFlingVelocity);
+ mMaximumFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMaxFlingVelocity);
int configMinRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
- R.dimen.config_viewMinRotaryEncoderFlingVelocity);
+ com.android.internal.R.dimen.config_viewMinRotaryEncoderFlingVelocity);
int configMaxRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
- R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
+ com.android.internal.R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
if (configMinRotaryEncoderFlingVelocity < 0 || configMaxRotaryEncoderFlingVelocity < 0) {
mMinimumRotaryEncoderFlingVelocity = NO_FLING_MIN_VELOCITY;
mMaximumRotaryEncoderFlingVelocity = NO_FLING_MAX_VELOCITY;
@@ -545,7 +551,8 @@ public class ViewConfiguration {
}
int configRotaryEncoderHapticScrollFeedbackTickIntervalPixels =
- res.getDimensionPixelSize(R.dimen
+ res.getDimensionPixelSize(
+ com.android.internal.R.dimen
.config_rotaryEncoderAxisScrollTickInterval);
mRotaryEncoderHapticScrollFeedbackTickIntervalPixels =
configRotaryEncoderHapticScrollFeedbackTickIntervalPixels > 0
@@ -553,31 +560,41 @@ public class ViewConfiguration {
: NO_HAPTIC_SCROLL_TICK_INTERVAL;
mRotaryEncoderHapticScrollFeedbackEnabled =
- res.getBoolean(R.bool
+ res.getBoolean(
+ com.android.internal.R.bool
.config_viewRotaryEncoderHapticScrollFedbackEnabled);
- mGlobalActionsKeyTimeout = res.getInteger(R.integer.config_globalActionsKeyTimeout);
+ mGlobalActionsKeyTimeout = res.getInteger(
+ com.android.internal.R.integer.config_globalActionsKeyTimeout);
- mHorizontalScrollFactor = res.getDimensionPixelSize(R.dimen.config_horizontalScrollFactor);
- mVerticalScrollFactor = res.getDimensionPixelSize(R.dimen.config_verticalScrollFactor);
+ mHorizontalScrollFactor = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_horizontalScrollFactor);
+ mVerticalScrollFactor = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_verticalScrollFactor);
mShowMenuShortcutsWhenKeyboardPresent = res.getBoolean(
- R.bool.config_showMenuShortcutsWhenKeyboardPresent);
+ com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
- mMinScalingSpan = res.getDimensionPixelSize(R.dimen.config_minScalingSpan);
+ mMinScalingSpan = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_minScalingSpan);
- mScreenshotChordKeyTimeout = res.getInteger(R.integer.config_screenshotChordKeyTimeout);
+ mScreenshotChordKeyTimeout = res.getInteger(
+ com.android.internal.R.integer.config_screenshotChordKeyTimeout);
mSmartSelectionInitializedTimeout = res.getInteger(
- R.integer.config_smartSelectionInitializedTimeoutMillis);
+ com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis);
mSmartSelectionInitializingTimeout = res.getInteger(
- R.integer.config_smartSelectionInitializingTimeoutMillis);
- mPreferKeepClearForFocusEnabled = res.getBoolean(R.bool.config_preferKeepClearForFocus);
+ com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis);
+ mPreferKeepClearForFocusEnabled = res.getBoolean(
+ com.android.internal.R.bool.config_preferKeepClearForFocus);
mViewBasedRotaryEncoderScrollHapticsEnabledConfig =
- res.getBoolean(R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
+ res.getBoolean(
+ com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
mViewTouchScreenHapticScrollFeedbackEnabled =
Flags.enableScrollFeedbackForTouch()
- ? res.getBoolean(R.bool.config_viewTouchScreenHapticScrollFeedbackEnabled)
+ ? res.getBoolean(
+ com.android.internal.R.bool
+ .config_viewTouchScreenHapticScrollFeedbackEnabled)
: false;
}
@@ -615,7 +632,6 @@ public class ViewConfiguration {
@VisibleForTesting
public static void resetCacheForTesting() {
sConfigurations.clear();
- sResourceCache = new ResourceCache();
}
/**
@@ -691,7 +707,7 @@ public class ViewConfiguration {
* components.
*/
public static int getPressedStateDuration() {
- return sResourceCache.getPressedStateDuration();
+ return PRESSED_STATE_DURATION;
}
/**
@@ -736,7 +752,7 @@ public class ViewConfiguration {
* considered to be a tap.
*/
public static int getTapTimeout() {
- return sResourceCache.getTapTimeout();
+ return TAP_TIMEOUT;
}
/**
@@ -745,7 +761,7 @@ public class ViewConfiguration {
* considered to be a tap.
*/
public static int getJumpTapTimeout() {
- return sResourceCache.getJumpTapTimeout();
+ return JUMP_TAP_TIMEOUT;
}
/**
@@ -754,7 +770,7 @@ public class ViewConfiguration {
* double-tap.
*/
public static int getDoubleTapTimeout() {
- return sResourceCache.getDoubleTapTimeout();
+ return DOUBLE_TAP_TIMEOUT;
}
/**
@@ -766,7 +782,7 @@ public class ViewConfiguration {
*/
@UnsupportedAppUsage
public static int getDoubleTapMinTime() {
- return sResourceCache.getDoubleTapMinTime();
+ return DOUBLE_TAP_MIN_TIME;
}
/**
@@ -776,7 +792,7 @@ public class ViewConfiguration {
* @hide
*/
public static int getHoverTapTimeout() {
- return sResourceCache.getHoverTapTimeout();
+ return HOVER_TAP_TIMEOUT;
}
/**
@@ -787,7 +803,7 @@ public class ViewConfiguration {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static int getHoverTapSlop() {
- return sResourceCache.getHoverTapSlop();
+ return HOVER_TAP_SLOP;
}
/**
@@ -1028,7 +1044,7 @@ public class ViewConfiguration {
* in milliseconds.
*/
public static long getZoomControlsTimeout() {
- return sResourceCache.getZoomControlsTimeout();
+ return ZOOM_CONTROLS_TIMEOUT;
}
/**
@@ -1097,14 +1113,14 @@ public class ViewConfiguration {
* friction.
*/
public static float getScrollFriction() {
- return sResourceCache.getScrollFriction();
+ return SCROLL_FRICTION;
}
/**
* @return the default duration in milliseconds for {@link ActionMode#hide(long)}.
*/
public static long getDefaultActionModeHideDuration() {
- return sResourceCache.getDefaultActionModeHideDuration();
+ return ACTION_MODE_HIDE_DURATION_DEFAULT;
}
/**
@@ -1455,137 +1471,8 @@ public class ViewConfiguration {
return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;
}
- private static int getDisplayDensity(Context context) {
+ private static final int getDisplayDensity(Context context) {
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (100.0f * metrics.density);
}
-
- /**
- * Fetches resource values statically and caches them locally for fast lookup. Note that these
- * values will not be updated during the lifetime of a process, even if resource overlays are
- * applied.
- */
- private static final class ResourceCache {
-
- private int mPressedStateDuration = -1;
- private int mTapTimeout = -1;
- private int mJumpTapTimeout = -1;
- private int mDoubleTapTimeout = -1;
- private int mDoubleTapMinTime = -1;
- private int mHoverTapTimeout = -1;
- private int mHoverTapSlop = -1;
- private long mZoomControlsTimeout = -1L;
- private float mScrollFriction = -1f;
- private long mDefaultActionModeHideDuration = -1L;
-
- public int getPressedStateDuration() {
- if (mPressedStateDuration < 0) {
- Resources resources = getCurrentResources();
- mPressedStateDuration = resources != null
- ? resources.getInteger(R.integer.config_pressedStateDurationMillis)
- : PRESSED_STATE_DURATION;
- }
- return mPressedStateDuration;
- }
-
- public int getTapTimeout() {
- if (mTapTimeout < 0) {
- Resources resources = getCurrentResources();
- mTapTimeout = resources != null
- ? resources.getInteger(R.integer.config_tapTimeoutMillis)
- : TAP_TIMEOUT;
- }
- return mTapTimeout;
- }
-
- public int getJumpTapTimeout() {
- if (mJumpTapTimeout < 0) {
- Resources resources = getCurrentResources();
- mJumpTapTimeout = resources != null
- ? resources.getInteger(R.integer.config_jumpTapTimeoutMillis)
- : JUMP_TAP_TIMEOUT;
- }
- return mJumpTapTimeout;
- }
-
- public int getDoubleTapTimeout() {
- if (mDoubleTapTimeout < 0) {
- Resources resources = getCurrentResources();
- mDoubleTapTimeout = resources != null
- ? resources.getInteger(R.integer.config_doubleTapTimeoutMillis)
- : DOUBLE_TAP_TIMEOUT;
- }
- return mDoubleTapTimeout;
- }
-
- public int getDoubleTapMinTime() {
- if (mDoubleTapMinTime < 0) {
- Resources resources = getCurrentResources();
- mDoubleTapMinTime = resources != null
- ? resources.getInteger(R.integer.config_doubleTapMinTimeMillis)
- : DOUBLE_TAP_MIN_TIME;
- }
- return mDoubleTapMinTime;
- }
-
- public int getHoverTapTimeout() {
- if (mHoverTapTimeout < 0) {
- Resources resources = getCurrentResources();
- mHoverTapTimeout = resources != null
- ? resources.getInteger(R.integer.config_hoverTapTimeoutMillis)
- : HOVER_TAP_TIMEOUT;
- }
- return mHoverTapTimeout;
- }
-
- public int getHoverTapSlop() {
- if (mHoverTapSlop < 0) {
- Resources resources = getCurrentResources();
- mHoverTapSlop = resources != null
- ? resources.getDimensionPixelSize(R.dimen.config_hoverTapSlop)
- : HOVER_TAP_SLOP;
- }
- return mHoverTapSlop;
- }
-
- public long getZoomControlsTimeout() {
- if (mZoomControlsTimeout < 0) {
- Resources resources = getCurrentResources();
- mZoomControlsTimeout = resources != null
- ? resources.getInteger(R.integer.config_zoomControlsTimeoutMillis)
- : ZOOM_CONTROLS_TIMEOUT;
- }
- return mZoomControlsTimeout;
- }
-
- public float getScrollFriction() {
- if (mScrollFriction < 0) {
- Resources resources = getCurrentResources();
- mScrollFriction = resources != null
- ? resources.getFloat(R.dimen.config_scrollFriction)
- : SCROLL_FRICTION;
- }
- return mScrollFriction;
- }
-
- public long getDefaultActionModeHideDuration() {
- if (mDefaultActionModeHideDuration < 0) {
- Resources resources = getCurrentResources();
- mDefaultActionModeHideDuration = resources != null
- ? resources.getInteger(R.integer.config_defaultActionModeHideDurationMillis)
- : ACTION_MODE_HIDE_DURATION_DEFAULT;
- }
- return mDefaultActionModeHideDuration;
- }
-
- private static Resources getCurrentResources() {
- if (!android.companion.virtualdevice.flags.Flags
- .migrateViewconfigurationConstantsToResources()) {
- return null;
- }
- Application application = ActivityThread.currentApplication();
- Context context = application != null ? application.getApplicationContext() : null;
- return context != null ? context.getResources() : null;
- }
- }
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index d413ba0b042c..09c6dc0e2b20 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -572,6 +572,13 @@ flag {
}
flag {
+ name: "enable_display_reconnect_interaction"
+ namespace: "lse_desktop_experience"
+ description: "Enables new interaction that occurs when a display is reconnected."
+ bug: "365873835"
+}
+
+flag {
name: "show_desktop_experience_dev_option"
namespace: "lse_desktop_experience"
description: "Replace the freeform windowing dev options with a desktop experience one."
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index a5e166b95177..5f1a641945e8 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -35,8 +35,20 @@ import android.telephony.SignalStrength;
interface IBatteryStats {
/** @hide */
+ const int RESULT_OK = 0;
+
+ /** @hide */
+ const int RESULT_RUNTIME_EXCEPTION = 1;
+
+ /** @hide */
+ const int RESULT_SECURITY_EXCEPTION = 2;
+
+ /** @hide */
const String KEY_UID_SNAPSHOTS = "uid_snapshots";
+ /** @hide */
+ const String KEY_EXCEPTION_MESSAGE = "exception";
+
// These first methods are also called by native code, so must
// be kept in sync with frameworks/native/libs/binder/include_batterystats/batterystats/IBatteryStats.h
@EnforcePermission("UPDATE_DEVICE_STATS")
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 270cf085b06f..e20a52b24485 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -231,6 +231,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
private int mLastRightInset = 0;
@UnsupportedAppUsage
private int mLastLeftInset = 0;
+ private WindowInsets mLastInsets = null;
private boolean mLastHasTopStableInset = false;
private boolean mLastHasBottomStableInset = false;
private boolean mLastHasRightStableInset = false;
@@ -1100,6 +1101,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
mLastWindowFlags = attrs.flags;
if (insets != null) {
+ mLastInsets = insets;
mLastForceConsumingTypes = insets.getForceConsumingTypes();
mLastForceConsumingOpaqueCaptionBar = insets.isForceConsumingOpaqueCaptionBar();
@@ -1176,6 +1178,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
mForceWindowDrawsBarBackgrounds, requestedVisibleTypes);
}
+ int consumingTypes = 0;
// When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
// mForceWindowDrawsBarBackgrounds, we still need to ensure that the rest of the view
// hierarchy doesn't notice it, unless they've explicitly asked for it.
@@ -1186,43 +1189,47 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
//
// Note: Once the app uses the R+ Window.setDecorFitsSystemWindows(false) API we no longer
// consume insets because they might no longer set SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.
- boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
+ final boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
|| (requestedVisibleTypes & WindowInsets.Type.navigationBars()) == 0;
- boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows;
- boolean forceConsumingNavBar =
+ final boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows;
+
+ final boolean fitsNavBar =
+ (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+ && decorFitsSystemWindows
+ && !hideNavigation;
+ final boolean forceConsumingNavBar =
((mForceWindowDrawsBarBackgrounds || mDrawLegacyNavigationBarBackgroundHandled)
&& (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
- && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
- && decorFitsSystemWindows
- && !hideNavigation)
+ && fitsNavBar)
|| ((mLastForceConsumingTypes & WindowInsets.Type.navigationBars()) != 0
&& hideNavigation);
-
- boolean consumingNavBar =
- ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
- && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
- && decorFitsSystemWindows
- && !hideNavigation)
+ final boolean consumingNavBar =
+ ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 && fitsNavBar)
|| forceConsumingNavBar;
+ if (consumingNavBar) {
+ consumingTypes |= WindowInsets.Type.navigationBars();
+ }
- // If we didn't request fullscreen layout, but we still got it because of the
- // mForceWindowDrawsBarBackgrounds flag, also consume top inset.
+ // If the fullscreen layout was not requested, but still received because of the
+ // mForceWindowDrawsBarBackgrounds flag, also consume status bar.
// If we should always consume system bars, only consume that if the app wanted to go to
// fullscreen, as otherwise we can expect the app to handle it.
- boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
+ final boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
|| (attrs.flags & FLAG_FULLSCREEN) != 0;
final boolean hideStatusBar = fullscreen
|| (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
- boolean consumingStatusBar =
- ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
- && decorFitsSystemWindows
- && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
- && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
- && mForceWindowDrawsBarBackgrounds
- && mLastTopInset != 0)
+ if (((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
+ && decorFitsSystemWindows
+ && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
+ && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
+ && mForceWindowDrawsBarBackgrounds
+ && mLastTopInset != 0)
|| ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0
- && hideStatusBar);
+ && hideStatusBar)) {
+ consumingTypes |= WindowInsets.Type.statusBars();
+ }
+ // Decide if caption bar need to be consumed
final boolean hideCaptionBar = fullscreen
|| (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;
final boolean consumingCaptionBar =
@@ -1237,22 +1244,23 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
&& mLastForceConsumingOpaqueCaptionBar
&& isOpaqueCaptionBar;
- final int consumedTop =
- (consumingStatusBar || consumingCaptionBar || consumingOpaqueCaptionBar)
- ? mLastTopInset : 0;
- int consumedRight = consumingNavBar ? mLastRightInset : 0;
- int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
- int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
+ if (consumingCaptionBar || consumingOpaqueCaptionBar) {
+ consumingTypes |= WindowInsets.Type.captionBar();
+ }
+
+ final Insets consumedInsets = mLastInsets != null
+ ? mLastInsets.getInsets(consumingTypes) : Insets.NONE;
if (mContentRoot != null
&& mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
- if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight
- || lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) {
- lp.topMargin = consumedTop;
- lp.rightMargin = consumedRight;
- lp.bottomMargin = consumedBottom;
- lp.leftMargin = consumedLeft;
+ if (lp.topMargin != consumedInsets.top || lp.rightMargin != consumedInsets.right
+ || lp.bottomMargin != consumedInsets.bottom || lp.leftMargin !=
+ consumedInsets.left) {
+ lp.topMargin = consumedInsets.top;
+ lp.rightMargin = consumedInsets.right;
+ lp.bottomMargin = consumedInsets.bottom;
+ lp.leftMargin = consumedInsets.left;
mContentRoot.setLayoutParams(lp);
if (insets == null) {
@@ -1261,11 +1269,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
requestApplyInsets();
}
}
- if (insets != null && (consumedLeft > 0
- || consumedTop > 0
- || consumedRight > 0
- || consumedBottom > 0)) {
- insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom);
+ if (insets != null && !Insets.NONE.equals(consumedInsets)) {
+ insets = insets.inset(consumedInsets);
}
}
diff --git a/core/res/res/values-w225dp/dimens.xml b/core/res/res/values-w225dp/dimens.xml
new file mode 100644
index 000000000000..0cd3293f0894
--- /dev/null
+++ b/core/res/res/values-w225dp/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 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
+ ~
+ ~ https://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.
+ -->
+<resources>
+ <!-- The width of the round scrollbar -->
+ <dimen name="round_scrollbar_width">6dp</dimen>
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 37e553ea3827..b3581d98face 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3061,43 +3061,6 @@
{@link MotionEvent#ACTION_SCROLL} event. -->
<dimen name="config_scrollFactor">64dp</dimen>
- <!-- Duration in milliseconds of the pressed state in child components. -->
- <integer name="config_pressedStateDurationMillis">64</integer>
-
- <!-- Duration in milliseconds we will wait to see if a touch event is a tap or a scroll.
- If the user does not move within this interval, it is considered to be a tap. -->
- <integer name="config_tapTimeoutMillis">100</integer>
-
- <!-- Duration in milliseconds we will wait to see if a touch event is a jump tap.
- If the user does not move within this interval, it is considered to be a tap. -->
- <integer name="config_jumpTapTimeoutMillis">500</integer>
-
- <!-- Duration in milliseconds between the first tap's up event and the second tap's down
- event for an interaction to be considered a double-tap. -->
- <integer name="config_doubleTapTimeoutMillis">300</integer>
-
- <!-- Minimum duration in milliseconds between the first tap's up event and the second tap's
- down event for an interaction to be considered a double-tap. -->
- <integer name="config_doubleTapMinTimeMillis">40</integer>
-
- <!-- Maximum duration in milliseconds between a touch pad touch and release for a given touch
- to be considered a tap (click) as opposed to a hover movement gesture. -->
- <integer name="config_hoverTapTimeoutMillis">150</integer>
-
- <!-- The amount of time in milliseconds that the zoom controls should be displayed on the
- screen. -->
- <integer name="config_zoomControlsTimeoutMillis">3000</integer>
-
- <!-- Default duration in milliseconds for {@link ActionMode#hide(long)}. -->
- <integer name="config_defaultActionModeHideDurationMillis">2000</integer>
-
- <!-- Maximum distance in pixels that a touch pad touch can move before being released
- for it to be considered a tap (click) as opposed to a hover movement gesture. -->
- <dimen name="config_hoverTapSlop">20px</dimen>
-
- <!-- The amount of friction applied to scrolls and flings. -->
- <item name="config_scrollFriction" format="float" type="dimen">0.015</item>
-
<!-- Maximum number of grid columns permitted in the ResolverActivity
used for picking activities to handle an intent. -->
<integer name="config_maxResolverActivityColumns">3</integer>
@@ -3299,6 +3262,14 @@
as private. {@see android.view.Display#FLAG_PRIVATE} -->
<integer-array translatable="false" name="config_localPrivateDisplayPorts"></integer-array>
+ <!-- Controls if local secondary displays should be able to steal focus and become top display.
+ Value specified in the array represents physical port address of each display and displays
+ in this list due to flag dependencies will be marked with the following flags:
+ {@see android.view.Display#FLAG_STEAL_TOP_FOCUS_DISABLED}
+ {@see android.view.Display#FLAG_OWN_FOCUS} -->
+ <integer-array translatable="false" name="config_localNotStealTopFocusDisplayPorts">
+ </integer-array>
+
<!-- The default mode for the default display. One of the following values (See Display.java):
0 - COLOR_MODE_DEFAULT
7 - COLOR_MODE_SRGB
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 484e8ef1e049..595160ec9f66 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -782,6 +782,9 @@
aliasing effects). This is only used on circular displays. -->
<dimen name="circular_display_mask_thickness">1px</dimen>
+ <!-- The width of the round scrollbar -->
+ <dimen name="round_scrollbar_width">5dp</dimen>
+
<dimen name="lock_pattern_dot_line_width">22dp</dimen>
<dimen name="lock_pattern_dot_size">14dp</dimen>
<dimen name="lock_pattern_dot_size_activated">30dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a34fbb89c629..9393aa4b6086 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -430,6 +430,7 @@
<java-symbol type="bool" name="config_enableProximityService" />
<java-symbol type="bool" name="config_enableVirtualDeviceManager" />
<java-symbol type="array" name="config_localPrivateDisplayPorts" />
+ <java-symbol type="array" name="config_localNotStealTopFocusDisplayPorts" />
<java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" />
<java-symbol type="bool" name="config_enableAppWidgetService" />
<java-symbol type="dimen" name="config_pictureInPictureMinAspectRatio" />
@@ -585,6 +586,7 @@
<java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
<java-symbol type="dimen" name="circular_display_mask_thickness" />
<java-symbol type="dimen" name="user_icon_size" />
+ <java-symbol type="dimen" name="round_scrollbar_width" />
<java-symbol type="string" name="add_account_button_label" />
<java-symbol type="string" name="addToDictionary" />
@@ -4153,17 +4155,6 @@
<java-symbol type="string" name="config_headlineFontFamily" />
<java-symbol type="string" name="config_headlineFontFamilyMedium" />
- <java-symbol type="integer" name="config_pressedStateDurationMillis" />
- <java-symbol type="integer" name="config_tapTimeoutMillis" />
- <java-symbol type="integer" name="config_jumpTapTimeoutMillis" />
- <java-symbol type="integer" name="config_doubleTapTimeoutMillis" />
- <java-symbol type="integer" name="config_doubleTapMinTimeMillis" />
- <java-symbol type="integer" name="config_hoverTapTimeoutMillis" />
- <java-symbol type="integer" name="config_zoomControlsTimeoutMillis" />
- <java-symbol type="integer" name="config_defaultActionModeHideDurationMillis" />
- <java-symbol type="dimen" name="config_hoverTapSlop" />
- <java-symbol type="dimen" name="config_scrollFriction" />
-
<java-symbol type="drawable" name="stat_sys_vitals" />
<java-symbol type="color" name="text_color_primary" />
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
index fdfb0c34cdeb..fa1948d9786c 100644
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -238,4 +239,42 @@ public class IntentTest {
// Not all keys from intent are kept - clip data keys are dropped.
assertFalse(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
}
+
+ @Test
+ public void testSetIntentExtrasClassLoaderEffectiveAfterExtraBundleUnparcel() {
+ Intent intent = new Intent();
+ intent.putExtra("bundle", new Bundle());
+
+ final Parcel parcel = Parcel.obtain();
+ intent.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ final Intent target = new Intent();
+ target.readFromParcel(parcel);
+ target.collectExtraIntentKeys();
+ ClassLoader cl = new ClassLoader() {
+ };
+ target.setExtrasClassLoader(cl);
+ assertThat(target.getBundleExtra("bundle").getClassLoader()).isEqualTo(cl);
+ }
+
+ @Test
+ public void testBundlePutAllClassLoader() {
+ Intent intent = new Intent();
+ Bundle b1 = new Bundle();
+ b1.putBundle("bundle", new Bundle());
+ intent.putExtra("b1", b1);
+ final Parcel parcel = Parcel.obtain();
+ intent.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ final Intent target = new Intent();
+ target.readFromParcel(parcel);
+
+ ClassLoader cl = new ClassLoader() {
+ };
+ target.setExtrasClassLoader(cl);
+ Bundle b2 = new Bundle();
+ b2.putAll(target.getBundleExtra("b1"));
+ assertThat(b2.getBundle("bundle").getClassLoader()).isEqualTo(cl);
+ }
+
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
new file mode 100644
index 000000000000..29ce8d90e66f
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.shared.bubbles
+
+/**
+ * Manages animating drop targets in response to dragging bubble icons or bubble expanded views
+ * across different drag zones.
+ */
+class DropTargetManager(
+ private val isLayoutRtl: Boolean,
+ private val dragZoneChangedListener: DragZoneChangedListener
+) {
+
+ private var state: DragState? = null
+
+ /** Must be called when a drag gesture is starting. */
+ fun onDragStarted(draggedObject: DraggedObject, dragZones: List<DragZone>) {
+ val state = DragState(dragZones, draggedObject)
+ dragZoneChangedListener.onInitialDragZoneSet(state.initialDragZone)
+ this.state = state
+ }
+
+ /** Called when the user drags to a new location. */
+ fun onDragUpdated(x: Int, y: Int) {
+ val state = state ?: return
+ val oldDragZone = state.currentDragZone
+ val newDragZone = state.getMatchingDragZone(x = x, y = y)
+ state.currentDragZone = newDragZone
+ if (oldDragZone != newDragZone) {
+ dragZoneChangedListener.onDragZoneChanged(from = oldDragZone, to = newDragZone)
+ }
+ }
+
+ /** Called when the drag ended. */
+ fun onDragEnded() {
+ state = null
+ }
+
+ /** Stores the current drag state. */
+ private inner class DragState(
+ private val dragZones: List<DragZone>,
+ draggedObject: DraggedObject
+ ) {
+ val initialDragZone =
+ if (draggedObject.initialLocation.isOnLeft(isLayoutRtl)) {
+ dragZones.filterIsInstance<DragZone.Bubble.Left>().first()
+ } else {
+ dragZones.filterIsInstance<DragZone.Bubble.Right>().first()
+ }
+ var currentDragZone: DragZone = initialDragZone
+
+ fun getMatchingDragZone(x: Int, y: Int): DragZone {
+ return dragZones.firstOrNull { it.contains(x, y) } ?: currentDragZone
+ }
+ }
+
+ /** An interface to be notified when drag zones change. */
+ interface DragZoneChangedListener {
+ /** An initial drag zone was set. Called when a drag starts. */
+ fun onInitialDragZoneSet(dragZone: DragZone)
+ /** Called when the object was dragged to a different drag zone. */
+ fun onDragZoneChanged(from: DragZone, to: DragZone)
+ }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java
index 4127adc1f901..12938db07ece 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java
@@ -24,4 +24,9 @@ public class DragAndDropConstants {
* ignore drag events.
*/
public static final String EXTRA_DISALLOW_HIT_REGION = "DISALLOW_HIT_REGION";
+
+ /**
+ * An Intent extra that Launcher can use to specify the {@link android.content.pm.ShortcutInfo}
+ */
+ public static final String EXTRA_SHORTCUT_INFO = "EXTRA_SHORTCUT_INFO";
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index ddcdf9f8c617..d9489287ff42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -54,6 +54,7 @@ import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.Flags;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.common.ComponentUtils;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.bubbles.BubbleInfo;
@@ -282,6 +283,29 @@ public class Bubble implements BubbleViewProvider {
mPackageName = intent.getPackage();
}
+ private Bubble(
+ PendingIntent intent,
+ UserHandle user,
+ String key,
+ @ShellMainThread Executor mainExecutor,
+ @ShellBackgroundThread Executor bgExecutor) {
+ mGroupKey = null;
+ mLocusId = null;
+ mFlags = 0;
+ mUser = user;
+ mIcon = null;
+ mType = BubbleType.TYPE_APP;
+ mKey = key;
+ mShowBubbleUpdateDot = false;
+ mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
+ mTaskId = INVALID_TASK_ID;
+ mPendingIntent = intent;
+ mIntent = null;
+ mDesiredHeight = Integer.MAX_VALUE;
+ mPackageName = ComponentUtils.getPackageName(intent);
+ }
+
private Bubble(ShortcutInfo info, @ShellMainThread Executor mainExecutor,
@ShellBackgroundThread Executor bgExecutor) {
mGroupKey = null;
@@ -336,6 +360,15 @@ public class Bubble implements BubbleViewProvider {
}
/** Creates an app bubble. */
+ public static Bubble createAppBubble(PendingIntent intent, UserHandle user,
+ @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
+ return new Bubble(intent,
+ user,
+ /* key= */ getAppBubbleKeyForApp(ComponentUtils.getPackageName(intent), user),
+ mainExecutor, bgExecutor);
+ }
+
+ /** Creates an app bubble. */
public static Bubble createAppBubble(Intent intent, UserHandle user, @Nullable Icon icon,
@ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
return new Bubble(intent,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index b93b7b86e661..8cf2370df48d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -46,6 +46,7 @@ import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.app.TaskInfo;
import android.content.BroadcastReceiver;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -118,6 +119,7 @@ import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider;
import com.android.wm.shell.shared.bubbles.DeviceConfig;
+import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -872,11 +874,19 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void onItemDroppedOverBubbleBarDragZone(BubbleBarLocation location, Intent appIntent,
- UserHandle userHandle) {
- if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
- hideBubbleBarExpandedViewDropTarget();
- expandStackAndSelectBubble(appIntent, userHandle, location);
+ public void onItemDroppedOverBubbleBarDragZone(BubbleBarLocation location, Intent itemIntent) {
+ hideBubbleBarExpandedViewDropTarget();
+ ShortcutInfo shortcutInfo = (ShortcutInfo) itemIntent
+ .getExtra(DragAndDropConstants.EXTRA_SHORTCUT_INFO);
+ if (shortcutInfo != null) {
+ expandStackAndSelectBubble(shortcutInfo, location);
+ return;
+ }
+ UserHandle user = (UserHandle) itemIntent.getExtra(Intent.EXTRA_USER);
+ PendingIntent pendingIntent = (PendingIntent) itemIntent
+ .getExtra(ClipDescription.EXTRA_PENDING_INTENT);
+ if (pendingIntent != null && user != null) {
+ expandStackAndSelectBubble(pendingIntent, user, location);
}
}
@@ -1506,9 +1516,16 @@ public class BubbleController implements ConfigurationChangeListener,
* Expands and selects a bubble created or found via the provided shortcut info.
*
* @param info the shortcut info for the bubble.
+ * @param bubbleBarLocation optional location in case bubble bar should be repositioned.
*/
- public void expandStackAndSelectBubble(ShortcutInfo info) {
+ public void expandStackAndSelectBubble(ShortcutInfo info,
+ @Nullable BubbleBarLocation bubbleBarLocation) {
if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
+ if (bubbleBarLocation != null) {
+ //TODO (b/388894910) combine location update with the setSelectedBubbleAndExpandStack &
+ // fix bubble bar flicking
+ setBubbleBarLocation(bubbleBarLocation, BubbleBarLocation.UpdateSource.APP_ICON_DRAG);
+ }
Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info);
if (b.isInflated()) {
@@ -1524,7 +1541,25 @@ public class BubbleController implements ConfigurationChangeListener,
*
* @param intent the intent for the bubble.
*/
- public void expandStackAndSelectBubble(Intent intent, UserHandle user,
+ public void expandStackAndSelectBubble(Intent intent, UserHandle user) {
+ if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
+ Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow
+ ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
+ if (b.isInflated()) {
+ mBubbleData.setSelectedBubbleAndExpandStack(b);
+ } else {
+ b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+ inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+ }
+ }
+
+ /**
+ * Expands and selects a bubble created or found for this app.
+ *
+ * @param pendingIntent the intent for the bubble.
+ * @param bubbleBarLocation optional location in case bubble bar should be repositioned.
+ */
+ public void expandStackAndSelectBubble(PendingIntent pendingIntent, UserHandle user,
@Nullable BubbleBarLocation bubbleBarLocation) {
if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
if (bubbleBarLocation != null) {
@@ -1532,8 +1567,9 @@ public class BubbleController implements ConfigurationChangeListener,
// fix bubble bar flicking
setBubbleBarLocation(bubbleBarLocation, BubbleBarLocation.UpdateSource.APP_ICON_DRAG);
}
- Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow
- ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
+ Bubble b = mBubbleData.getOrCreateBubble(pendingIntent, user);
+ ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - pendingIntent=%s",
+ pendingIntent);
if (b.isInflated()) {
mBubbleData.setSelectedBubbleAndExpandStack(b);
} else {
@@ -2756,13 +2792,13 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void showShortcutBubble(ShortcutInfo info) {
- mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(info));
+ mMainExecutor.execute(() -> mController
+ .expandStackAndSelectBubble(info, /* bubbleBarLocation = */ null));
}
@Override
public void showAppBubble(Intent intent, UserHandle user) {
- mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent,
- user, /* bubbleBarLocation = */ null));
+ mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent, user));
}
@Override
@@ -2983,9 +3019,10 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void expandStackAndSelectBubble(ShortcutInfo info) {
- mMainExecutor.execute(() -> {
- BubbleController.this.expandStackAndSelectBubble(info);
- });
+ mMainExecutor.execute(() ->
+ BubbleController.this
+ .expandStackAndSelectBubble(info, /* bubbleBarLocation = */ null)
+ );
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 96d0f6d5654e..f97133a4c3d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -471,6 +471,16 @@ public class BubbleData {
return bubbleToReturn;
}
+ Bubble getOrCreateBubble(PendingIntent pendingIntent, UserHandle user) {
+ String bubbleKey = Bubble.getAppBubbleKeyForApp(pendingIntent.getCreatorPackage(), user);
+ Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
+ if (bubbleToReturn == null) {
+ bubbleToReturn = Bubble.createAppBubble(pendingIntent, user, mMainExecutor,
+ mBgExecutor);
+ }
+ return bubbleToReturn;
+ }
+
Bubble getOrCreateBubble(TaskInfo taskInfo) {
UserHandle user = UserHandle.of(mCurrentUserId);
String bubbleKey = Bubble.getAppBubbleKeyForTask(taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 4a0eee861d21..e47ac61a53dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -117,15 +117,24 @@ public class BubbleTaskViewHelper {
Context context =
mContext.createContextAsUser(
mBubble.getUser(), Context.CONTEXT_RESTRICTED);
- PendingIntent pi = PendingIntent.getActivity(
- context,
- /* requestCode= */ 0,
- mBubble.getIntent()
- .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
- /* options= */ null);
- mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
- launchBounds);
+ Intent fillInIntent = null;
+ //first try get pending intent from the bubble
+ PendingIntent pi = mBubble.getPendingIntent();
+ if (pi == null) {
+ // if null - create new one
+ pi = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ mBubble.getIntent()
+ .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
+ PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT,
+ /* options= */ null);
+ } else {
+ fillInIntent = new Intent(pi.getIntent());
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
} else if (isShortcutBubble) {
options.setLaunchedFromBubble(true);
options.setApplyActivityFlagsForBubbles(true);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt
index afe5c87604d9..3ff80b5ab8ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.bubbles.bar
import android.content.Intent
import android.graphics.Rect
-import android.os.UserHandle
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
/** Controller that takes care of the bubble bar drag events. */
@@ -31,11 +30,7 @@ interface BubbleBarDragListener {
fun onItemDraggedOutsideBubbleBarDropZone()
/** Called when the drop event happens over the bubble bar drop zone. */
- fun onItemDroppedOverBubbleBarDragZone(
- location: BubbleBarLocation,
- intent: Intent,
- userHandle: UserHandle
- )
+ fun onItemDroppedOverBubbleBarDragZone(location: BubbleBarLocation, itemIntent: Intent)
/**
* Returns mapping of the bubble bar locations to the corresponding
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 3c7780711a14..7491abd4248b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -343,10 +343,22 @@ class DesktopTasksController(
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
.isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
) {
+ logV(
+ "isDesktopModeShowing: hasVisibleTasks=%s hasTopTransparentFullscreenTask=%s hasMinimizedPip=%s",
+ hasVisibleTasks,
+ hasTopTransparentFullscreenTask,
+ hasMinimizedPip,
+ )
return hasVisibleTasks || hasTopTransparentFullscreenTask || hasMinimizedPip
} else if (Flags.enableDesktopWindowingPip()) {
+ logV(
+ "isDesktopModeShowing: hasVisibleTasks=%s hasMinimizedPip=%s",
+ hasVisibleTasks,
+ hasMinimizedPip,
+ )
return hasVisibleTasks || hasMinimizedPip
}
+ logV("isDesktopModeShowing: hasVisibleTasks=%s", hasVisibleTasks)
return hasVisibleTasks
}
@@ -3074,6 +3086,7 @@ class DesktopTasksController(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
pendingIntentLaunchFlags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+ splashScreenStyle = SPLASH_SCREEN_STYLE_ICON
}
if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
dragAndDropFullscreenCookie = Binder()
@@ -3082,7 +3095,12 @@ class DesktopTasksController(
val wct = WindowContainerTransaction()
wct.sendPendingIntent(launchIntent, null, opts.toBundle())
if (windowingMode == WINDOWING_MODE_FREEFORM) {
- desktopModeDragAndDropTransitionHandler.handleDropEvent(wct)
+ if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) {
+ // TODO b/376389593: Use a custom tab tearing transition/animation
+ startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null)
+ } else {
+ desktopModeDragAndDropTransitionHandler.handleDropEvent(wct)
+ }
} else {
transitions.startTransition(TRANSIT_OPEN, wct, null)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 2571e0e36cd9..b3c1a92f5e1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -47,7 +47,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
import android.view.DragEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -70,6 +69,7 @@ import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -627,8 +627,7 @@ public class DragLayout extends LinearLayout
@Nullable
private BubbleBarLocation getBubbleBarLocation(int x, int y) {
Intent appData = mSession.appData;
- if (appData == null || appData.getExtra(Intent.EXTRA_INTENT) == null
- || appData.getExtra(Intent.EXTRA_USER) == null) {
+ if (appData == null) {
// there is no app data, so drop event over the bubble bar can not be handled
return null;
}
@@ -686,11 +685,10 @@ public class DragLayout extends LinearLayout
// Process the drop exclusive by DropTarget OR by the BubbleBar
if (mCurrentTarget != null) {
mPolicy.onDropped(mCurrentTarget, hideTaskToken);
- } else if (appData != null && mCurrentBubbleBarTarget != null) {
- Intent appIntent = (Intent) appData.getExtra(Intent.EXTRA_INTENT);
- UserHandle user = (UserHandle) appData.getExtra(Intent.EXTRA_USER);
+ } else if (appData != null && mCurrentBubbleBarTarget != null
+ && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
mBubbleBarDragListener.onItemDroppedOverBubbleBarDragZone(mCurrentBubbleBarTarget,
- appIntent, user);
+ appData);
}
// Start animating the drop UI out with the drag surface
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 8ad2e1d3c7c9..ae8f8c4eff79 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
@@ -317,7 +317,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
"RecentsTransitionHandler.mergeAnimation: no controller found");
return;
}
- controller.merge(info, startT, mergeTarget, finishCallback);
+ controller.merge(info, startT, finishT, mergeTarget, finishCallback);
}
@Override
@@ -912,7 +912,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
* before any unhandled transitions.
*/
@SuppressLint("NewApi")
- void merge(TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget,
+ void merge(TransitionInfo info, SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
if (mFinishCB == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -1072,8 +1073,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
Slog.e(TAG, "Returning to recents without closing any opening tasks.");
}
// Setup may hide it initially since it doesn't know that overview was still active.
- t.show(recentsOpening.getLeash());
- t.setAlpha(recentsOpening.getLeash(), 1.f);
+ startT.show(recentsOpening.getLeash());
+ startT.setAlpha(recentsOpening.getLeash(), 1.f);
mState = STATE_NORMAL;
}
boolean didMergeThings = false;
@@ -1142,31 +1143,31 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mOpeningTasks.add(pausingTask);
// Setup hides opening tasks initially, so make it visible again (since we
// are already showing it).
- t.show(change.getLeash());
- t.setAlpha(change.getLeash(), 1.f);
+ startT.show(change.getLeash());
+ startT.setAlpha(change.getLeash(), 1.f);
} else if (isLeaf) {
// We are receiving new opening leaf tasks, so convert to onTasksAppeared.
final RemoteAnimationTarget target = TransitionUtil.newTarget(
- change, layer, info, t, mLeashMap);
+ change, layer, info, startT, mLeashMap);
appearedTargets[nextTargetIdx++] = target;
// reparent into the original `mInfo` since that's where we are animating.
final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo);
final boolean wasClosing = closingIdx >= 0;
- t.reparent(target.leash, root.getLeash());
- t.setPosition(target.leash,
+ startT.reparent(target.leash, root.getLeash());
+ startT.setPosition(target.leash,
change.getStartAbsBounds().left - root.getOffset().x,
change.getStartAbsBounds().top - root.getOffset().y);
- t.setLayer(target.leash, layer);
+ startT.setLayer(target.leash, layer);
if (wasClosing) {
// App was previously visible and is closing
- t.show(target.leash);
- t.setAlpha(target.leash, 1f);
+ startT.show(target.leash);
+ startT.setAlpha(target.leash, 1f);
// Also override the task alpha as it was set earlier when dispatching
// the transition and setting up the leash to hide the
- t.setAlpha(change.getLeash(), 1f);
+ startT.setAlpha(change.getLeash(), 1f);
} else {
// Hide the animation leash, let the listener show it
- t.hide(target.leash);
+ startT.hide(target.leash);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
" opening new leaf taskId=%d wasClosing=%b",
@@ -1175,10 +1176,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
" opening new taskId=%d", change.getTaskInfo().taskId);
- t.setLayer(change.getLeash(), layer);
+ startT.setLayer(change.getLeash(), layer);
// Setup hides opening tasks initially, so make it visible since recents
// is only animating the leafs.
- t.show(change.getLeash());
+ startT.show(change.getLeash());
mOpeningTasks.add(new TaskState(change, null));
}
}
@@ -1194,7 +1195,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
// Activity only transition, so consume the merge as it doesn't affect the rest of
// recents.
Slog.d(TAG, "Got an activity only transition during recents, so apply directly");
- mergeActivityOnly(info, t);
+ mergeActivityOnly(info, startT);
} else if (!didMergeThings) {
// Didn't recognize anything in incoming transition so don't merge it.
Slog.w(TAG, "Don't know how to merge this transition, foundRecentsClosing="
@@ -1206,7 +1207,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
return;
}
// At this point, we are accepting the merge.
- t.apply();
+ startT.apply();
+ // Since we're accepting the merge, update the finish transaction so that changes via
+ // that transaction will be applied on top of those of the merged transitions
+ mFinishTransaction = finishT;
// not using the incoming anim-only surfaces
info.releaseAnimSurfaces();
if (appearedTargets != null) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index edb9b2d2fede..cd1c16a93475 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -5427,38 +5427,90 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun onUnhandledDrag_newFreeformIntent() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFreeformIntent_tabTearingAnimationBugfixFlagEnabled() {
testOnUnhandledDrag(
DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
PointF(1200f, 700f),
Rect(240, 700, 2160, 1900),
+ tabTearingAnimationFlagEnabled = true,
)
}
@Test
- fun onUnhandledDrag_newFreeformIntentSplitLeft() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFreeformIntent_tabTearingAnimationBugfixFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ PointF(1200f, 700f),
+ Rect(240, 700, 2160, 1900),
+ tabTearingAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFreeformIntentSplitLeft_tabTearingAnimationBugfixFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ PointF(50f, 700f),
+ Rect(0, 0, 500, 1000),
+ tabTearingAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFreeformIntentSplitLeft_tabTearingAnimationBugfixFlagDisabled() {
testOnUnhandledDrag(
DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
PointF(50f, 700f),
Rect(0, 0, 500, 1000),
+ tabTearingAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFreeformIntentSplitRight_tabTearingAnimationBugfixFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ PointF(2500f, 700f),
+ Rect(500, 0, 1000, 1000),
+ tabTearingAnimationFlagEnabled = true,
)
}
@Test
- fun onUnhandledDrag_newFreeformIntentSplitRight() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFreeformIntentSplitRight_tabTearingAnimationBugfixFlagDisabled() {
testOnUnhandledDrag(
DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
PointF(2500f, 700f),
Rect(500, 0, 1000, 1000),
+ tabTearingAnimationFlagEnabled = false,
)
}
@Test
- fun onUnhandledDrag_newFullscreenIntent() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFullscreenIntent_tabTearingAnimationBugfixFlagEnabled() {
testOnUnhandledDrag(
DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
PointF(1200f, 50f),
Rect(),
+ tabTearingAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFullscreenIntent_tabTearingAnimationBugfixFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ PointF(1200f, 50f),
+ Rect(),
+ tabTearingAnimationFlagEnabled = false,
)
}
@@ -5812,6 +5864,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
indicatorType: DesktopModeVisualIndicator.IndicatorType,
inputCoordinate: PointF,
expectedBounds: Rect,
+ tabTearingAnimationFlagEnabled: Boolean,
) {
setUpLandscapeDisplay()
val task = setUpFreeformTask()
@@ -5842,6 +5895,16 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
anyOrNull(),
eq(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT),
)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
spyController.onUnhandledDrag(
mockPendingIntent,
@@ -5858,8 +5921,19 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
verify(transitions).startTransition(any(), capture(arg), anyOrNull())
} else {
expectedWindowingMode = WINDOWING_MODE_FREEFORM
- // All other launches use a special handler.
- verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
+ if (tabTearingAnimationFlagEnabled) {
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ capture(arg),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ } else {
+ // All other launches use a special handler.
+ verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
+ }
}
assertThat(
ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
new file mode 100644
index 000000000000..efb91c5fbfda
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.shared.bubbles
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFails
+
+/** Unit tests for [DropTargetManager]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DropTargetManagerTest {
+
+ private lateinit var dropTargetManager: DropTargetManager
+ private lateinit var dragZoneChangedListener: FakeDragZoneChangedListener
+ private val dropTarget = Rect(0, 0, 0, 0)
+
+ // create 3 drop zones that are horizontally next to each other
+ // -------------------------------------------------
+ // | | | |
+ // | bubble | | bubble |
+ // | | dismiss | |
+ // | left | | right |
+ // | | | |
+ // -------------------------------------------------
+ private val bubbleLeftDragZone =
+ DragZone.Bubble.Left(bounds = Rect(0, 0, 100, 100), dropTarget = dropTarget)
+ private val dismissDragZone = DragZone.Dismiss(bounds = Rect(100, 0, 200, 100))
+ private val bubbleRightDragZone =
+ DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = dropTarget)
+
+ @Before
+ fun setUp() {
+ dragZoneChangedListener = FakeDragZoneChangedListener()
+ dropTargetManager = DropTargetManager(isLayoutRtl = false, dragZoneChangedListener)
+ }
+
+ @Test
+ fun onDragStarted_notifiesInitialDragZone() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ assertThat(dragZoneChangedListener.initialDragZone).isEqualTo(bubbleLeftDragZone)
+ }
+
+ @Test
+ fun onDragStarted_missingExpectedDragZone_fails() {
+ assertFails {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.RIGHT),
+ listOf(bubbleLeftDragZone)
+ )
+ }
+ }
+
+ @Test
+ fun onDragUpdated_notifiesDragZoneChanged() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
+ )
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone)
+ assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleRightDragZone)
+
+ dropTargetManager.onDragUpdated(
+ dismissDragZone.bounds.centerX(),
+ dismissDragZone.bounds.centerY()
+ )
+ assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleRightDragZone)
+ assertThat(dragZoneChangedListener.toDragZone).isEqualTo(dismissDragZone)
+ }
+
+ @Test
+ fun onDragUpdated_withinSameZone_doesNotNotify() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
+ )
+ dropTargetManager.onDragUpdated(
+ bubbleLeftDragZone.bounds.centerX(),
+ bubbleLeftDragZone.bounds.centerY()
+ )
+ assertThat(dragZoneChangedListener.fromDragZone).isNull()
+ assertThat(dragZoneChangedListener.toDragZone).isNull()
+ }
+
+ @Test
+ fun onDragUpdated_outsideAllZones_doesNotNotify() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ val pointX = 200
+ val pointY = 200
+ assertThat(bubbleLeftDragZone.contains(pointX, pointY)).isFalse()
+ assertThat(bubbleRightDragZone.contains(pointX, pointY)).isFalse()
+ dropTargetManager.onDragUpdated(pointX, pointY)
+ assertThat(dragZoneChangedListener.fromDragZone).isNull()
+ assertThat(dragZoneChangedListener.toDragZone).isNull()
+ }
+
+ @Test
+ fun onDragUpdated_hasOverlappingZones_notifiesFirstDragZoneChanged() {
+ // create a drag zone that spans across the width of all 3 drag zones, but extends below
+ // them
+ val splitDragZone = DragZone.Split.Left(bounds = Rect(0, 0, 300, 200))
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone, splitDragZone)
+ )
+
+ // drag to a point that is within both the bubble right zone and split zone
+ val (pointX, pointY) =
+ Pair(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ assertThat(splitDragZone.contains(pointX, pointY)).isTrue()
+ dropTargetManager.onDragUpdated(pointX, pointY)
+ // verify we dragged to the bubble right zone because that has higher priority than split
+ assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone)
+ assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleRightDragZone)
+
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ 150 // below the bubble and dismiss drag zones but within split
+ )
+ assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleRightDragZone)
+ assertThat(dragZoneChangedListener.toDragZone).isEqualTo(splitDragZone)
+
+ val (dismissPointX, dismissPointY) =
+ Pair(dismissDragZone.bounds.centerX(), dismissDragZone.bounds.centerY())
+ assertThat(splitDragZone.contains(dismissPointX, dismissPointY)).isTrue()
+ dropTargetManager.onDragUpdated(dismissPointX, dismissPointY)
+ assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(splitDragZone)
+ assertThat(dragZoneChangedListener.toDragZone).isEqualTo(dismissDragZone)
+ }
+
+ @Test
+ fun onDragUpdated_afterDragEnded_doesNotNotify() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
+ )
+ dropTargetManager.onDragEnded()
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ assertThat(dragZoneChangedListener.fromDragZone).isNull()
+ assertThat(dragZoneChangedListener.toDragZone).isNull()
+ }
+
+ private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener {
+ var initialDragZone: DragZone? = null
+ var fromDragZone: DragZone? = null
+ var toDragZone: DragZone? = null
+
+ override fun onInitialDragZoneSet(dragZone: DragZone) {
+ initialDragZone = dragZone
+ }
+ override fun onDragZoneChanged(from: DragZone, to: DragZone) {
+ fromDragZone = from
+ toDragZone = to
+ }
+ }
+}
diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS
index bc174599a4d3..70d13ab8b3e5 100644
--- a/libs/hwui/OWNERS
+++ b/libs/hwui/OWNERS
@@ -4,7 +4,6 @@ alecmouri@google.com
djsollen@google.com
jreck@google.com
njawad@google.com
-scroggo@google.com
sumir@google.com
# For text, e.g. Typeface, Font, Minikin, etc.
diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
index e173c5e996df..0f6a2a082e0c 100644
--- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
+++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
@@ -118,6 +118,7 @@ public class SettingsSpinnerPreference extends Preference
spinner.setAdapter(mAdapter);
spinner.setSelection(mPosition);
spinner.setOnItemSelectedListener(mOnSelectedListener);
+ spinner.setLongClickable(false);
if (mShouldPerformClick) {
mShouldPerformClick = false;
// To show dropdown view.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
index 0c7d6f093289..b173db0a0505 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -37,7 +37,7 @@ object BluetoothLeBroadcastMetadataExt {
private const val KEY_BT_ADVERTISER_ADDRESS = "AD"
private const val KEY_BT_BROADCAST_ID = "BI"
private const val KEY_BT_BROADCAST_CODE = "BC"
- private const val KEY_BT_STREAM_METADATA = "MD"
+ private const val KEY_BT_PUBLIC_METADATA = "PM"
private const val KEY_BT_STANDARD_QUALITY = "SQ"
private const val KEY_BT_HIGH_QUALITY = "HQ"
@@ -84,7 +84,7 @@ object BluetoothLeBroadcastMetadataExt {
}
if (this.publicBroadcastMetadata != null &&
this.publicBroadcastMetadata?.rawMetadata?.size != 0) {
- entries.add(Pair(KEY_BT_STREAM_METADATA, Base64.encodeToString(
+ entries.add(Pair(KEY_BT_PUBLIC_METADATA, Base64.encodeToString(
this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP)))
}
if ((this.audioConfigQuality and
@@ -160,7 +160,7 @@ object BluetoothLeBroadcastMetadataExt {
var sourceAdvertiserSid = -1
var broadcastId = -1
var broadcastName: String? = null
- var streamMetadata: BluetoothLeAudioContentMetadata? = null
+ var publicMetadata: BluetoothLeAudioContentMetadata? = null
var paSyncInterval = -1
var broadcastCode: ByteArray? = null
var audioConfigQualityStandard = -1
@@ -207,11 +207,11 @@ object BluetoothLeBroadcastMetadataExt {
broadcastCode = Base64.decode(value.dropLastWhile { it.equals(0.toByte()) }
.toByteArray(), Base64.NO_WRAP)
}
- KEY_BT_STREAM_METADATA -> {
- require(streamMetadata == null) {
- "Duplicate streamMetadata $input"
+ KEY_BT_PUBLIC_METADATA -> {
+ require(publicMetadata == null) {
+ "Duplicate publicMetadata $input"
}
- streamMetadata = BluetoothLeAudioContentMetadata
+ publicMetadata = BluetoothLeAudioContentMetadata
.fromRawBytes(Base64.decode(value, Base64.NO_WRAP))
}
KEY_BT_STANDARD_QUALITY -> {
@@ -256,7 +256,7 @@ object BluetoothLeBroadcastMetadataExt {
Log.d(TAG, "parseQrCodeToMetadata: main data elements sourceAddrType=$sourceAddrType, " +
"sourceAddr=$sourceAddrString, sourceAdvertiserSid=$sourceAdvertiserSid, " +
"broadcastId=$broadcastId, broadcastName=$broadcastName, " +
- "streamMetadata=${streamMetadata != null}, " +
+ "publicMetadata=${publicMetadata != null}, " +
"paSyncInterval=$paSyncInterval, " +
"broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}, " +
"audioConfigQualityStandard=$audioConfigQualityStandard, " +
@@ -317,7 +317,7 @@ object BluetoothLeBroadcastMetadataExt {
setBroadcastName(broadcastName)
// QR code should set PBP(public broadcast profile) for auracast
setPublicBroadcast(true)
- setPublicBroadcastMetadata(streamMetadata)
+ setPublicBroadcastMetadata(publicMetadata)
setPaSyncInterval(paSyncInterval)
setEncrypted(broadcastCode != null)
setBroadcastCode(broadcastCode)
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
index 1ad20dc02042..5f6eb5e49573 100644
--- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
@@ -233,7 +233,7 @@ class BluetoothLeBroadcastMetadataExtTest {
const val QR_CODE_STRING =
"BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;" +
- "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"
+ "PM:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"
const val QR_CODE_STRING_NON_ENCRYPTED =
"BLUETOOTH:UUID:184F;BN:SG9ja2V5;AT:0;AD:AABBCC001122;BI:DE51E9;SQ:1;AS:1;PI:FFFF;" +
"NS:1;BS:1;NB:1;;"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 744388f47d0e..19806e7cdf64 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -207,6 +207,8 @@ filegroup {
"tests/src/**/systemui/statusbar/notification/row/NotificationConversationInfoTest.java",
"tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt",
"tests/src/**/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java",
+ "tests/src/**/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierDisabledTest.java",
"tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java",
"tests/src/**/systemui/statusbar/phone/CentralSurfacesImplTest.java",
"tests/src/**/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java",
@@ -538,6 +540,7 @@ android_library {
kotlincflags: [
"-Xjvm-default=all",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
+ "-P plugin:androidx.compose.compiler.plugins.kotlin:sourceInformation=true",
],
plugins: [
@@ -552,6 +555,11 @@ android_library {
},
}
+platform_compat_config {
+ name: "SystemUI-core-compat-config",
+ src: ":SystemUI-core",
+}
+
filegroup {
name: "AAA-src",
srcs: ["tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java"],
@@ -754,6 +762,7 @@ android_library {
"kosmos",
"testables",
"androidx.test.rules",
+ "platform-compat-test-rules",
],
libs: [
"android.test.runner.stubs.system",
@@ -888,6 +897,7 @@ android_robolectric_test {
static_libs: [
"RoboTestLibraries",
"androidx.compose.runtime_runtime",
+ "platform-compat-test-rules",
],
libs: [
"android.test.runner.impl",
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index c63c2b48638c..9c6bb2c8f778 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -42,6 +42,9 @@ android_library {
"//frameworks/libs/systemui:tracinglib-platform",
],
- kotlincflags: ["-Xjvm-default=all"],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-P plugin:androidx.compose.compiler.plugins.kotlin:sourceInformation=true",
+ ],
use_resource_processor: true,
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 4a4607b6e8fc..2ca70558f18b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -207,12 +207,7 @@ fun CommunalContainer(
Box(modifier = Modifier.fillMaxSize())
}
- scene(
- CommunalScenes.Communal,
- userActions =
- if (viewModel.v2FlagEnabled()) emptyMap()
- else mapOf(Swipe.End to CommunalScenes.Blank),
- ) {
+ scene(CommunalScenes.Communal, userActions = mapOf(Swipe.End to CommunalScenes.Blank)) {
CommunalScene(
backgroundType = backgroundType,
colors = colors,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 3c0480d150e0..418a7a52a97e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1705,15 +1705,38 @@ private fun Umo(
contentScope: ContentScope?,
modifier: Modifier = Modifier,
) {
- if (SceneContainerFlag.isEnabled && contentScope != null) {
- contentScope.MediaCarousel(
- modifier = modifier.fillMaxSize(),
- isVisible = true,
- mediaHost = viewModel.mediaHost,
- carouselController = viewModel.mediaCarouselController,
- )
- } else {
- UmoLegacy(viewModel, modifier)
+ val showNextActionLabel = stringResource(R.string.accessibility_action_label_umo_show_next)
+ val showPreviousActionLabel =
+ stringResource(R.string.accessibility_action_label_umo_show_previous)
+
+ Box(
+ modifier =
+ modifier.thenIf(!viewModel.isEditMode) {
+ Modifier.semantics {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(showNextActionLabel) {
+ viewModel.onShowNextMedia()
+ true
+ },
+ CustomAccessibilityAction(showPreviousActionLabel) {
+ viewModel.onShowPreviousMedia()
+ true
+ },
+ )
+ }
+ }
+ ) {
+ if (SceneContainerFlag.isEnabled && contentScope != null) {
+ contentScope.MediaCarousel(
+ modifier = modifier.fillMaxSize(),
+ isVisible = true,
+ mediaHost = viewModel.mediaHost,
+ carouselController = viewModel.mediaCarouselController,
+ )
+ } else {
+ UmoLegacy(viewModel, modifier)
+ }
}
}
@@ -1724,7 +1747,7 @@ private fun UmoLegacy(viewModel: BaseCommunalViewModel, modifier: Modifier = Mod
modifier
.clip(
shape =
- RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ RoundedCornerShape(dimensionResource(R.dimen.notification_corner_radius))
)
.background(MaterialTheme.colorScheme.primary)
.pointerInput(Unit) {
diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp
index 090e9ccedda0..42dd85a3d0a7 100644
--- a/packages/SystemUI/compose/scene/Android.bp
+++ b/packages/SystemUI/compose/scene/Android.bp
@@ -45,6 +45,9 @@ android_library {
"mechanics",
],
- kotlincflags: ["-Xjvm-default=all"],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-P plugin:androidx.compose.compiler.plugins.kotlin:sourceInformation=true",
+ ],
use_resource_processor: true,
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 85155157eda2..433894b58350 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -78,6 +78,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.controller.mediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -120,6 +121,7 @@ import platform.test.runner.parameterized.Parameters
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
@Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
@@ -161,6 +163,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
whenever(mediaHost.visible).thenReturn(true)
+ whenever(kosmos.mediaCarouselController.mediaCarouselScrollHandler)
+ .thenReturn(mediaCarouselScrollHandler)
kosmos.powerInteractor.setAwakeForTest()
@@ -903,6 +907,20 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ fun onShowPreviousMedia_scrollHandler_isCalled() =
+ testScope.runTest {
+ underTest.onShowPreviousMedia()
+ verify(mediaCarouselScrollHandler).scrollByStep(-1)
+ }
+
+ @Test
+ fun onShowNextMedia_scrollHandler_isCalled() =
+ testScope.runTest {
+ underTest.onShowNextMedia()
+ verify(mediaCarouselScrollHandler).scrollByStep(1)
+ }
+
+ @Test
@EnableFlags(FLAG_BOUNCER_UI_REVAMP)
fun uiIsBlurred_whenPrimaryBouncerIsShowing() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index d073cf1ac9db..46940297e673 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.media.controls.ui.view
+import android.content.res.Resources
import android.testing.TestableLooper
import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,16 +28,19 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.Mock
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -42,6 +48,7 @@ import org.mockito.MockitoAnnotations
class MediaCarouselScrollHandlerTest : SysuiTestCase() {
private val carouselWidth = 1038
+ private val settingsButtonWidth = 200
private val motionEventUp = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0f, 0f, 0)
@Mock lateinit var mediaCarousel: MediaScrollView
@@ -53,6 +60,9 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
@Mock lateinit var falsingManager: FalsingManager
@Mock lateinit var logSmartspaceImpression: (Boolean) -> Unit
@Mock lateinit var logger: MediaUiEventLogger
+ @Mock lateinit var contentContainer: ViewGroup
+ @Mock lateinit var settingsButton: View
+ @Mock lateinit var resources: Resources
lateinit var executor: FakeExecutor
private val clock = FakeSystemClock()
@@ -63,6 +73,7 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
fun setup() {
MockitoAnnotations.initMocks(this)
executor = FakeExecutor(clock)
+ whenever(mediaCarousel.contentContainer).thenReturn(contentContainer)
mediaCarouselScrollHandler =
MediaCarouselScrollHandler(
mediaCarousel,
@@ -74,10 +85,9 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
closeGuts,
falsingManager,
logSmartspaceImpression,
- logger
+ logger,
)
mediaCarouselScrollHandler.playerWidthPlusPadding = carouselWidth
-
whenever(mediaCarousel.touchListener).thenReturn(mediaCarouselScrollHandler.touchListener)
}
@@ -128,4 +138,107 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
}
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight() {
+ setupMediaContainer(visibleIndex = 0)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel).smoothScrollTo(eq(carouselWidth), anyInt())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft() {
+ setupMediaContainer(visibleIndex = 1)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel).smoothScrollTo(eq(0), anyInt())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight_alreadyAtEnd() {
+ setupMediaContainer(visibleIndex = 1)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft_alreadyAtStart() {
+ setupMediaContainer(visibleIndex = 0)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollLeft_alreadyAtStart_isRTL() {
+ setupMediaContainer(visibleIndex = 0)
+ whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+ mediaCarouselScrollHandler.scrollByStep(-1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(-settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testCarouselScrollByStep_scrollRight_alreadyAtEnd_isRTL() {
+ setupMediaContainer(visibleIndex = 1)
+ whenever(mediaCarousel.isLayoutRtl).thenReturn(true)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel).animationTargetX = eq(settingsButtonWidth.toFloat())
+ }
+
+ @Test
+ fun testScrollByStep_noScroll_notDismissible() {
+ setupMediaContainer(visibleIndex = 1, showsSettingsButton = false)
+
+ mediaCarouselScrollHandler.scrollByStep(1)
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel, never()).animationTargetX = anyFloat()
+ }
+
+ private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) {
+ whenever(contentContainer.childCount).thenReturn(2)
+ val child1: View = mock()
+ val child2: View = mock()
+ whenever(child1.left).thenReturn(0)
+ whenever(child2.left).thenReturn(carouselWidth)
+ whenever(contentContainer.getChildAt(0)).thenReturn(child1)
+ whenever(contentContainer.getChildAt(1)).thenReturn(child2)
+
+ whenever(settingsButton.width).thenReturn(settingsButtonWidth)
+ whenever(settingsButton.context).thenReturn(context)
+ whenever(settingsButton.resources).thenReturn(resources)
+ whenever(settingsButton.resources.getDimensionPixelSize(anyInt())).thenReturn(20)
+ mediaCarouselScrollHandler.onSettingsButtonUpdated(settingsButton)
+
+ mediaCarouselScrollHandler.visibleMediaIndex = visibleIndex
+ mediaCarouselScrollHandler.showsSettingsButton = showsSettingsButton
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
index 8eea2a8e6121..048028cdc0fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -21,52 +21,44 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.statusbar.lockscreenShadeTransitionController
+import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.isNull
import org.mockito.Mockito.verify
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@SmallTest
class NotificationShelfInteractorTest : SysuiTestCase() {
- private val keyguardRepository = FakeKeyguardRepository()
- private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-
- private val screenOffAnimationController =
- mock<ScreenOffAnimationController>().also {
- whenever(it.allowWakeUpIfDozing()).thenReturn(true)
+ private val kosmos =
+ Kosmos().apply {
+ testCase = this@NotificationShelfInteractorTest
+ lockscreenShadeTransitionController = mock()
+ screenOffAnimationController = mock()
+ statusBarStateController = mock()
+ whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
}
- private val statusBarStateController: StatusBarStateController = mock()
- private val powerRepository = FakePowerRepository()
- private val powerInteractor =
- PowerInteractorFactory.create(
- repository = powerRepository,
- screenOffAnimationController = screenOffAnimationController,
- statusBarStateController = statusBarStateController,
- )
- .powerInteractor
-
- private val keyguardTransitionController: LockscreenShadeTransitionController = mock()
- private val underTest =
- NotificationShelfInteractor(
- keyguardRepository,
- deviceEntryFaceAuthRepository,
- powerInteractor,
- keyguardTransitionController,
- )
+ private val underTest = kosmos.notificationShelfInteractor
+
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val deviceEntryFaceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+
+ private val statusBarStateController = kosmos.statusBarStateController
+ private val powerRepository = kosmos.fakePowerRepository
+ private val keyguardTransitionController = kosmos.lockscreenShadeTransitionController
@Test
fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index e2fb3ba11a02..d570f18e35d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -19,73 +19,53 @@ package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
import android.os.PowerManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.shade.domain.interactor.enableDualShade
+import com.android.systemui.shade.domain.interactor.enableSingleShade
+import com.android.systemui.shade.domain.interactor.enableSplitShade
+import com.android.systemui.statusbar.lockscreenShadeTransitionController
+import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito
import org.mockito.Mockito.verify
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@SmallTest
class NotificationShelfViewModelTest : SysuiTestCase() {
- @Component(modules = [SysUITestModule::class, ActivatableNotificationViewModelModule::class])
- @SysUISingleton
- interface TestComponent : SysUITestComponent<NotificationShelfViewModel> {
-
- val deviceEntryFaceAuthRepository: FakeDeviceEntryFaceAuthRepository
- val keyguardRepository: FakeKeyguardRepository
- val powerRepository: FakePowerRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- ): TestComponent
+ private val kosmos =
+ Kosmos().apply {
+ testCase = this@NotificationShelfViewModelTest
+ lockscreenShadeTransitionController = mock()
+ screenOffAnimationController = mock()
+ statusBarStateController = mock()
+ whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
}
- }
-
- private val keyguardTransitionController: LockscreenShadeTransitionController = mock()
- private val screenOffAnimationController: ScreenOffAnimationController = mock {
- whenever(allowWakeUpIfDozing()).thenReturn(true)
- }
- private val statusBarStateController: SysuiStatusBarStateController = mock()
-
- private val testComponent: TestComponent =
- DaggerNotificationShelfViewModelTest_TestComponent.factory()
- .create(
- test = this,
- mocks =
- TestMocksModule(
- lockscreenShadeTransitionController = keyguardTransitionController,
- screenOffAnimationController = screenOffAnimationController,
- statusBarStateController = statusBarStateController,
- )
- )
+ private val underTest = kosmos.notificationShelfViewModel
+ private val deviceEntryFaceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val keyguardTransitionController = kosmos.lockscreenShadeTransitionController
+ private val powerRepository = kosmos.fakePowerRepository
@Test
fun canModifyColorOfNotifications_whenKeyguardNotShowing() =
- testComponent.runTest {
+ kosmos.runTest {
val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
keyguardRepository.setKeyguardShowing(false)
@@ -95,7 +75,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {
@Test
fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() =
- testComponent.runTest {
+ kosmos.runTest {
val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
keyguardRepository.setKeyguardShowing(true)
@@ -106,7 +86,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {
@Test
fun cannotModifyColorOfNotifications_whenBypass() =
- testComponent.runTest {
+ kosmos.runTest {
val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
keyguardRepository.setKeyguardShowing(true)
@@ -117,7 +97,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {
@Test
fun isClickable_whenKeyguardShowing() =
- testComponent.runTest {
+ kosmos.runTest {
val isClickable by collectLastValue(underTest.isClickable)
keyguardRepository.setKeyguardShowing(true)
@@ -127,7 +107,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {
@Test
fun isNotClickable_whenKeyguardNotShowing() =
- testComponent.runTest {
+ kosmos.runTest {
val isClickable by collectLastValue(underTest.isClickable)
keyguardRepository.setKeyguardShowing(false)
@@ -137,7 +117,7 @@ class NotificationShelfViewModelTest : SysuiTestCase() {
@Test
fun onClicked_goesToLockedShade() =
- with(testComponent) {
+ kosmos.runTest {
whenever(statusBarStateController.isDozing).thenReturn(true)
underTest.onShelfClicked()
@@ -146,4 +126,48 @@ class NotificationShelfViewModelTest : SysuiTestCase() {
assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
verify(keyguardTransitionController).goToLockedShade(Mockito.isNull(), eq(true))
}
+
+ @Test
+ @EnableSceneContainer
+ fun isAlignedToEnd_splitShade_true() =
+ kosmos.runTest {
+ val isShelfAlignedToEnd by collectLastValue(underTest.isAlignedToEnd)
+
+ kosmos.enableSplitShade()
+
+ assertThat(isShelfAlignedToEnd).isTrue()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isAlignedToEnd_singleShade_false() =
+ kosmos.runTest {
+ val isShelfAlignedToEnd by collectLastValue(underTest.isAlignedToEnd)
+
+ kosmos.enableSingleShade()
+
+ assertThat(isShelfAlignedToEnd).isFalse()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isAlignedToEnd_dualShade_wideScreen_false() =
+ kosmos.runTest {
+ val isShelfAlignedToEnd by collectLastValue(underTest.isAlignedToEnd)
+
+ kosmos.enableDualShade(wideLayout = true)
+
+ assertThat(isShelfAlignedToEnd).isFalse()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isAlignedToEnd_dualShade_narrowScreen_false() =
+ kosmos.runTest {
+ val isShelfAlignedToEnd by collectLastValue(underTest.isAlignedToEnd)
+
+ kosmos.enableDualShade(wideLayout = false)
+
+ assertThat(isShelfAlignedToEnd).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
index d14ff35f824a..e5cb0fbc9e4b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
@@ -49,6 +49,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
private val sectionsManager = mock<NotificationSectionsManager>()
private val msdlPlayer = kosmos.fakeMSDLPlayer
private var canRowBeDismissed = true
+ private var magneticAnimationsCancelled = false
private val underTest = kosmos.magneticNotificationRowManagerImpl
@@ -64,6 +65,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
children = notificationTestHelper.createGroup(childrenNumber).childrenContainer
swipedRow = children.attachedChildren[childrenNumber / 2]
configureMagneticRowListener(swipedRow)
+ magneticAnimationsCancelled = false
}
@Test
@@ -247,6 +249,35 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
assertThat(underTest.currentState).isEqualTo(State.IDLE)
}
+ @Test
+ fun onMagneticInteractionEnd_whenDetached_cancelsMagneticAnimations() =
+ kosmos.testScope.runTest {
+ // GIVEN the swiped row is detached
+ setDetachedState()
+
+ // WHEN the interaction ends on the row
+ underTest.onMagneticInteractionEnd(swipedRow, velocity = null)
+
+ // THEN magnetic animations are cancelled
+ assertThat(magneticAnimationsCancelled).isTrue()
+ }
+
+ @Test
+ fun onMagneticInteractionEnd_forMagneticNeighbor_cancelsMagneticAnimations() =
+ kosmos.testScope.runTest {
+ val neighborRow = children.attachedChildren[childrenNumber / 2 - 1]
+ configureMagneticRowListener(neighborRow)
+
+ // GIVEN that targets are set
+ setTargets()
+
+ // WHEN the interactionEnd is called on a target different from the swiped row
+ underTest.onMagneticInteractionEnd(neighborRow, null)
+
+ // THEN magnetic animations are cancelled
+ assertThat(magneticAnimationsCancelled).isTrue()
+ }
+
private fun setDetachedState() {
val threshold = 100f
underTest.setSwipeThresholdPx(threshold)
@@ -284,7 +315,11 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
startVelocity: Float,
) {}
- override fun cancelMagneticAnimations() {}
+ override fun cancelMagneticAnimations() {
+ magneticAnimationsCancelled = true
+ }
+
+ override fun cancelTranslationAnimations() {}
override fun canRowBeDismissed(): Boolean = canRowBeDismissed
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 766ae73cb49d..789701f5e4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -405,7 +405,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 0, 0);
mSwipeHelper.snapChild(mNotificationRow, 0, 0);
- verify(mCallback, times(1)).onDragCancelledWithVelocity(mNotificationRow, 0);
+ verify(mCallback, times(1)).onDragCancelled(mNotificationRow);
verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 0, 0);
verify(mSwipeHelper, times(1)).handleMenuCoveredOrDismissed();
}
@@ -416,7 +416,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 10, 0);
mSwipeHelper.snapChild(mNotificationRow, 10, 0);
- verify(mCallback, times(1)).onDragCancelledWithVelocity(mNotificationRow, 0);
+ verify(mCallback, times(1)).onDragCancelled(mNotificationRow);
verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 10, 0);
verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed();
}
@@ -426,7 +426,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
doNothing().when(mSwipeHelper).superSnapChild(mView, 10, 0);
mSwipeHelper.snapChild(mView, 10, 0);
- verify(mCallback).onDragCancelledWithVelocity(mView, 0);
+ verify(mCallback).onDragCancelled(mView);
verify(mSwipeHelper, never()).superSnapChild(mView, 10, 0);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 2e12336f6e93..6f785a3731e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone;
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import static org.junit.Assert.assertFalse;
@@ -76,6 +77,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInte
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.ui.BouncerView;
import com.android.systemui.bouncer.ui.BouncerViewDelegate;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
@@ -171,6 +173,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private SceneInteractor mSceneInteractor;
@Mock private DismissCallbackRegistry mDismissCallbackRegistry;
@Mock private BouncerInteractor mBouncerInteractor;
+ @Mock private CommunalSceneInteractor mCommunalSceneInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -209,6 +212,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
.thenReturn(mNotificationShadeWindowView);
when(mNotificationShadeWindowView.getWindowInsetsController())
.thenReturn(mWindowInsetsController);
+ when(mCommunalSceneInteractor.isIdleOnCommunal()).thenReturn(MutableStateFlow(false));
mStatusBarKeyguardViewManager =
new StatusBarKeyguardViewManager(
@@ -245,7 +249,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mExecutor,
() -> mDeviceEntryInteractor,
mDismissCallbackRegistry,
- () -> mBouncerInteractor) {
+ () -> mBouncerInteractor,
+ mCommunalSceneInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -749,7 +754,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mExecutor,
() -> mDeviceEntryInteractor,
mDismissCallbackRegistry,
- () -> mBouncerInteractor) {
+ () -> mBouncerInteractor,
+ mCommunalSceneInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
index 91cd019c85d1..43808f215a81 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
@@ -149,9 +149,9 @@
style="@style/TextAppearance.AuthCredential.Indicator"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginTop="24dp"
android:layout_marginHorizontal="24dp"
- android:accessibilityLiveRegion="assertive"
+ android:layout_marginTop="24dp"
+ android:accessibilityLiveRegion="polite"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d18a90a17abe..86292039d93d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1351,6 +1351,10 @@
<string name="accessibility_action_label_shrink_widget">Decrease height</string>
<!-- Label for accessibility action to expand a widget in edit mode. [CHAR LIMIT=NONE] -->
<string name="accessibility_action_label_expand_widget">Increase height</string>
+ <!-- Label for accessibility action to show the next media player. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_umo_show_next">Show next</string>
+ <!-- Label for accessibility action to show the previous media player. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_umo_show_previous">Show previous</string>
<!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
<string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
<!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 5ef4d4014ba6..7f2c89346423 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -258,7 +258,7 @@
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">24dp</item>
- <item name="android:textSize">36dp</item>
+ <item name="android:textSize">36sp</item>
<item name="android:focusable">true</item>
<item name="android:textColor">@androidprv:color/materialColorOnSurface</item>
</style>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index f835ad689132..e2065f175c79 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -352,6 +352,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
&& Math.abs(delta) > Math.abs(deltaPerpendicular)) {
if (mCallback.canChildBeDragged(mTouchedView)) {
mIsSwiping = true;
+ mCallback.setMagneticAndRoundableTargets(mTouchedView);
mCallback.onBeginDrag(mTouchedView);
mInitialTouchPos = getPos(ev);
mTranslation = getTranslation(mTouchedView);
@@ -444,6 +445,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
};
Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
+ mCallback.onMagneticInteractionEnd(animView, velocity);
if (anim == null) {
onDismissChildWithAnimationFinished();
return;
@@ -733,7 +735,8 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
dismissChild(mTouchedView, velocity,
!swipedFastEnough() /* useAccelerateInterpolator */);
} else {
- mCallback.onDragCancelledWithVelocity(mTouchedView, velocity);
+ mCallback.onMagneticInteractionEnd(mTouchedView, velocity);
+ mCallback.onDragCancelled(mTouchedView);
snapChild(mTouchedView, 0 /* leftTarget */, velocity);
}
mTouchedView = null;
@@ -935,18 +938,24 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
void onBeginDrag(View v);
+ /**
+ * Set magnetic and roundable targets for a view.
+ */
+ void setMagneticAndRoundableTargets(View v);
+
void onChildDismissed(View v);
void onDragCancelled(View v);
/**
- * A drag operation has been cancelled on a view with a final velocity.
- * @param v View that was dragged.
- * @param finalVelocity Final velocity of the drag.
+ * Notify that a magnetic interaction ended on a view with a velocity.
+ * <p>
+ * This method should be called when a view will snap back or be dismissed.
+ *
+ * @param view The {@link View} whose magnetic interaction ended.
+ * @param velocity The velocity when the interaction ended.
*/
- default void onDragCancelledWithVelocity(View v, float finalVelocity) {
- onDragCancelled(v);
- }
+ void onMagneticInteractionEnd(View view, float velocity);
/**
* Called when the child is long pressed and available to start drag and drop.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 6cd763a9d3d0..bbf9a19012a4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -31,6 +31,7 @@ import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieProperty
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.KeyguardPINView
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
@@ -50,7 +51,6 @@ import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */
@SysUISingleton
@@ -65,51 +65,53 @@ constructor(
private val layoutInflater: Lazy<LayoutInflater>,
private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>,
private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>,
- private val windowManager: Lazy<WindowManager>
+ private val windowManager: Lazy<WindowManager>,
) : CoreStartable {
override fun start() {
- applicationScope
- .launch {
- sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable ->
- if (isSfpsAvailable) {
- combine(
- biometricStatusInteractor.get().sfpsAuthenticationReason,
- deviceEntrySideFpsOverlayInteractor
- .get()
- .showIndicatorForDeviceEntry,
- sideFpsProgressBarViewModel.get().isVisible,
- ::Triple
+ applicationScope.launch {
+ sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable ->
+ if (isSfpsAvailable) {
+ combine(
+ biometricStatusInteractor.get().sfpsAuthenticationReason,
+ deviceEntrySideFpsOverlayInteractor.get().showIndicatorForDeviceEntry,
+ sideFpsProgressBarViewModel.get().isVisible,
+ ::Triple,
+ )
+ .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair)
+ .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
+ val (
+ systemServerAuthReason,
+ showIndicatorForDeviceEntry,
+ progressBarIsVisible) =
+ combinedFlows
+ Log.d(
+ TAG,
+ "systemServerAuthReason = $systemServerAuthReason, " +
+ "showIndicatorForDeviceEntry = " +
+ "$showIndicatorForDeviceEntry, " +
+ "progressBarIsVisible = $progressBarIsVisible",
)
- .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair)
- .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
- val (
- systemServerAuthReason,
- showIndicatorForDeviceEntry,
- progressBarIsVisible) =
- combinedFlows
- Log.d(
- TAG,
- "systemServerAuthReason = $systemServerAuthReason, " +
- "showIndicatorForDeviceEntry = " +
- "$showIndicatorForDeviceEntry, " +
- "progressBarIsVisible = $progressBarIsVisible"
- )
- if (!isInRearDisplayMode) {
- if (progressBarIsVisible) {
- hide()
- } else if (systemServerAuthReason != NotRunning) {
- show()
- } else if (showIndicatorForDeviceEntry) {
- show()
- } else {
- hide()
- }
+ if (!isInRearDisplayMode) {
+ if (progressBarIsVisible) {
+ hide()
+ } else if (systemServerAuthReason != NotRunning) {
+ show()
+ } else if (showIndicatorForDeviceEntry) {
+ show()
+ overlayView?.announceForAccessibility(
+ applicationContext.resources.getString(
+ R.string.accessibility_side_fingerprint_indicator_label
+ )
+ )
+ } else {
+ hide()
}
}
- }
+ }
}
}
+ }
}
private var overlayView: View? = null
@@ -119,7 +121,7 @@ constructor(
if (overlayView?.isAttachedToWindow == true) {
Log.d(
TAG,
- "show(): overlayView $overlayView isAttachedToWindow already, ignoring show request"
+ "show(): overlayView $overlayView isAttachedToWindow already, ignoring show request",
)
return
}
@@ -137,11 +139,6 @@ constructor(
overlayView!!.visibility = View.INVISIBLE
Log.d(TAG, "show(): adding overlayView $overlayView")
windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
- overlayView!!.announceForAccessibility(
- applicationContext.resources.getString(
- R.string.accessibility_side_fingerprint_indicator_label
- )
- )
}
/** Hide the side fingerprint sensor indicator */
@@ -163,7 +160,7 @@ constructor(
fun bind(
overlayView: View,
viewModel: SideFpsOverlayViewModel,
- windowManager: WindowManager
+ windowManager: WindowManager,
) {
overlayView.repeatWhenAttached {
val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
@@ -186,7 +183,7 @@ constructor(
object : View.AccessibilityDelegate() {
override fun dispatchPopulateAccessibilityEvent(
host: View,
- event: AccessibilityEvent
+ event: AccessibilityEvent,
): Boolean {
return if (
event.getEventType() ===
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 49003a735fbd..a4860dfc47ce 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -202,6 +202,12 @@ abstract class BaseCommunalViewModel(
/** Called as the user request to show the customize widget button. */
open fun onLongClick() {}
+ /** Called as the user requests to switch to the previous player in UMO. */
+ open fun onShowPreviousMedia() {}
+
+ /** Called as the user requests to switch to the next player in UMO. */
+ open fun onShowNextMedia() {}
+
/** Called as the UI determines that a new widget has been added to the grid. */
open fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 4bc44005d2fc..dd4018a9d7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -254,6 +254,14 @@ constructor(
}
}
+ override fun onShowPreviousMedia() {
+ mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(-1)
+ }
+
+ override fun onShowNextMedia() {
+ mediaCarouselController.mediaCarouselScrollHandler.scrollByStep(1)
+ }
+
override fun onTapWidget(componentName: ComponentName, rank: Int) {
metricsLogger.logTapWidget(componentName.flattenToString(), rank)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index efa9c21f96b4..caf0fd4450fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard;
-import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED;
@@ -76,7 +75,6 @@ import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
-import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -194,8 +192,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -286,9 +282,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private static final int SYSTEM_READY = 18;
private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19;
private static final int BOOT_INTERACTOR = 20;
- private static final int BEFORE_USER_SWITCHING = 21;
- private static final int USER_SWITCHING = 22;
- private static final int USER_SWITCH_COMPLETE = 23;
/** Enum for reasons behind updating wakeAndUnlock state. */
@Retention(RetentionPolicy.SOURCE)
@@ -306,8 +299,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
int WAKE_AND_UNLOCK = 3;
}
- private final List<LockNowCallback> mLockNowCallbacks = new ArrayList<>();
-
/**
* The default amount of time we stay awake (used for all key input)
*/
@@ -366,18 +357,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
private final Lazy<ShadeController> mShadeController;
private final Lazy<CommunalSceneInteractor> mCommunalSceneInteractor;
- /*
- * Records the user id on request to go away, for validation when WM calls back to start the
- * exit animation.
- */
- private int mGoingAwayRequestedForUserId = -1;
-
private boolean mSystemReady;
private boolean mBootCompleted;
private boolean mBootSendUserPresent;
private boolean mShuttingDown;
private boolean mDozing;
private boolean mAnimatingScreenOff;
+ private boolean mIgnoreDismiss;
private final Context mContext;
private final FalsingCollector mFalsingCollector;
@@ -640,78 +626,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
};
- @VisibleForTesting
- protected UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() {
-
- @Override
- public void onBeforeUserSwitching(int newUser, @NonNull Runnable resultCallback) {
- mHandler.sendMessage(mHandler.obtainMessage(BEFORE_USER_SWITCHING,
- newUser, 0, resultCallback));
- }
-
- @Override
- public void onUserChanging(int newUser, @NonNull Context userContext,
- @NonNull Runnable resultCallback) {
- mHandler.sendMessage(mHandler.obtainMessage(USER_SWITCHING,
- newUser, 0, resultCallback));
- }
-
- @Override
- public void onUserChanged(int newUser, Context userContext) {
- mHandler.sendMessage(mHandler.obtainMessage(USER_SWITCH_COMPLETE,
- newUser, 0));
- }
- };
-
- /**
- * Handle {@link #BEFORE_USER_SWITCHING}
- */
- @VisibleForTesting
- void handleBeforeUserSwitching(int userId, Runnable resultCallback) {
- Log.d(TAG, String.format("onBeforeUserSwitching %d", userId));
- synchronized (KeyguardViewMediator.this) {
- mHandler.removeMessages(DISMISS);
- notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
- resetKeyguardDonePendingLocked();
- adjustStatusBarLocked();
- mKeyguardStateController.notifyKeyguardGoingAway(false);
- if (mLockPatternUtils.isSecure(userId) && !mShowing) {
- doKeyguardLocked(null);
- } else {
- resetStateLocked();
- }
- resultCallback.run();
- }
- }
-
- /**
- * Handle {@link #USER_SWITCHING}
- */
- @VisibleForTesting
- void handleUserSwitching(int userId, Runnable resultCallback) {
- Log.d(TAG, String.format("onUserSwitching %d", userId));
- synchronized (KeyguardViewMediator.this) {
- if (!mLockPatternUtils.isSecure(userId)) {
- dismiss(null, null);
- }
- resultCallback.run();
- }
- }
-
- /**
- * Handle {@link #USER_SWITCH_COMPLETE}
- */
- @VisibleForTesting
- void handleUserSwitchComplete(int userId) {
- Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
- // Calling dismiss on a secure user will show the bouncer
- if (mLockPatternUtils.isSecure(userId)) {
- // We are calling dismiss with a delay as there are race conditions in some scenarios
- // caused by async layout listeners
- mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
- }
- }
-
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
@@ -728,6 +642,27 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
@Override
+ public void onUserSwitching(int userId) {
+ Log.d(TAG, String.format("onUserSwitching %d", userId));
+ synchronized (KeyguardViewMediator.this) {
+ mIgnoreDismiss = true;
+ notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
+ resetKeyguardDonePendingLocked();
+ resetStateLocked();
+ adjustStatusBarLocked();
+ }
+ }
+
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ mIgnoreDismiss = false;
+ Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
+ // We are calling dismiss with a delay as there are race conditions in some scenarios
+ // caused by async layout listeners
+ mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
+ }
+
+ @Override
public void onDeviceProvisioned() {
sendUserPresentBroadcast();
}
@@ -1736,13 +1671,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
com.android.internal.R.anim.lock_screen_behind_enter);
mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
- // start() can be invoked in the middle of user switching, so check for this state and issue
- // the call manually as that important event was missed.
- if (mUserTracker.isUserSwitching()) {
- handleBeforeUserSwitching(mUserTracker.getUserId(), () -> {});
- handleUserSwitching(mUserTracker.getUserId(), () -> {});
- }
+
mJavaAdapter.alwaysCollectFlow(
mWallpaperRepository.getWallpaperSupportsAmbientMode(),
this::setWallpaperSupportsAmbientMode);
@@ -1791,7 +1720,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// System ready can be invoked in the middle of user switching, so check for this state
// and issue the call manually as that important event was missed.
if (mUserTracker.isUserSwitching()) {
- mUserChangedCallback.onUserChanging(mUserTracker.getUserId(), mContext, () -> {});
+ mUpdateCallback.onUserSwitching(mUserTracker.getUserId());
}
}
// Most services aren't available until the system reaches the ready state, so we
@@ -2432,23 +2361,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mCommunalSceneInteractor.get().showHubFromPowerButton();
}
- int currentUserId = mSelectedUserInteractor.getSelectedUserId();
- if (options != null && options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK) != null) {
- LockNowCallback callback = new LockNowCallback(currentUserId,
- IRemoteCallback.Stub.asInterface(
- options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK)));
- synchronized (mLockNowCallbacks) {
- mLockNowCallbacks.add(callback);
- }
- Log.d(TAG, "LockNowCallback required for user: " + callback.mUserId);
- }
-
// if another app is disabling us, don't show
if (!mExternallyEnabled
&& !mLockPatternUtils.isUserInLockdown(
mSelectedUserInteractor.getSelectedUserId())) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
- notifyLockNowCallback();
+
mNeedToReshowWhenReenabled = true;
return;
}
@@ -2466,7 +2384,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// We're removing "reset" in the refactor - "resetting" the views will happen
// as a reaction to the root cause of the "reset" signal.
if (KeyguardWmStateRefactor.isEnabled()) {
- notifyLockNowCallback();
return;
}
@@ -2479,7 +2396,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
+ "previously hiding. It should be safe to short-circuit "
+ "here.");
resetStateLocked(/* hideBouncer= */ false);
- notifyLockNowCallback();
return;
}
} else {
@@ -2506,7 +2422,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Log.d(TAG, "doKeyguard: not showing because device isn't provisioned and the sim is"
+ " not locked or missing");
}
- notifyLockNowCallback();
return;
}
@@ -2514,7 +2429,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (mLockPatternUtils.isLockScreenDisabled(mSelectedUserInteractor.getSelectedUserId())
&& !lockedOrMissing && !forceShow) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
- notifyLockNowCallback();
return;
}
@@ -2562,6 +2476,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
+ if (mIgnoreDismiss) {
+ android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)");
+ return;
+ }
+
if (mKeyguardStateController.isKeyguardGoingAway()) {
Log.i(TAG, "Ignoring dismiss because we're already going away.");
return;
@@ -2579,7 +2498,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
private void resetStateLocked(boolean hideBouncer) {
- if (DEBUG) Log.d(TAG, "resetStateLocked");
+ if (DEBUG) Log.e(TAG, "resetStateLocked");
Message msg = mHandler.obtainMessage(RESET, hideBouncer ? 1 : 0, 0);
mHandler.sendMessage(msg);
}
@@ -2827,18 +2746,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
message = "BOOT_INTERACTOR";
handleBootInteractor();
break;
- case BEFORE_USER_SWITCHING:
- message = "BEFORE_USER_SWITCHING";
- handleBeforeUserSwitching(msg.arg1, (Runnable) msg.obj);
- break;
- case USER_SWITCHING:
- message = "USER_SWITCHING";
- handleUserSwitching(msg.arg1, (Runnable) msg.obj);
- break;
- case USER_SWITCH_COMPLETE:
- message = "USER_SWITCH_COMPLETE";
- handleUserSwitchComplete(msg.arg1);
- break;
}
Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
}
@@ -2980,9 +2887,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUiBgExecutor.execute(() -> {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ", "
+ reason + ")");
- if (showing) {
- notifyLockNowCallback();
- }
if (KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager if flag is enabled.
@@ -3027,7 +2931,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
synchronized (KeyguardViewMediator.this) {
if (!mSystemReady) {
if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
- notifyLockNowCallback();
return;
}
if (DEBUG) Log.d(TAG, "handleShow");
@@ -3086,11 +2989,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
- final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
+ private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
@SuppressLint("MissingPermission")
@Override
public void run() {
Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
+ Log.d(TAG, "keyguardGoingAwayRunnable");
mKeyguardViewControllerLazy.get().keyguardGoingAway();
int flags = 0;
@@ -3127,10 +3031,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// Handled in WmLockscreenVisibilityManager if flag is enabled.
if (!KeyguardWmStateRefactor.isEnabled()) {
- mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
- Log.d(TAG, "keyguardGoingAway requested for userId: "
- + mGoingAwayRequestedForUserId);
-
// Don't actually hide the Keyguard at the moment, wait for window manager
// until it tells us it's safe to do so with startKeyguardExitAnimation.
// Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager
@@ -3269,30 +3169,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
+ " fadeoutDuration=" + fadeoutDuration);
- int currentUserId = mSelectedUserInteractor.getSelectedUserId();
- if (!KeyguardWmStateRefactor.isEnabled() && mGoingAwayRequestedForUserId != currentUserId) {
- Log.e(TAG, "Not executing handleStartKeyguardExitAnimationInner() due to userId "
- + "mismatch. Requested: " + mGoingAwayRequestedForUserId + ", current: "
- + currentUserId);
- if (finishedCallback != null) {
- // There will not execute animation, send a finish callback to ensure the remote
- // animation won't hang there.
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onAnimationFinished", e);
- }
- }
- mHiding = false;
- if (mLockPatternUtils.isSecure(currentUserId)) {
- doKeyguardLocked(null);
- } else {
- resetStateLocked();
- dismiss(null, null);
- }
- return;
- }
-
synchronized (KeyguardViewMediator.this) {
mIsKeyguardExitAnimationCanceled = false;
// Tell ActivityManager that we canceled the keyguard animation if
@@ -3537,13 +3413,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
* app transition before finishing the current RemoteAnimation, or the keyguard being re-shown).
*/
private void handleCancelKeyguardExitAnimation() {
- if (!KeyguardWmStateRefactor.isEnabled()
- && mGoingAwayRequestedForUserId != mSelectedUserInteractor.getSelectedUserId()) {
- Log.e(TAG, "Setting pendingLock = true due to userId mismatch. Requested: "
- + mGoingAwayRequestedForUserId + ", current: "
- + mSelectedUserInteractor.getSelectedUserId());
- setPendingLock(true);
- }
if (mPendingLock) {
Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. "
+ "There's a pending lock, so we were cancelled because the device was locked "
@@ -3644,7 +3513,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mSurfaceBehindRemoteAnimationRequested = true;
if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS && !KeyguardWmStateRefactor.isEnabled()) {
- mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */);
return;
}
@@ -3665,9 +3533,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager.
- mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
- Log.d(TAG, "keyguardGoingAway requested for userId: "
- + mGoingAwayRequestedForUserId);
mActivityTaskManagerService.keyguardGoingAway(flags);
}
} catch (RemoteException e) {
@@ -4123,29 +3988,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged);
}
- private void notifyLockNowCallback() {
- List<LockNowCallback> callbacks;
- synchronized (mLockNowCallbacks) {
- callbacks = new ArrayList<LockNowCallback>(mLockNowCallbacks);
- mLockNowCallbacks.clear();
- }
- Iterator<LockNowCallback> iter = callbacks.listIterator();
- while (iter.hasNext()) {
- LockNowCallback callback = iter.next();
- iter.remove();
- if (callback.mUserId != mSelectedUserInteractor.getSelectedUserId()) {
- Log.i(TAG, "Not notifying lockNowCallback due to user mismatch");
- continue;
- }
- Log.i(TAG, "Notifying lockNowCallback");
- try {
- callback.mRemoteCallback.sendResult(null);
- } catch (RemoteException e) {
- Log.e(TAG, "Could not issue LockNowCallback sendResult", e);
- }
- }
- }
-
private void notifyTrustedChangedLocked(boolean trusted) {
int size = mKeyguardStateCallbacks.size();
for (int i = size - 1; i >= 0; i--) {
@@ -4310,14 +4152,4 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
};
}
-
- private class LockNowCallback {
- final int mUserId;
- final IRemoteCallback mRemoteCallback;
-
- LockNowCallback(int userId, IRemoteCallback remoteCallback) {
- mUserId = userId;
- mRemoteCallback = remoteCallback;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 5c03d65e570f..8f6815829ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -69,7 +69,7 @@ constructor(
* Note that [onCancel] isn't used when the scene framework is enabled.
*/
fun sharedFlow(
- duration: Duration,
+ duration: Duration = transitionDuration,
onStep: (Float) -> Float,
startTime: Duration = 0.milliseconds,
onStart: (() -> Unit)? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/GlanceableHubBlurProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/GlanceableHubBlurProvider.kt
index 19cd501fa787..50f8e086ac6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/GlanceableHubBlurProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/GlanceableHubBlurProvider.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.transitions
+import android.util.MathUtils
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -33,8 +34,18 @@ constructor(
blurConfig: BlurConfig,
) {
val exitBlurRadius: Flow<Float> =
- transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
+ transitionAnimation.sharedFlow(
+ onStep = { MathUtils.lerp(blurConfig.maxBlurRadiusPx, blurConfig.minBlurRadiusPx, it) },
+ onStart = { blurConfig.maxBlurRadiusPx },
+ onFinish = { blurConfig.minBlurRadiusPx },
+ onCancel = { blurConfig.maxBlurRadiusPx },
+ )
val enterBlurRadius: Flow<Float> =
- transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+ transitionAnimation.sharedFlow(
+ onStep = { MathUtils.lerp(blurConfig.minBlurRadiusPx, blurConfig.maxBlurRadiusPx, it) },
+ onStart = { blurConfig.minBlurRadiusPx },
+ onFinish = { blurConfig.maxBlurRadiusPx },
+ onCancel = { blurConfig.minBlurRadiusPx },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index d63c2e07b94f..0107a5278e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -23,11 +23,11 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
+import androidx.annotation.VisibleForTesting
import androidx.core.view.GestureDetectorCompat
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
import com.android.app.tracing.TraceStateLogger
-import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
@@ -38,9 +38,10 @@ import com.android.systemui.res.R
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.shared.animation.PhysicsAnimator
+import kotlin.math.sign
private const val FLING_SLOP = 1000000
-private const val DISMISS_DELAY = 100L
+@VisibleForTesting const val DISMISS_DELAY = 100L
private const val SCROLL_DELAY = 100L
private const val RUBBERBAND_FACTOR = 0.2f
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
@@ -64,7 +65,7 @@ class MediaCarouselScrollHandler(
private val closeGuts: (immediate: Boolean) -> Unit,
private val falsingManager: FalsingManager,
private val logSmartspaceImpression: (Boolean) -> Unit,
- private val logger: MediaUiEventLogger
+ private val logger: MediaUiEventLogger,
) {
/** Trace state logger for media carousel visibility */
private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
@@ -96,7 +97,7 @@ class MediaCarouselScrollHandler(
/** What's the currently visible player index? */
var visibleMediaIndex: Int = 0
- private set
+ @VisibleForTesting set
/** How much are we scrolled into the current media? */
private var scrollIntoCurrentMedia: Int = 0
@@ -137,14 +138,14 @@ class MediaCarouselScrollHandler(
eStart: MotionEvent?,
eCurrent: MotionEvent,
vX: Float,
- vY: Float
+ vY: Float,
) = onFling(vX, vY)
override fun onScroll(
down: MotionEvent?,
lastMotion: MotionEvent,
distanceX: Float,
- distanceY: Float
+ distanceY: Float,
) = onScroll(down!!, lastMotion, distanceX)
override fun onDown(e: MotionEvent): Boolean {
@@ -157,6 +158,7 @@ class MediaCarouselScrollHandler(
val touchListener =
object : Gefingerpoken {
override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
+
override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
}
@@ -168,7 +170,7 @@ class MediaCarouselScrollHandler(
scrollX: Int,
scrollY: Int,
oldScrollX: Int,
- oldScrollY: Int
+ oldScrollY: Int,
) {
if (playerWidthPlusPadding == 0) {
return
@@ -177,7 +179,7 @@ class MediaCarouselScrollHandler(
val relativeScrollX = scrollView.relativeScrollX
onMediaScrollingChanged(
relativeScrollX / playerWidthPlusPadding,
- relativeScrollX % playerWidthPlusPadding
+ relativeScrollX % playerWidthPlusPadding,
)
}
}
@@ -209,7 +211,7 @@ class MediaCarouselScrollHandler(
0,
carouselWidth,
carouselHeight,
- cornerRadius.toFloat()
+ cornerRadius.toFloat(),
)
}
}
@@ -235,7 +237,7 @@ class MediaCarouselScrollHandler(
getMaxTranslation().toFloat(),
0.0f,
1.0f,
- Math.abs(contentTranslation)
+ Math.abs(contentTranslation),
)
val settingsTranslation =
(1.0f - settingsOffset) *
@@ -323,7 +325,7 @@ class MediaCarouselScrollHandler(
CONTENT_TRANSLATION,
newTranslation,
startVelocity = 0.0f,
- config = translationConfig
+ config = translationConfig,
)
.start()
scrollView.animationTargetX = newTranslation
@@ -391,7 +393,7 @@ class MediaCarouselScrollHandler(
CONTENT_TRANSLATION,
newTranslation,
startVelocity = 0.0f,
- config = translationConfig
+ config = translationConfig,
)
.start()
} else {
@@ -430,7 +432,7 @@ class MediaCarouselScrollHandler(
CONTENT_TRANSLATION,
newTranslation,
startVelocity = vX,
- config = translationConfig
+ config = translationConfig,
)
.start()
scrollView.animationTargetX = newTranslation
@@ -583,10 +585,35 @@ class MediaCarouselScrollHandler(
// We need to post this to wait for the active player becomes visible.
mainExecutor.executeDelayed(
{ scrollView.smoothScrollTo(view.left, scrollView.scrollY) },
- SCROLL_DELAY
+ SCROLL_DELAY,
)
}
+ /**
+ * Scrolls the media carousel by the number of players specified by [step]. If scrolling beyond
+ * the carousel's bounds:
+ * - If the carousel is not dismissible, the settings button is displayed.
+ * - If the carousel is dismissible, no action taken.
+ *
+ * @param step A positive number means next, and negative means previous.
+ */
+ fun scrollByStep(step: Int) {
+ val destIndex = visibleMediaIndex + step
+ if (destIndex >= mediaContent.childCount || destIndex < 0) {
+ if (!showsSettingsButton) return
+ var translation = getMaxTranslation() * sign(-step.toFloat())
+ translation = if (isRtl) -translation else translation
+ PhysicsAnimator.getInstance(this)
+ .spring(CONTENT_TRANSLATION, translation, config = translationConfig)
+ .start()
+ scrollView.animationTargetX = translation
+ } else if (scrollView.getContentTranslation() != 0.0f) {
+ resetTranslation(true)
+ } else {
+ scrollToPlayer(destIndex = destIndex)
+ }
+ }
+
companion object {
private val CONTENT_TRANSLATION =
object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index f0f476e65e2f..364da5f8e80d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -30,19 +30,14 @@ import com.android.systemui.compose.ComposeInitializer
import com.android.systemui.res.R
/** A view that can serve as the root of the main SysUI window. */
-open class WindowRootView(
- context: Context,
- attrs: AttributeSet?,
-) :
- FrameLayout(
- context,
- attrs,
- ) {
+open class WindowRootView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
private lateinit var layoutInsetsController: LayoutInsetsController
private var leftInset = 0
private var rightInset = 0
+ private var previousInsets: WindowInsets? = null
+
override fun onAttachedToWindow() {
super.onAttachedToWindow()
@@ -66,11 +61,14 @@ open class WindowRootView(
override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams? {
return LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT
+ FrameLayout.LayoutParams.MATCH_PARENT,
)
}
override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
+ if (windowInsets == previousInsets) {
+ return windowInsets
+ }
val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
if (fitsSystemWindows) {
val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
@@ -95,7 +93,7 @@ open class WindowRootView(
leftInset = pairInsets.first
rightInset = pairInsets.second
applyMargins()
- return windowInsets
+ return windowInsets.also { previousInsets = WindowInsets(it) }
}
fun setLayoutInsetsController(layoutInsetsController: LayoutInsetsController) {
@@ -143,37 +141,22 @@ open class WindowRootView(
interface LayoutInsetsController {
/** Update the insets and calculate them accordingly. */
- fun getinsets(
- windowInsets: WindowInsets?,
- displayCutout: DisplayCutout?,
- ): Pair<Int, Int>
+ fun getinsets(windowInsets: WindowInsets?, displayCutout: DisplayCutout?): Pair<Int, Int>
}
private class LayoutParams : FrameLayout.LayoutParams {
var ignoreRightInset = false
- constructor(
- width: Int,
- height: Int,
- ) : super(
- width,
- height,
- )
+ constructor(width: Int, height: Int) : super(width, height)
@SuppressLint("CustomViewStyleable")
- constructor(
- context: Context,
- attrs: AttributeSet?,
- ) : super(
- context,
- attrs,
- ) {
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
val obtainedAttributes =
context.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout)
ignoreRightInset =
obtainedAttributes.getBoolean(
R.styleable.StatusBarWindowView_Layout_ignoreRightInset,
- false
+ false,
)
obtainedAttributes.recycle()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 155049f512d8..31fdec6147f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -93,6 +93,7 @@ public class NotificationShelf extends ActivatableNotificationView {
private int mPaddingBetweenElements;
private int mNotGoneIndex;
private boolean mHasItemsInStableShelf;
+ private boolean mAlignedToEnd;
private int mScrollFastThreshold;
private boolean mInteractive;
private boolean mAnimationsEnabled = true;
@@ -412,8 +413,22 @@ public class NotificationShelf extends ActivatableNotificationView {
public boolean isAlignedToEnd() {
if (!NotificationMinimalism.isEnabled()) {
return false;
+ } else if (SceneContainerFlag.isEnabled()) {
+ return mAlignedToEnd;
+ } else {
+ return mAmbientState.getUseSplitShade();
+ }
+ }
+
+ /** @see #isAlignedToEnd() */
+ public void setAlignedToEnd(boolean alignedToEnd) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ if (mAlignedToEnd != alignedToEnd) {
+ mAlignedToEnd = alignedToEnd;
+ requestLayout();
}
- return mAmbientState.getUseSplitShade();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 09cc3f23032e..9dc651ed507a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -643,6 +643,10 @@ public final class NotificationEntry extends ListEntry {
return row.isMediaRow();
}
+ public boolean containsCustomViews() {
+ return getSbn().getNotification().containsCustomViews();
+ }
+
public void resetUserExpansion() {
if (row != null) row.resetUserExpansion();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
index 6491223e6e10..f9e9bee4d809 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -12,7 +12,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.util.children
/** Walks view hiearchy of a given notification to estimate its memory use. */
-internal object NotificationMemoryViewWalker {
+object NotificationMemoryViewWalker {
private const val TAG = "NotificationMemory"
@@ -26,9 +26,13 @@ internal object NotificationMemoryViewWalker {
private var softwareBitmaps = 0
fun addSmallIcon(smallIconUse: Int) = apply { smallIcon += smallIconUse }
+
fun addLargeIcon(largeIconUse: Int) = apply { largeIcon += largeIconUse }
+
fun addSystem(systemIconUse: Int) = apply { systemIcons += systemIconUse }
+
fun addStyle(styleUse: Int) = apply { style += styleUse }
+
fun addSoftwareBitmapPenalty(softwareBitmapUse: Int) = apply {
softwareBitmaps += softwareBitmapUse
}
@@ -67,14 +71,14 @@ internal object NotificationMemoryViewWalker {
getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
getViewUsage(
ViewType.PRIVATE_CONTRACTED_VIEW,
- row.privateLayout?.contractedChild
+ row.privateLayout?.contractedChild,
),
getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
getViewUsage(
ViewType.PUBLIC_VIEW,
row.publicLayout?.expandedChild,
row.publicLayout?.contractedChild,
- row.publicLayout?.headsUpChild
+ row.publicLayout?.headsUpChild,
),
)
.filterNotNull()
@@ -107,14 +111,14 @@ internal object NotificationMemoryViewWalker {
row.publicLayout?.expandedChild,
row.publicLayout?.contractedChild,
row.publicLayout?.headsUpChild,
- seenObjects = seenObjects
+ seenObjects = seenObjects,
)
}
private fun getViewUsage(
type: ViewType,
vararg rootViews: View?,
- seenObjects: HashSet<Int> = hashSetOf()
+ seenObjects: HashSet<Int> = hashSetOf(),
): NotificationViewUsage? {
val usageBuilder = lazy { UsageBuilder() }
rootViews.forEach { rootView ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 76ba7f9ea901..2bc48746f847 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -106,7 +106,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
@Override
public void triggerMagneticForce(float endTranslation, @NonNull SpringForce springForce,
float startVelocity) {
- cancelMagneticAnimations();
+ cancelTranslationAnimations();
mMagneticAnimator.setSpring(springForce);
mMagneticAnimator.setStartVelocity(startVelocity);
mMagneticAnimator.animateToFinalPosition(endTranslation);
@@ -114,11 +114,15 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
@Override
public void cancelMagneticAnimations() {
- cancelTranslationAnimations();
mMagneticAnimator.cancel();
}
@Override
+ public void cancelTranslationAnimations() {
+ ExpandableView.this.cancelTranslationAnimations();
+ }
+
+ @Override
public boolean canRowBeDismissed() {
return canExpandableViewBeDismissed();
}
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 c7e15fdb98c7..73e8246907aa 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
@@ -901,6 +901,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder
if (!satisfiesMinHeightRequirement(view, entry, resources)) {
return "inflated notification does not meet minimum height requirement";
}
+
+ if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) {
+ if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) {
+ return "inflated notification does not meet maximum memory size requirement";
+ }
+ }
+
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentCompat.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentCompat.java
new file mode 100644
index 000000000000..c55cb6725e45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentCompat.java
@@ -0,0 +1,36 @@
+/*
+ * 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.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.os.Build;
+
+/**
+ * Holds compat {@link ChangeId} for {@link NotificationCustomContentMemoryVerifier}.
+ */
+final class NotificationCustomContentCompat {
+ /**
+ * Enables memory size checking of custom views included in notifications to ensure that
+ * they conform to the size limit set in `config_notificationStripRemoteViewSizeBytes`
+ * config.xml parameter.
+ * Notifications exceeding the size will be rejected.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ public static final long CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS = 270553691L;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifier.kt
new file mode 100644
index 000000000000..a3e6a5cddc94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifier.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.compat.CompatChanges
+import android.content.Context
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.traceSection
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/** Checks whether Notifications with Custom content views conform to configured memory limits. */
+object NotificationCustomContentMemoryVerifier {
+
+ private const val NOTIFICATION_SERVICE_TAG = "NotificationService"
+
+ /** Notifications with custom views need to conform to maximum memory consumption. */
+ @JvmStatic
+ fun requiresImageViewMemorySizeCheck(entry: NotificationEntry): Boolean {
+ if (!com.android.server.notification.Flags.notificationCustomViewUriRestriction()) {
+ return false
+ }
+
+ return entry.containsCustomViews()
+ }
+
+ /**
+ * This walks the custom view hierarchy contained in the passed Notification view and determines
+ * if the total memory consumption of all image views satisfies the limit set by
+ * [getStripViewSizeLimit]. It will also log to logcat if the limit exceeds
+ * [getWarnViewSizeLimit].
+ *
+ * @return true if the Notification conforms to the view size limits.
+ */
+ @JvmStatic
+ fun satisfiesMemoryLimits(view: View, entry: NotificationEntry): Boolean {
+ val mainColumnView =
+ view.findViewById<View>(com.android.internal.R.id.notification_main_column)
+ if (mainColumnView == null) {
+ Log.wtf(
+ NOTIFICATION_SERVICE_TAG,
+ "R.id.notification_main_column view should not be null!",
+ )
+ return true
+ }
+
+ val memorySize =
+ traceSection("computeViewHiearchyImageViewSize") {
+ computeViewHierarchyImageViewSize(view)
+ }
+
+ if (memorySize > getStripViewSizeLimit(view.context)) {
+ val stripOversizedView = isCompatChangeEnabledForUid(entry.sbn.uid)
+ if (stripOversizedView) {
+ Log.w(
+ NOTIFICATION_SERVICE_TAG,
+ "Dropped notification due to too large RemoteViews ($memorySize bytes) on " +
+ "pkg: ${entry.sbn.packageName} tag: ${entry.sbn.tag} id: ${entry.sbn.id}",
+ )
+ } else {
+ Log.w(
+ NOTIFICATION_SERVICE_TAG,
+ "RemoteViews too large on pkg: ${entry.sbn.packageName} " +
+ "tag: ${entry.sbn.tag} id: ${entry.sbn.id} " +
+ "this WILL notification WILL be dropped when targetSdk " +
+ "is set to ${Build.VERSION_CODES.BAKLAVA}!",
+ )
+ }
+
+ // We still warn for size, but return "satisfies = ok" if the target SDK
+ // is too low.
+ return !stripOversizedView
+ }
+
+ if (memorySize > getWarnViewSizeLimit(view.context)) {
+ // We emit the same warning as NotificationManagerService does to keep some consistency
+ // for developers.
+ Log.w(
+ NOTIFICATION_SERVICE_TAG,
+ "RemoteViews too large on pkg: ${entry.sbn.packageName} " +
+ "tag: ${entry.sbn.tag} id: ${entry.sbn.id} " +
+ "this notifications might be dropped in a future release",
+ )
+ }
+ return true
+ }
+
+ private fun isCompatChangeEnabledForUid(uid: Int): Boolean =
+ try {
+ CompatChanges.isChangeEnabled(
+ NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS,
+ uid,
+ )
+ } catch (e: RuntimeException) {
+ Log.wtf(NOTIFICATION_SERVICE_TAG, "Failed to contact system_server for compat change.")
+ false
+ }
+
+ @VisibleForTesting
+ @JvmStatic
+ fun computeViewHierarchyImageViewSize(view: View): Int =
+ when (view) {
+ is ViewGroup -> {
+ var use = 0
+ for (i in 0 until view.childCount) {
+ use += computeViewHierarchyImageViewSize(view.getChildAt(i))
+ }
+ use
+ }
+ is ImageView -> computeImageViewSize(view)
+ else -> 0
+ }
+
+ /**
+ * Returns the memory size of a Bitmap contained in a passed [ImageView] in bytes. If the view
+ * contains any other kind of drawable, the memory size is estimated from its intrinsic
+ * dimensions.
+ *
+ * @return Bitmap size in bytes or 0 if no drawable is set.
+ */
+ private fun computeImageViewSize(view: ImageView): Int {
+ val drawable = view.drawable
+ return computeDrawableSize(drawable)
+ }
+
+ private fun computeDrawableSize(drawable: Drawable?): Int {
+ return when (drawable) {
+ null -> 0
+ is AdaptiveIconDrawable ->
+ computeDrawableSize(drawable.foreground) +
+ computeDrawableSize(drawable.background) +
+ computeDrawableSize(drawable.monochrome)
+ is BitmapDrawable -> drawable.bitmap.allocationByteCount
+ // People can sneak large drawables into those custom memory views via resources -
+ // we use the intrisic size as a proxy for how much memory rendering those will
+ // take.
+ else -> drawable.intrinsicWidth * drawable.intrinsicHeight * 4
+ }
+ }
+
+ /** @return Size of remote views after which a size warning is logged. */
+ @VisibleForTesting
+ fun getWarnViewSizeLimit(context: Context): Int =
+ context.resources.getInteger(
+ com.android.internal.R.integer.config_notificationWarnRemoteViewSizeBytes
+ )
+
+ /** @return Size of remote views after which the notification is dropped. */
+ @VisibleForTesting
+ fun getStripViewSizeLimit(context: Context): Int =
+ context.resources.getInteger(
+ com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes
+ )
+}
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 20c3464536e9..589e5b8be240 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
@@ -1396,9 +1396,17 @@ constructor(
*/
@VisibleForTesting
fun isValidView(view: View, entry: NotificationEntry, resources: Resources): String? {
- return if (!satisfiesMinHeightRequirement(view, entry, resources)) {
- "inflated notification does not meet minimum height requirement"
- } else null
+ if (!satisfiesMinHeightRequirement(view, entry, resources)) {
+ return "inflated notification does not meet minimum height requirement"
+ }
+
+ if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) {
+ if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) {
+ return "inflated notification does not meet maximum memory size requirement"
+ }
+ }
+
+ return null
}
private fun satisfiesMinHeightRequirement(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
index 9fdd0bcc4ee9..0703f2de250d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -21,11 +21,14 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationShelf
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
/** Interactor for the [NotificationShelf] */
@SysUISingleton
@@ -35,6 +38,7 @@ constructor(
private val keyguardRepository: KeyguardRepository,
private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
private val powerInteractor: PowerInteractor,
+ private val shadeModeInteractor: ShadeModeInteractor,
private val keyguardTransitionController: LockscreenShadeTransitionController,
) {
/** Is the shelf showing on the keyguard? */
@@ -51,6 +55,16 @@ constructor(
isKeyguardShowing && isBypassEnabled
}
+ /** Should the shelf be aligned to the end in the current configuration? */
+ val isAlignedToEnd: Flow<Boolean>
+ get() =
+ shadeModeInteractor.shadeMode.map { shadeMode ->
+ when (shadeMode) {
+ ShadeMode.Split -> true
+ else -> false
+ }
+ }
+
/** Transition keyguard to the locked shade, triggered by the shelf. */
fun goToLockedShadeFromShelf() {
powerInteractor.wakeUpIfDozing("SHADE_CLICK", PowerManager.WAKE_REASON_GESTURE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 0352a304a5c1..f663ea019319 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -16,15 +16,16 @@
package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
object NotificationShelfViewBinder {
@@ -41,6 +42,11 @@ object NotificationShelfViewBinder {
viewModel.canModifyColorOfNotifications.collect(::setCanModifyColorOfNotifications)
}
launch { viewModel.isClickable.collect(::setCanInteract) }
+
+ if (SceneContainerFlag.isEnabled) {
+ launch { viewModel.isAlignedToEnd.collect(::setAlignedToEnd) }
+ }
+
registerViewListenersWhileAttached(shelf, viewModel)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index 5ca8b53d0704..96cdda6d4a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** ViewModel for [NotificationShelf]. */
@@ -40,6 +42,15 @@ constructor(
val canModifyColorOfNotifications: Flow<Boolean>
get() = interactor.isShelfStatic.map { static -> !static }
+ /** Is the shelf aligned to the end in the current configuration? */
+ val isAlignedToEnd: Flow<Boolean> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ interactor.isAlignedToEnd
+ }
+ }
+
/** Notifies that the user has clicked the shelf. */
fun onShelfClicked() {
interactor.goToLockedShadeFromShelf()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
index 3941700496f4..5a29a699a7e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
@@ -97,6 +97,7 @@ constructor(
stackScrollLayout,
MAGNETIC_TRANSLATION_MULTIPLIERS.size,
)
+ currentMagneticListeners.swipedListener()?.cancelTranslationAnimations()
newListeners.forEach {
if (currentMagneticListeners.contains(it)) {
it?.cancelMagneticAnimations()
@@ -214,22 +215,32 @@ constructor(
}
override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) {
- if (!row.isSwipedTarget()) return
-
- when (currentState) {
- State.PULLING -> {
- snapNeighborsBack(velocity)
- currentState = State.IDLE
- }
- State.DETACHED -> {
- currentState = State.IDLE
+ if (row.isSwipedTarget()) {
+ when (currentState) {
+ State.PULLING -> {
+ snapNeighborsBack(velocity)
+ currentState = State.IDLE
+ }
+ State.DETACHED -> {
+ // Cancel any detaching animation that may be occurring
+ currentMagneticListeners.swipedListener()?.cancelMagneticAnimations()
+ currentState = State.IDLE
+ }
+ else -> {}
}
- else -> {}
+ } else {
+ // A magnetic neighbor may be dismissing. In this case, we need to cancel any snap back
+ // magnetic animation to let the external dismiss animation proceed.
+ val listener = currentMagneticListeners.find { it == row.magneticRowListener }
+ listener?.cancelMagneticAnimations()
}
}
override fun reset() {
- currentMagneticListeners.forEach { it?.cancelMagneticAnimations() }
+ currentMagneticListeners.forEach {
+ it?.cancelMagneticAnimations()
+ it?.cancelTranslationAnimations()
+ }
currentState = State.IDLE
currentMagneticListeners = listOf()
currentRoundableTargets = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
index 46036d4c1fad..5959ef1e093b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
@@ -42,6 +42,9 @@ interface MagneticRowListener {
/** Cancel any animations related to the magnetic interactions of the row */
fun cancelMagneticAnimations()
+ /** Cancel any other animations related to the row's translation */
+ fun cancelTranslationAnimations()
+
/** Can the row be dismissed. */
fun canRowBeDismissed(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 48a6a4c057df..810d0b43b0dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -463,6 +463,13 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
@Override
+ public void onMagneticInteractionEnd(View view, float velocity) {
+ if (view instanceof ExpandableNotificationRow row) {
+ mMagneticNotificationRowManager.onMagneticInteractionEnd(row, velocity);
+ }
+ }
+
+ @Override
public float getTotalTranslationLength(View animView) {
return mView.getTotalTranslationLength(animView);
}
@@ -504,14 +511,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
public void onDragCancelled(View v) {
}
- @Override
- public void onDragCancelledWithVelocity(View v, float finalVelocity) {
- if (v instanceof ExpandableNotificationRow row) {
- mMagneticNotificationRowManager.onMagneticInteractionEnd(
- row, finalVelocity);
- }
- }
-
/**
* Handles cleanup after the given {@code view} has been fully swiped out (including
* re-invoking dismiss logic in case the notification has not made its way out yet).
@@ -539,10 +538,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
*/
public void handleChildViewDismissed(View view) {
- if (view instanceof ExpandableNotificationRow row) {
- mMagneticNotificationRowManager.onMagneticInteractionEnd(
- row, null /* velocity */);
- }
// The View needs to clean up the Swipe states, e.g. roundness.
mView.onSwipeEnd();
if (mView.getClearAllInProgress()) {
@@ -614,11 +609,15 @@ public class NotificationStackScrollLayoutController implements Dumpable {
@Override
public void onBeginDrag(View v) {
+ mView.onSwipeBegin(v);
+ }
+
+ @Override
+ public void setMagneticAndRoundableTargets(View v) {
if (v instanceof ExpandableNotificationRow row) {
mMagneticNotificationRowManager.setMagneticAndRoundableTargets(
row, mView, mSectionsManager);
}
- mView.onSwipeBegin(v);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index d476d482226d..6f4047f48205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -362,7 +362,8 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
superSnapChild(animView, targetLeft, velocity);
}
- mCallback.onDragCancelledWithVelocity(animView, velocity);
+ mCallback.onMagneticInteractionEnd(animView, velocity);
+ mCallback.onDragCancelled(animView);
if (targetLeft == 0) {
handleMenuCoveredOrDismissed();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index a339bc98457e..58326dbb3a34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -61,6 +61,7 @@ import kotlinx.coroutines.flow.StateFlowKt;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Objects;
/**
* The header group on Keyguard.
@@ -103,6 +104,9 @@ public class KeyguardStatusBarView extends RelativeLayout {
*/
private int mCutoutSideNudge = 0;
+ @Nullable
+ private WindowInsets mPreviousInsets = null;
+
private DisplayCutout mDisplayCutout;
private int mRoundedCornerPadding = 0;
// right and left padding applied to this view to account for cutouts and rounded corners
@@ -284,9 +288,12 @@ public class KeyguardStatusBarView extends RelativeLayout {
WindowInsets updateWindowInsets(
WindowInsets insets,
StatusBarContentInsetsProvider insetsProvider) {
- mLayoutState = LAYOUT_NONE;
- if (updateLayoutConsideringCutout(insetsProvider)) {
- requestLayout();
+ if (!Objects.equals(mPreviousInsets, insets)) {
+ mLayoutState = LAYOUT_NONE;
+ if (updateLayoutConsideringCutout(insetsProvider)) {
+ requestLayout();
+ }
+ mPreviousInsets = new WindowInsets(insets);
}
return super.onApplyWindowInsets(insets);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b2c4ef95242b..01de925f3d78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -65,6 +65,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags;
import com.android.systemui.bouncer.ui.BouncerView;
import com.android.systemui.bouncer.util.BouncerTestUtilsKt;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
@@ -170,6 +171,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final Lazy<SceneInteractor> mSceneInteractorLazy;
private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractorLazy;
private final DismissCallbackRegistry mDismissCallbackRegistry;
+ private final CommunalSceneInteractor mCommunalSceneInteractor;
private Job mListenForAlternateBouncerTransitionSteps = null;
private Job mListenForKeyguardAuthenticatedBiometricsHandled = null;
@@ -406,7 +408,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Main DelayableExecutor executor,
Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy,
DismissCallbackRegistry dismissCallbackRegistry,
- Lazy<BouncerInteractor> bouncerInteractor
+ Lazy<BouncerInteractor> bouncerInteractor,
+ CommunalSceneInteractor communalSceneInteractor
) {
mContext = context;
mExecutor = executor;
@@ -443,6 +446,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor;
mDeviceEntryInteractorLazy = deviceEntryInteractorLazy;
mDismissCallbackRegistry = dismissCallbackRegistry;
+ mCommunalSceneInteractor = communalSceneInteractor;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -1364,11 +1368,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
- boolean hideBouncerOverDream = isBouncerShowing()
- && mDreamOverlayStateController.isOverlayActive();
+ boolean hideBouncerOverDreamOrHub = isBouncerShowing()
+ && (mDreamOverlayStateController.isOverlayActive()
+ || mCommunalSceneInteractor.isIdleOnCommunal().getValue());
mCentralSurfaces.endAffordanceLaunch();
// The second condition is for SIM card locked bouncer
- if (hideBouncerOverDream || (primaryBouncerIsScrimmed() && !needsFullscreenBouncer())) {
+ if (hideBouncerOverDreamOrHub
+ || (primaryBouncerIsScrimmed() && !needsFullscreenBouncer())) {
hideBouncer(false);
updateStates();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
index 72d093c65a91..9f05850f3405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
@@ -22,11 +22,11 @@ interface SplitShadeStateController {
/** Returns true if the device should use the split notification shade. */
@Deprecated(
- message = "This is deprecated, please use ShadeInteractor#shadeMode instead",
+ message = "This is deprecated, please use ShadeModeInteractor#shadeMode instead",
replaceWith =
ReplaceWith(
- "shadeInteractor.shadeMode",
- "com.android.systemui.shade.domain.interactor.ShadeInteractor",
+ "shadeModeInteractor.shadeMode",
+ "com.android.systemui.shade.domain.interactor.ShadeModeInteractor",
),
)
fun shouldUseSplitNotificationShade(resources: Resources): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
index 4b01ded16495..f1abf105be8e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
@@ -25,7 +25,7 @@ import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineDispatcher
/**
- * Repository for observing values of [Settings.Secure] for the currently active user. That means
+ * Repository for observing values of [Settings.System] for the currently active user. That means
* when user is switched and the new user has different value, flow will emit new value.
*/
// TODO: b/377244768 - Make internal once call sites inject SystemSettingsRepository instead.
diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
index 6b7de982e00a..22a74c86e0f1 100644
--- a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
@@ -16,10 +16,8 @@
package com.android.systemui.window.data.repository
-import android.annotation.SuppressLint
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
/** Repository that maintains state for the window blur effect. */
@@ -28,6 +26,4 @@ class WindowRootViewBlurRepository @Inject constructor() {
val blurRadius = MutableStateFlow(0)
val isBlurOpaque = MutableStateFlow(false)
-
- @SuppressLint("SharedFlowCreation") val onBlurApplied = MutableSharedFlow<Int>()
}
diff --git a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
index e21e0a1cadc7..9e369347dea5 100644
--- a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.window.domain.interactor
+import android.annotation.SuppressLint
import android.util.Log
import com.android.systemui.Flags
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -28,6 +29,7 @@ import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -52,6 +54,8 @@ constructor(
private val communalInteractor: CommunalInteractor,
private val repository: WindowRootViewBlurRepository,
) {
+ @SuppressLint("SharedFlowCreation") private val _onBlurAppliedEvent = MutableSharedFlow<Int>()
+
private var isBouncerTransitionInProgress: StateFlow<Boolean> =
if (Flags.bouncerUiRevamp()) {
keyguardTransitionInteractor
@@ -68,7 +72,7 @@ constructor(
* root view.
*/
suspend fun onBlurApplied(appliedBlurRadius: Int) {
- repository.onBlurApplied.emit(appliedBlurRadius)
+ _onBlurAppliedEvent.emit(appliedBlurRadius)
}
/** Radius of blur to be applied on the window root view. */
@@ -77,7 +81,7 @@ constructor(
/**
* Emits the applied blur radius whenever blur is successfully applied to the window root view.
*/
- val onBlurAppliedEvent: Flow<Int> = repository.onBlurApplied
+ val onBlurAppliedEvent: Flow<Int> = _onBlurAppliedEvent
/** Whether the blur applied is opaque or transparent. */
val isBlurOpaque: Flow<Boolean> =
diff --git a/packages/SystemUI/tests/res/layout/custom_view_flipper.xml b/packages/SystemUI/tests/res/layout/custom_view_flipper.xml
new file mode 100644
index 000000000000..eb3ba82b043b
--- /dev/null
+++ b/packages/SystemUI/tests/res/layout/custom_view_flipper.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ViewFlipper
+ android:id="@+id/flipper"
+ android:layout_width="match_parent"
+ android:layout_height="400dp"
+ android:flipInterval="1000"
+ />
+
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/tests/res/layout/custom_view_flipper_image.xml b/packages/SystemUI/tests/res/layout/custom_view_flipper_image.xml
new file mode 100644
index 000000000000..e2a00bd845cd
--- /dev/null
+++ b/packages/SystemUI/tests/res/layout/custom_view_flipper_image.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/imageview"
+ android:layout_width="match_parent"
+ android:layout_height="400dp" /> \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index e8054c07eac8..4ccfa29d4ba0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -206,7 +206,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock ShadeInteractor mShadeInteractor;
private @Mock ShadeWindowLogger mShadeWindowLogger;
private @Mock SelectedUserInteractor mSelectedUserInteractor;
- private @Mock UserTracker.Callback mUserTrackerCallback;
private @Mock KeyguardInteractor mKeyguardInteractor;
private @Mock KeyguardTransitionBootInteractor mKeyguardTransitionBootInteractor;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
@@ -281,7 +280,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
() -> mShadeInteractor,
mShadeWindowLogger,
() -> mSelectedUserInteractor,
- mock(UserTracker.class),
+ mUserTracker,
mKosmos.getNotificationShadeWindowModel(),
mSecureSettings,
mKosmos::getCommunalInteractor,
@@ -319,7 +318,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
} catch (Exception e) {
// Just so we don't have to add the exception signature to every test.
- fail(e.getMessage());
+ fail();
}
}
@@ -331,156 +330,18 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
/* First test the default behavior: handleUserSwitching() is not invoked */
when(mUserTracker.isUserSwitching()).thenReturn(false);
+ mViewMediator.mUpdateCallback = mock(KeyguardUpdateMonitorCallback.class);
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
- verify(mUserTrackerCallback, never()).onUserChanging(eq(userId), eq(mContext),
- any(Runnable.class));
+ verify(mViewMediator.mUpdateCallback, never()).onUserSwitching(userId);
/* Next test user switching is already in progress when started */
when(mUserTracker.isUserSwitching()).thenReturn(true);
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
- verify(mUserTrackerCallback).onUserChanging(eq(userId), eq(mContext),
- any(Runnable.class));
- }
-
- @Test
- @TestableLooper.RunWithLooper(setAsMainLooper = true)
- public void testGoingAwayFollowedByBeforeUserSwitchDoesNotHideKeyguard() {
- setCurrentUser(/* userId= */1099, /* isSecure= */false);
-
- // Setup keyguard
- mViewMediator.onSystemReady();
- processAllMessagesAndBgExecutorMessages();
- mViewMediator.setShowingLocked(true, "");
-
- // Request keyguard going away
- when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
- mViewMediator.mKeyguardGoingAwayRunnable.run();
-
- // After the request, begin a switch to a new secure user
- int nextUserId = 500;
- setCurrentUser(nextUserId, /* isSecure= */true);
- Runnable result = mock(Runnable.class);
- mViewMediator.handleBeforeUserSwitching(nextUserId, result);
- processAllMessagesAndBgExecutorMessages();
- verify(result).run();
-
- // After that request has begun, have WM tell us to exit keyguard
- RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
- mock(RemoteAnimationTarget.class)
- };
- RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
- mock(RemoteAnimationTarget.class)
- };
- IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
- mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
- null, callback);
- processAllMessagesAndBgExecutorMessages();
-
- // The call to exit should be rejected, and keyguard should still be visible
- verify(mKeyguardUnlockAnimationController, never()).notifyStartSurfaceBehindRemoteAnimation(
- any(), any(), any(), anyLong(), anyBoolean());
- try {
- assertATMSLockScreenShowing(true);
- } catch (Exception e) {
- fail(e.getMessage());
- }
- assertTrue(mViewMediator.isShowingAndNotOccluded());
- }
-
- @Test
- @TestableLooper.RunWithLooper(setAsMainLooper = true)
- public void testUserSwitchToSecureUserShowsBouncer() {
- setCurrentUser(/* userId= */1099, /* isSecure= */true);
-
- // Setup keyguard
- mViewMediator.onSystemReady();
- processAllMessagesAndBgExecutorMessages();
- mViewMediator.setShowingLocked(true, "");
-
- // After the request, begin a switch to a new secure user
- int nextUserId = 500;
- setCurrentUser(nextUserId, /* isSecure= */true);
-
- Runnable beforeResult = mock(Runnable.class);
- mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult);
- processAllMessagesAndBgExecutorMessages();
- verify(beforeResult).run();
-
- // Dismiss should not be called while user switch is in progress
- Runnable onSwitchResult = mock(Runnable.class);
- mViewMediator.handleUserSwitching(nextUserId, onSwitchResult);
- processAllMessagesAndBgExecutorMessages();
- verify(onSwitchResult).run();
- verify(mStatusBarKeyguardViewManager, never()).dismissAndCollapse();
-
- // The attempt to dismiss only comes on user switch complete, which will trigger a call to
- // show the bouncer in StatusBarKeyguardViewManager
- mViewMediator.handleUserSwitchComplete(nextUserId);
- TestableLooper.get(this).moveTimeForward(600);
- processAllMessagesAndBgExecutorMessages();
-
- verify(mStatusBarKeyguardViewManager).dismissAndCollapse();
- }
-
- @Test
- @TestableLooper.RunWithLooper(setAsMainLooper = true)
- public void testUserSwitchToInsecureUserDismissesKeyguard() {
- int userId = 1099;
- when(mUserTracker.getUserId()).thenReturn(userId);
-
- // Setup keyguard
- mViewMediator.onSystemReady();
- processAllMessagesAndBgExecutorMessages();
- mViewMediator.setShowingLocked(true, "");
-
- // After the request, begin a switch to an insecure user
- int nextUserId = 500;
- when(mLockPatternUtils.isSecure(nextUserId)).thenReturn(false);
-
- Runnable beforeResult = mock(Runnable.class);
- mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult);
- processAllMessagesAndBgExecutorMessages();
- verify(beforeResult).run();
-
- // The call to dismiss comes during the user switch
- Runnable onSwitchResult = mock(Runnable.class);
- mViewMediator.handleUserSwitching(nextUserId, onSwitchResult);
- processAllMessagesAndBgExecutorMessages();
- verify(onSwitchResult).run();
-
- verify(mStatusBarKeyguardViewManager).dismissAndCollapse();
- }
-
- @Test
- @TestableLooper.RunWithLooper(setAsMainLooper = true)
- public void testUserSwitchToSecureUserWhileKeyguardNotVisibleShowsKeyguard() {
- setCurrentUser(/* userId= */1099, /* isSecure= */true);
-
- // Setup keyguard as not visible
- mViewMediator.onSystemReady();
- processAllMessagesAndBgExecutorMessages();
- mViewMediator.setShowingLocked(false, "");
- processAllMessagesAndBgExecutorMessages();
-
- // Begin a switch to a new secure user
- int nextUserId = 500;
- setCurrentUser(nextUserId, /* isSecure= */true);
-
- Runnable beforeResult = mock(Runnable.class);
- mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult);
- processAllMessagesAndBgExecutorMessages();
- verify(beforeResult).run();
-
- try {
- assertATMSLockScreenShowing(true);
- } catch (Exception e) {
- fail();
- }
- assertTrue(mViewMediator.isShowingAndNotOccluded());
+ verify(mViewMediator.mUpdateCallback).onUserSwitching(userId);
}
@Test
@@ -1244,7 +1105,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
processAllMessagesAndBgExecutorMessages();
verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean());
-
+ assertATMSAndKeyguardViewMediatorStatesMatch();
}
@Test
@@ -1288,7 +1149,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
- mViewMediator.mKeyguardGoingAwayRunnable.run();
mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
null, callback);
processAllMessagesAndBgExecutorMessages();
@@ -1343,6 +1203,13 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
// The captor will have the most recent setLockScreenShown call's value.
assertEquals(showing, showingCaptor.getValue());
+
+ // We're now just after the last setLockScreenShown call. If we expect the lockscreen to be
+ // showing, ensure that we didn't subsequently ask for it to go away.
+ if (showing) {
+ orderedSetLockScreenShownCalls.verify(mActivityTaskManagerService, never())
+ .keyguardGoingAway(anyInt());
+ }
}
/**
@@ -1504,7 +1371,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mKeyguardTransitionBootInteractor,
mKosmos::getCommunalSceneInteractor,
mock(WindowManagerOcclusionManager.class));
- mViewMediator.mUserChangedCallback = mUserTrackerCallback;
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null);
@@ -1518,10 +1384,4 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private void captureKeyguardUpdateMonitorCallback() {
verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture());
}
-
- private void setCurrentUser(int userId, boolean isSecure) {
- when(mUserTracker.getUserId()).thenReturn(userId);
- when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(userId);
- when(mLockPatternUtils.isSecure(userId)).thenReturn(isSecure);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierFlagDisabledTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierFlagDisabledTest.java
new file mode 100644
index 000000000000..09fa3871f6e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierFlagDisabledTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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 static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildAcceptableNotificationEntry;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.DisableFlags;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.notification.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationCustomContentMemoryVerifierFlagDisabledTest extends SysuiTestCase {
+
+ @Rule
+ public PlatformCompatChangeRule mCompatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ @DisableFlags(Flags.FLAG_NOTIFICATION_CUSTOM_VIEW_URI_RESTRICTION)
+ @EnableCompatChanges({
+ NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS
+ })
+ public void requiresImageViewMemorySizeCheck_flagDisabled_returnsFalse() {
+ NotificationEntry entry = buildAcceptableNotificationEntry(mContext);
+ assertThat(NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry))
+ .isFalse();
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java
new file mode 100644
index 000000000000..1cadb3c0a909
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java
@@ -0,0 +1,285 @@
+/*
+ * 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 static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildAcceptableNotification;
+import static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildAcceptableNotificationEntry;
+import static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildOversizedNotification;
+import static com.android.systemui.statusbar.notification.row.NotificationCustomContentNotificationBuilder.buildWarningSizedNotification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Notification;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.platform.test.annotations.EnableFlags;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.notification.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileNotFoundException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_NOTIFICATION_CUSTOM_VIEW_URI_RESTRICTION)
+public class NotificationCustomContentMemoryVerifierTest extends SysuiTestCase {
+
+ private static final String AUTHORITY = "notification.memory.test.authority";
+ private static final Uri TEST_URI = new Uri.Builder()
+ .scheme("content")
+ .authority(AUTHORITY)
+ .path("path")
+ .build();
+
+ @Rule
+ public PlatformCompatChangeRule mCompatChangeRule = new PlatformCompatChangeRule();
+
+ @Before
+ public void setUp() {
+ TestImageContentProvider provider = new TestImageContentProvider(mContext);
+ mContext.getContentResolver().addProvider(AUTHORITY, provider);
+ provider.onCreate();
+ }
+
+ @Test
+ @EnableCompatChanges({
+ NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS})
+ public void requiresImageViewMemorySizeCheck_customViewNotification_returnsTrue() {
+ NotificationEntry entry =
+ buildAcceptableNotificationEntry(
+ mContext);
+ assertThat(NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry))
+ .isTrue();
+ }
+
+ @Test
+ @EnableCompatChanges({
+ NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS})
+ public void requiresImageViewMemorySizeCheck_plainNotification_returnsFalse() {
+ Notification notification =
+ new Notification.Builder(mContext, "ChannelId")
+ .setContentTitle("Just a notification")
+ .setContentText("Yep")
+ .build();
+ NotificationEntry entry = new NotificationEntryBuilder().setNotification(
+ notification).build();
+ assertThat(NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry))
+ .isFalse();
+ }
+
+
+ @Test
+ @EnableCompatChanges({
+ NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS})
+ public void satisfiesMemoryLimits_smallNotification_returnsTrue() {
+ Notification.Builder notification =
+ buildAcceptableNotification(mContext,
+ TEST_URI);
+ NotificationEntry entry = toEntry(notification);
+ View inflatedView = inflateNotification(notification);
+ assertThat(
+ NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(inflatedView, entry)
+ )
+ .isTrue();
+ }
+
+ @Test
+ @EnableCompatChanges({
+ NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS})
+ public void satisfiesMemoryLimits_oversizedNotification_returnsFalse() {
+ Notification.Builder notification =
+ buildOversizedNotification(mContext,
+ TEST_URI);
+ NotificationEntry entry = toEntry(notification);
+ View inflatedView = inflateNotification(notification);
+ assertThat(
+ NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(inflatedView, entry)
+ ).isFalse();
+ }
+
+ @Test
+ @DisableCompatChanges(
+ {NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS}
+ )
+ public void satisfiesMemoryLimits_oversizedNotification_compatDisabled_returnsTrue() {
+ Notification.Builder notification =
+ buildOversizedNotification(mContext,
+ TEST_URI);
+ NotificationEntry entry = toEntry(notification);
+ View inflatedView = inflateNotification(notification);
+ assertThat(
+ NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(inflatedView, entry)
+ ).isTrue();
+ }
+
+ @Test
+ @EnableCompatChanges({
+ NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS})
+ public void satisfiesMemoryLimits_warningSizedNotification_returnsTrue() {
+ Notification.Builder notification =
+ buildWarningSizedNotification(mContext,
+ TEST_URI);
+ NotificationEntry entry = toEntry(notification);
+ View inflatedView = inflateNotification(notification);
+ assertThat(
+ NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(inflatedView, entry)
+ )
+ .isTrue();
+ }
+
+ @Test
+ @EnableCompatChanges({
+ NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS})
+ public void satisfiesMemoryLimits_viewWithoutCustomNotificationRoot_returnsTrue() {
+ NotificationEntry entry = new NotificationEntryBuilder().build();
+ View view = new FrameLayout(mContext);
+ assertThat(NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry))
+ .isTrue();
+ }
+
+ @Test
+ @EnableCompatChanges({
+ NotificationCustomContentCompat.CHECK_SIZE_OF_INFLATED_CUSTOM_VIEWS})
+ public void computeViewHierarchyImageViewSize_smallNotification_returnsSensibleValue() {
+ Notification.Builder notification =
+ buildAcceptableNotification(mContext,
+ TEST_URI);
+ // This should have a size of a single image
+ View inflatedView = inflateNotification(notification);
+ assertThat(
+ NotificationCustomContentMemoryVerifier.computeViewHierarchyImageViewSize(
+ inflatedView))
+ .isGreaterThan(170000);
+ }
+
+ private View inflateNotification(Notification.Builder builder) {
+ RemoteViews remoteViews = builder.createBigContentView();
+ return remoteViews.apply(mContext, new FrameLayout(mContext));
+ }
+
+ private NotificationEntry toEntry(Notification.Builder builder) {
+ return new NotificationEntryBuilder().setNotification(builder.build())
+ .setUid(Process.myUid()).build();
+ }
+
+
+ /** This provider serves the images for inflation. */
+ class TestImageContentProvider extends ContentProvider {
+
+ TestImageContentProvider(Context context) {
+ ProviderInfo info = new ProviderInfo();
+ info.authority = AUTHORITY;
+ info.exported = true;
+ attachInfoForTesting(context, info);
+ setAuthorities(AUTHORITY);
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) {
+ return getContext().getResources().openRawResourceFd(
+ NotificationCustomContentNotificationBuilder.getDRAWABLE_IMAGE_RESOURCE())
+ .getParcelFileDescriptor();
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) {
+ return getContext().getResources().openRawResourceFd(
+ NotificationCustomContentNotificationBuilder.getDRAWABLE_IMAGE_RESOURCE());
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts,
+ CancellationSignal signal) throws FileNotFoundException {
+ return openTypedAssetFile(uri, mimeTypeFilter, opts);
+ }
+
+ @Override
+ public int delete(Uri uri, Bundle extras) {
+ return 0;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return "image/png";
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values, Bundle extras) {
+ return super.insert(uri, values, extras);
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, Bundle queryArgs,
+ CancellationSignal cancellationSignal) {
+ return super.query(uri, projection, queryArgs, cancellationSignal);
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+ }
+
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentNotificationBuilder.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentNotificationBuilder.kt
new file mode 100644
index 000000000000..ca4f24da3c08
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentNotificationBuilder.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+@file:JvmName("NotificationCustomContentNotificationBuilder")
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Notification.DecoratedCustomViewStyle
+import android.content.Context
+import android.graphics.drawable.BitmapDrawable
+import android.net.Uri
+import android.os.Process
+import android.widget.RemoteViews
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.tests.R
+import org.hamcrest.Matchers.lessThan
+import org.junit.Assume.assumeThat
+
+public val DRAWABLE_IMAGE_RESOURCE = R.drawable.romainguy_rockaway
+
+fun buildAcceptableNotificationEntry(context: Context): NotificationEntry {
+ return NotificationEntryBuilder()
+ .setNotification(buildAcceptableNotification(context, null).build())
+ .setUid(Process.myUid())
+ .build()
+}
+
+fun buildAcceptableNotification(context: Context, uri: Uri?): Notification.Builder =
+ buildNotification(context, uri, 1)
+
+fun buildOversizedNotification(context: Context, uri: Uri): Notification.Builder {
+ val numImagesForOversize =
+ (NotificationCustomContentMemoryVerifier.getStripViewSizeLimit(context) /
+ drawableSizeOnDevice(context)) + 2
+ return buildNotification(context, uri, numImagesForOversize)
+}
+
+fun buildWarningSizedNotification(context: Context, uri: Uri): Notification.Builder {
+ val numImagesForOversize =
+ (NotificationCustomContentMemoryVerifier.getWarnViewSizeLimit(context) /
+ drawableSizeOnDevice(context)) + 1
+ // The size needs to be smaller than outright stripping size.
+ assumeThat(
+ numImagesForOversize * drawableSizeOnDevice(context),
+ lessThan(NotificationCustomContentMemoryVerifier.getStripViewSizeLimit(context)),
+ )
+ return buildNotification(context, uri, numImagesForOversize)
+}
+
+fun buildNotification(context: Context, uri: Uri?, numImages: Int): Notification.Builder {
+ val remoteViews = RemoteViews(context.packageName, R.layout.custom_view_flipper)
+ repeat(numImages) { i ->
+ val remoteViewFlipperImageView =
+ RemoteViews(context.packageName, R.layout.custom_view_flipper_image)
+
+ if (uri == null) {
+ remoteViewFlipperImageView.setImageViewResource(
+ R.id.imageview,
+ R.drawable.romainguy_rockaway,
+ )
+ } else {
+ val imageUri = uri.buildUpon().appendPath(i.toString()).build()
+ remoteViewFlipperImageView.setImageViewUri(R.id.imageview, imageUri)
+ }
+ remoteViews.addView(R.id.flipper, remoteViewFlipperImageView)
+ }
+
+ return Notification.Builder(context, "ChannelId")
+ .setSmallIcon(android.R.drawable.ic_info)
+ .setStyle(DecoratedCustomViewStyle())
+ .setCustomContentView(remoteViews)
+ .setCustomBigContentView(remoteViews)
+ .setContentTitle("This is a remote view!")
+}
+
+fun drawableSizeOnDevice(context: Context): Int {
+ val drawable = context.resources.getDrawable(DRAWABLE_IMAGE_RESOURCE)
+ return (drawable as BitmapDrawable).bitmap.allocationByteCount
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorKosmos.kt
index 2057b849c069..c7380c91f703 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.statusbar.lockscreenShadeTransitionController
val Kosmos.notificationShelfInteractor by Fixture {
@@ -28,6 +29,7 @@ val Kosmos.notificationShelfInteractor by Fixture {
keyguardRepository = keyguardRepository,
deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
powerInteractor = powerInteractor,
+ shadeModeInteractor = shadeModeInteractor,
keyguardTransitionController = lockscreenShadeTransitionController,
)
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b75728e9f97c..f03e8c713228 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -110,6 +110,7 @@ import android.util.SparseIntArray;
import android.view.Display;
import android.view.WindowManager;
import android.widget.Toast;
+import android.window.DisplayWindowPolicyController;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -1411,8 +1412,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mirroredDisplayId == Display.INVALID_DISPLAY ? displayId : mirroredDisplayId;
}
- @GuardedBy("mVirtualDeviceLock")
- private GenericWindowPolicyController createWindowPolicyControllerLocked(
+ private GenericWindowPolicyController createWindowPolicyController(
@NonNull Set<String> displayCategories) {
final boolean activityLaunchAllowedByDefault =
getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT;
@@ -1421,28 +1421,28 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
final boolean showTasksInHostDeviceRecents =
getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT;
- if (mActivityListenerAdapter == null) {
- mActivityListenerAdapter = new GwpcActivityListener();
- }
-
- final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
- WindowManager.LayoutParams.FLAG_SECURE,
- WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
- mAttributionSource,
- getAllowedUserHandles(),
- activityLaunchAllowedByDefault,
- mActivityPolicyExemptions,
- mActivityPolicyPackageExemptions,
- crossTaskNavigationAllowedByDefault,
- /* crossTaskNavigationExemptions= */crossTaskNavigationAllowedByDefault
- ? mParams.getBlockedCrossTaskNavigations()
- : mParams.getAllowedCrossTaskNavigations(),
- mActivityListenerAdapter,
- displayCategories,
- showTasksInHostDeviceRecents,
- mParams.getHomeComponent());
- gwpc.registerRunningAppsChangedListener(/* listener= */ this);
- return gwpc;
+ synchronized (mVirtualDeviceLock) {
+ if (mActivityListenerAdapter == null) {
+ mActivityListenerAdapter = new GwpcActivityListener();
+ }
+
+ return new GenericWindowPolicyController(
+ WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ mAttributionSource,
+ getAllowedUserHandles(),
+ activityLaunchAllowedByDefault,
+ mActivityPolicyExemptions,
+ mActivityPolicyPackageExemptions,
+ crossTaskNavigationAllowedByDefault,
+ /* crossTaskNavigationExemptions= */crossTaskNavigationAllowedByDefault
+ ? mParams.getBlockedCrossTaskNavigations()
+ : mParams.getAllowedCrossTaskNavigations(),
+ mActivityListenerAdapter,
+ displayCategories,
+ showTasksInHostDeviceRecents,
+ mParams.getHomeComponent());
+ }
}
@Override // Binder call
@@ -1450,55 +1450,54 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@NonNull IVirtualDisplayCallback callback) {
checkCallerIsDeviceOwner();
- int displayId;
- boolean showPointer;
- boolean isTrustedDisplay;
- GenericWindowPolicyController gwpc;
- synchronized (mVirtualDeviceLock) {
- gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
- displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig,
+ final boolean isTrustedDisplay =
+ (virtualDisplayConfig.getFlags() & DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED)
+ == DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+ if (!isTrustedDisplay && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
+ throw new SecurityException(
+ "All displays must be trusted for devices with custom clipboard policy.");
+ }
+
+ GenericWindowPolicyController gwpc =
+ createWindowPolicyController(virtualDisplayConfig.getDisplayCategories());
+
+ // Create the display outside of the lock to avoid deadlock. DisplayManagerService will
+ // acquire the global WM lock while creating the display. At the same time, WM may query
+ // VDM and this virtual device to get policies, display ownership, etc.
+ int displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig,
callback, this, gwpc, mOwnerPackageName);
- boolean isMirrorDisplay =
- mDisplayManagerInternal.getDisplayIdToMirror(displayId)
- != Display.INVALID_DISPLAY;
- gwpc.setDisplayId(displayId, isMirrorDisplay);
- isTrustedDisplay =
- (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
- == Display.FLAG_TRUSTED;
- if (!isTrustedDisplay
- && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
- throw new SecurityException("All displays must be trusted for devices with "
- + "custom clipboard policy.");
- }
+ if (displayId == Display.INVALID_DISPLAY) {
+ return displayId;
+ }
- if (mVirtualDisplays.contains(displayId)) {
- gwpc.unregisterRunningAppsChangedListener(this);
- throw new IllegalStateException(
- "Virtual device already has a virtual display with ID " + displayId);
+ // DisplayManagerService will call onVirtualDisplayCreated() after the display is created,
+ // while holding its own lock to ensure that this device knows about the display before any
+ // other display listeners are notified about the display creation.
+ VirtualDisplayWrapper displayWrapper;
+ boolean showPointer;
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplays.contains(displayId)) {
+ throw new IllegalStateException("Virtual device was not notified about the "
+ + "creation of display with ID " + displayId);
}
-
- PowerManager.WakeLock wakeLock =
- isTrustedDisplay ? createAndAcquireWakeLockForDisplay(displayId) : null;
- mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock,
- isTrustedDisplay, isMirrorDisplay));
+ displayWrapper = mVirtualDisplays.get(displayId);
showPointer = mDefaultShowPointerIcon;
}
+ displayWrapper.acquireWakeLock();
+ gwpc.registerRunningAppsChangedListener(/* listener= */ this);
- final long token = Binder.clearCallingIdentity();
- try {
+ Binder.withCleanCallingIdentity(() -> {
mInputController.setMouseScalingEnabled(false, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
- if (isTrustedDisplay) {
+ if (displayWrapper.isTrusted()) {
mInputController.setShowPointerIcon(showPointer, displayId);
mInputController.setDisplayImePolicy(displayId,
WindowManager.DISPLAY_IME_POLICY_LOCAL);
} else {
gwpc.setShowInHostDeviceRecents(true);
}
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ });
Counter.logIncrementWithUid(
"virtual_devices.value_virtual_display_created_count",
@@ -1506,7 +1505,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return displayId;
}
- private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+ private PowerManager.WakeLock createWakeLockForDisplay(int displayId) {
if (Flags.deviceAwareDisplayPower()) {
return null;
}
@@ -1516,7 +1515,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
TAG + ":" + displayId, displayId);
- wakeLock.acquire();
return wakeLock;
} finally {
Binder.restoreCallingIdentity(token);
@@ -1561,17 +1559,47 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return result;
}
+ /**
+ * DisplayManagerService is notifying this virtual device about the display creation. This
+ * should happen before the DisplayManagerInternal#createVirtualDisplay() call above
+ * returns.
+ * This is called while holding the DisplayManagerService lock, so no heavy-weight work must
+ * be done here and especially *** no calls to WindowManager! ***
+ */
+ public void onVirtualDisplayCreated(int displayId, IVirtualDisplayCallback callback,
+ DisplayWindowPolicyController dwpc) {
+ final boolean isMirrorDisplay =
+ mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY;
+ final boolean isTrustedDisplay =
+ (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+ == Display.FLAG_TRUSTED;
+
+ GenericWindowPolicyController gwpc = (GenericWindowPolicyController) dwpc;
+ gwpc.setDisplayId(displayId, isMirrorDisplay);
+ PowerManager.WakeLock wakeLock =
+ isTrustedDisplay ? createWakeLockForDisplay(displayId) : null;
+ synchronized (mVirtualDeviceLock) {
+ if (mVirtualDisplays.contains(displayId)) {
+ Slog.wtf(TAG, "Virtual device already has a virtual display with ID " + displayId);
+ return;
+ }
+ mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock,
+ isTrustedDisplay, isMirrorDisplay));
+ }
+ }
+
+ /**
+ * This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
+ * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
+ * At this point, the display is already released, but we still need to release the
+ * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding
+ * WindowPolicyController.
+ *
+ * Note that when the display is destroyed during VirtualDeviceImpl.close() call,
+ * this callback won't be invoked because the display is removed from
+ * VirtualDeviceManagerService before any resources are released.
+ */
void onVirtualDisplayRemoved(int displayId) {
- /* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
- * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
- * At this point, the display is already released, but we still need to release the
- * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding
- * WindowPolicyController.
- *
- * Note that when the display is destroyed during VirtualDeviceImpl.close() call,
- * this callback won't be invoked because the display is removed from
- * VirtualDeviceManagerService before any resources are released.
- */
VirtualDisplayWrapper virtualDisplayWrapper;
synchronized (mVirtualDeviceLock) {
virtualDisplayWrapper = mVirtualDisplays.removeReturnOld(displayId);
@@ -1847,6 +1875,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mWindowPolicyController;
}
+ void acquireWakeLock() {
+ if (mWakeLock != null && !mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ }
+ }
+
void releaseWakeLock() {
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
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 8a0b85859b66..ff82ca00b840 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -40,7 +40,6 @@ import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
-import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtualnative.IVirtualDeviceManagerNative;
import android.compat.annotation.ChangeId;
@@ -49,6 +48,7 @@ import android.content.AttributionSource;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IVirtualDisplayCallback;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -67,6 +67,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.widget.Toast;
+import android.window.DisplayWindowPolicyController;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -751,6 +752,16 @@ public class VirtualDeviceManagerService extends SystemService {
}
@Override
+ public void onVirtualDisplayCreated(IVirtualDevice virtualDevice, int displayId,
+ IVirtualDisplayCallback callback, DisplayWindowPolicyController dwpc) {
+ VirtualDeviceImpl virtualDeviceImpl = getVirtualDeviceForId(
+ ((VirtualDeviceImpl) virtualDevice).getDeviceId());
+ if (virtualDeviceImpl != null) {
+ virtualDeviceImpl.onVirtualDisplayCreated(displayId, callback, dwpc);
+ }
+ }
+
+ @Override
public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
VirtualDeviceImpl virtualDeviceImpl = getVirtualDeviceForId(
((VirtualDeviceImpl) virtualDevice).getDeviceId());
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c8b0a57fe9f0..5ff6999e40b3 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -3705,8 +3705,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
@Override
public void takeUidSnapshotsAsync(int[] requestUids, ResultReceiver resultReceiver) {
if (!onlyCaller(requestUids)) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.BATTERY_STATS, null);
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BATTERY_STATS, null);
+ } catch (SecurityException ex) {
+ resultReceiver.send(IBatteryStats.RESULT_SECURITY_EXCEPTION,
+ Bundle.forPair(IBatteryStats.KEY_EXCEPTION_MESSAGE, ex.getMessage()));
+ return;
+ }
}
if (shouldCollectExternalStats()) {
@@ -3727,13 +3733,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
Bundle resultData = new Bundle(1);
resultData.putParcelableArray(IBatteryStats.KEY_UID_SNAPSHOTS, results);
- resultReceiver.send(0, resultData);
+ resultReceiver.send(IBatteryStats.RESULT_OK, resultData);
} catch (Exception ex) {
if (DBG) {
Slog.d(TAG, "Crashed while returning results for takeUidSnapshots("
+ Arrays.toString(requestUids) + ") i=" + i, ex);
}
- throw ex;
+ resultReceiver.send(IBatteryStats.RESULT_RUNTIME_EXCEPTION,
+ Bundle.forPair(IBatteryStats.KEY_EXCEPTION_MESSAGE, ex.getMessage()));
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e0fbaf43ea43..27e9e44f1090 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -31,7 +31,6 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
-import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.os.PowerWhitelistManager.REASON_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -3905,6 +3904,10 @@ class UserController implements Handler.Callback {
return mService.mWindowManager;
}
+ ActivityTaskManagerInternal getActivityTaskManagerInternal() {
+ return mService.mAtmInternal;
+ }
+
void activityManagerOnUserStopped(@UserIdInt int userId) {
LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId);
}
@@ -4119,25 +4122,40 @@ class UserController implements Handler.Callback {
}
void lockDeviceNowAndWaitForKeyguardShown() {
+ if (getWindowManager().isKeyguardLocked()) {
+ Slogf.w(TAG, "Not locking the device since the keyguard is already locked");
+ return;
+ }
+
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("lockDeviceNowAndWaitForKeyguardShown");
final CountDownLatch latch = new CountDownLatch(1);
- Bundle bundle = new Bundle();
- bundle.putBinder(LOCK_ON_USER_SWITCH_CALLBACK, new IRemoteCallback.Stub() {
- public void sendResult(Bundle data) {
- latch.countDown();
- }
- });
- getWindowManager().lockNow(bundle);
+ ActivityTaskManagerInternal.ScreenObserver screenObserver =
+ new ActivityTaskManagerInternal.ScreenObserver() {
+ @Override
+ public void onAwakeStateChanged(boolean isAwake) {
+
+ }
+
+ @Override
+ public void onKeyguardStateChanged(boolean isShowing) {
+ if (isShowing) {
+ latch.countDown();
+ }
+ }
+ };
+
+ getActivityTaskManagerInternal().registerScreenObserver(screenObserver);
+ getWindowManager().lockDeviceNow();
try {
if (!latch.await(20, TimeUnit.SECONDS)) {
- throw new RuntimeException("User controller expected a callback while waiting "
- + "to show the keyguard. Timed out after 20 seconds.");
+ throw new RuntimeException("Keyguard is not shown in 20 seconds");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
+ getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver);
t.traceEnd();
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 2219ecc77167..6f79f7073b89 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -66,7 +66,6 @@ import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
import static com.android.media.audio.Flags.replaceStreamBtSco;
import static com.android.media.audio.Flags.ringMyCar;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
-import static com.android.media.audio.Flags.vgsVssSyncMuteOrder;
import static com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
@@ -4977,9 +4976,8 @@ public class AudioService extends IAudioService.Stub
+ roForegroundAudioControl());
pw.println("\tandroid.media.audio.scoManagedByAudio:"
+ scoManagedByAudio());
- pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder:"
- + vgsVssSyncMuteOrder());
pw.println("\tcom.android.media.audio.absVolumeIndexFix - EOL");
+ pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder - EOL");
pw.println("\tcom.android.media.audio.replaceStreamBtSco:"
+ replaceStreamBtSco());
pw.println("\tcom.android.media.audio.equalScoLeaVcIndexRange:"
@@ -9010,22 +9008,13 @@ public class AudioService extends IAudioService.Stub
synced = true;
continue;
}
- if (vgsVssSyncMuteOrder()) {
- if ((isMuted() != streamMuted) && isVssMuteBijective(
- stream)) {
- vss.mute(isMuted(), "VGS.applyAllVolumes#1");
- }
+ if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
+ vss.mute(isMuted(), "VGS.applyAllVolumes#1");
}
if (indexForStream != index) {
vss.setIndex(index * 10, device,
caller, true /*hasModifyAudioSettings*/);
}
- if (!vgsVssSyncMuteOrder()) {
- if ((isMuted() != streamMuted) && isVssMuteBijective(
- stream)) {
- vss.mute(isMuted(), "VGS.applyAllVolumes#1");
- }
- }
}
}
}
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 471b7b4ddfc8..d412277d2605 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -24,8 +24,10 @@ import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.sensor.VirtualSensor;
import android.content.Context;
+import android.hardware.display.IVirtualDisplayCallback;
import android.os.LocaleList;
import android.util.ArraySet;
+import android.window.DisplayWindowPolicyController;
import java.util.Set;
import java.util.function.Consumer;
@@ -104,6 +106,17 @@ public abstract class VirtualDeviceManagerInternal {
public abstract @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid);
/**
+ * Notifies that a virtual display was created.
+ *
+ * @param virtualDevice The virtual device that owns the virtual display.
+ * @param displayId The display id of the created virtual display.
+ * @param callback The callback of the virtual display.
+ * @param dwpc The DisplayWindowPolicyController of the created virtual display.
+ */
+ public abstract void onVirtualDisplayCreated(IVirtualDevice virtualDevice, int displayId,
+ IVirtualDisplayCallback callback, DisplayWindowPolicyController dwpc);
+
+ /**
* Notifies that a virtual display is removed.
*
* @param virtualDevice The virtual device where the virtual display located.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e83efc573ea8..854b0dd7676b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2041,6 +2041,7 @@ public final class DisplayManagerService extends SystemService {
packageName,
displayUniqueId,
virtualDevice,
+ dwpc,
surface,
flags,
virtualDisplayConfig);
@@ -2135,6 +2136,7 @@ public final class DisplayManagerService extends SystemService {
String packageName,
String uniqueId,
IVirtualDevice virtualDevice,
+ DisplayWindowPolicyController dwpc,
Surface surface,
int flags,
VirtualDisplayConfig virtualDisplayConfig) {
@@ -2188,6 +2190,16 @@ public final class DisplayManagerService extends SystemService {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (display != null) {
+ // Notify the virtual device that the display has been created. This needs to be called
+ // in this locked section before the repository had the chance to notify any listeners
+ // to ensure that the device is aware of the new display before others know about it.
+ if (virtualDevice != null) {
+ final VirtualDeviceManagerInternal vdm =
+ getLocalService(VirtualDeviceManagerInternal.class);
+ vdm.onVirtualDisplayCreated(
+ virtualDevice, display.getDisplayIdLocked(), callback, dwpc);
+ }
+
return display.getDisplayIdLocked();
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index b49c01b3e2a8..83ca563e0534 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -781,6 +781,11 @@ final class LocalDisplayAdapter extends DisplayAdapter {
if (isDisplayPrivate(physicalAddress)) {
mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
}
+
+ if (isDisplayStealTopFocusDisabled(physicalAddress)) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS;
+ mInfo.flags |= DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED;
+ }
}
if (DisplayCutout.getMaskBuiltInDisplayCutout(res, mInfo.uniqueId)) {
@@ -1467,6 +1472,23 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
return false;
}
+
+ private boolean isDisplayStealTopFocusDisabled(DisplayAddress.Physical physicalAddress) {
+ if (physicalAddress == null) {
+ return false;
+ }
+ final Resources res = getOverlayContext().getResources();
+ int[] ports = res.getIntArray(R.array.config_localNotStealTopFocusDisplayPorts);
+ if (ports != null) {
+ int port = physicalAddress.getPort();
+ for (int p : ports) {
+ if (p == port) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
private boolean hdrTypesEqual(int[] modeHdrTypes, int[] recordHdrTypes) {
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index b529853c63a4..058bbc08a9ef 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -267,6 +267,50 @@ import java.util.stream.Stream;
notifyRequestFailed(requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
}
+ @Override
+ public void selectRoute(long requestId, String sessionId, String routeId) {
+ if (SYSTEM_SESSION_ID.equals(sessionId)) {
+ super.selectRoute(requestId, sessionId, routeId);
+ return;
+ }
+ synchronized (mLock) {
+ var sessionRecord = getSessionRecordByOriginalId(sessionId);
+ var proxyRecord = sessionRecord != null ? sessionRecord.getProxyRecord() : null;
+ if (proxyRecord != null) {
+ var targetSourceRouteId =
+ proxyRecord.mNewOriginalIdToSourceOriginalIdMap.get(routeId);
+ if (targetSourceRouteId != null) {
+ proxyRecord.mProxy.selectRoute(
+ requestId, sessionRecord.getServiceSessionId(), targetSourceRouteId);
+ }
+ return;
+ }
+ }
+ notifyRequestFailed(requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
+ }
+
+ @Override
+ public void deselectRoute(long requestId, String sessionId, String routeId) {
+ if (SYSTEM_SESSION_ID.equals(sessionId)) {
+ super.selectRoute(requestId, sessionId, routeId);
+ return;
+ }
+ synchronized (mLock) {
+ var sessionRecord = getSessionRecordByOriginalId(sessionId);
+ var proxyRecord = sessionRecord != null ? sessionRecord.getProxyRecord() : null;
+ if (proxyRecord != null) {
+ var targetSourceRouteId =
+ proxyRecord.mNewOriginalIdToSourceOriginalIdMap.get(routeId);
+ if (targetSourceRouteId != null) {
+ proxyRecord.mProxy.deselectRoute(
+ requestId, sessionRecord.getServiceSessionId(), targetSourceRouteId);
+ }
+ return;
+ }
+ }
+ notifyRequestFailed(requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
+ }
+
@GuardedBy("mLock")
private SystemMediaSessionRecord getSessionRecordByOriginalId(String sessionOriginalId) {
if (FORCE_GLOBAL_ROUTING_SESSION) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index cc4c2b5bf893..068d68d25017 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -40,6 +40,7 @@ import static com.android.server.pm.AppsFilterUtils.canQueryViaUsesLibrary;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ApplicationPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningDetails;
@@ -173,6 +174,10 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
* Report a change to observers.
*/
private void onChanged() {
+ // App visibility may have changed, which means that earlier fetches from these caches may
+ // be invalid.
+ PackageManager.invalidatePackageInfoCache();
+ ApplicationPackageManager.invalidateGetPackagesForUidCache();
dispatchChange(this);
}
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 576e5d5d0cd2..439b503c0c57 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -506,6 +506,10 @@ class AppWarnings {
context = new ContextThemeWrapper(context, context.getThemeResId()) {
@Override
public void startActivity(Intent intent) {
+ // PageSizeMismatch dialog stays on top of the browser even after opening link
+ // set broadcast to close the dialog when link has been clicked.
+ sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
super.startActivity(intent);
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
index 66aaa562b873..a01df8bf108d 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
@@ -37,6 +37,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.UserInfo;
+import android.app.PropertyInvalidatedCache;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
@@ -50,6 +51,8 @@ import android.util.SparseArray;
import androidx.annotation.NonNull;
+import android.app.ApplicationPackageManager;
+import android.content.pm.PackageManager;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ParsedActivity;
@@ -64,8 +67,10 @@ import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.server.om.OverlayReferenceMapper;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableTester;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -244,6 +249,55 @@ public class AppsFilterImplTest {
(Answer<Boolean>) invocation ->
((AndroidPackage) invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
>= Build.VERSION_CODES.R);
+ PropertyInvalidatedCache.setTestMode(true);
+ PackageManager.sApplicationInfoCache.testPropertyName();
+ ApplicationPackageManager.sGetPackagesForUidCache.testPropertyName();
+ }
+
+ @After
+ public void tearDown() {
+ PropertyInvalidatedCache.setTestMode(false);
+ }
+
+ /**
+ * A class to make it easier to verify that PM caches are properly invalidated by
+ * AppsFilterImpl operations. This extends WatchableTester to test the cache nonces along
+ * with change reporting.
+ */
+ private static class NonceTester extends WatchableTester {
+ // The nonces from caches under consideration. The no-parameter constructor fetches the
+ // values from the cacches.
+ private static record Nonces(long applicationInfo, long packageInfo) {
+ Nonces() {
+ this(ApplicationPackageManager.sGetPackagesForUidCache.getNonce(),
+ PackageManager.sApplicationInfoCache.getNonce());
+ }
+ }
+
+ // Track the latest cache nonces.
+ private Nonces mNonces;
+
+ NonceTester(Watchable w, String k) {
+ super(w, k);
+ mNonces = new Nonces();
+ }
+
+ @Override
+ public void verifyChangeReported(String msg) {
+ super.verifyChangeReported(msg);
+ Nonces update = new Nonces();
+ assertTrue(msg, update.applicationInfo != mNonces.applicationInfo);
+ assertTrue(msg, update.packageInfo != mNonces.packageInfo);
+ mNonces = update;
+ }
+
+ @Override
+ public void verifyNoChangeReported(String msg) {
+ super.verifyNoChangeReported(msg);
+ Nonces update = new Nonces();
+ assertTrue(msg, update.applicationInfo == mNonces.applicationInfo);
+ assertTrue(msg, update.packageInfo == mNonces.packageInfo);
+ }
}
@Test
@@ -1167,7 +1221,7 @@ public class AppsFilterImplTest {
final AppsFilterImpl appsFilter =
new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
false, /* overlayProvider */ null, mMockHandler);
- final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+ final WatchableTester watcher = new NonceTester(appsFilter, "onChange");
watcher.register();
simulateAddBasicAndroid(appsFilter);
watcher.verifyChangeReported("addBasicAndroid");
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 5393e20889c0..b9cea0c72306 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -388,6 +388,34 @@ public class LocalDisplayAdapterTest {
PORT_C, false);
}
+ /**
+ * Confirm that display is marked as trusted, has own focus, disables steal top focus when it
+ * is listed in com.android.internal.R.array.config_localNotStealTopFocusDisplayPorts.
+ */
+ @Test
+ public void testStealTopFocusDisabledDisplay() throws Exception {
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_B));
+ setUpDisplay(new FakeDisplay(PORT_C));
+ updateAvailableDisplays();
+
+ doReturn(new int[]{ PORT_B }).when(mMockedResources).getIntArray(
+ com.android.internal.R.array.config_localNotStealTopFocusDisplayPorts);
+ mAdapter.registerLocked();
+
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // This should not have the flags
+ assertNotStealTopFocusFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+ PORT_A, false);
+ // This should have the flags
+ assertNotStealTopFocusFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+ PORT_B, true);
+ // This should not have the flags
+ assertNotStealTopFocusFlag(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(),
+ PORT_C, false);
+ }
+
@Test
public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()
throws InterruptedException {
@@ -452,6 +480,42 @@ public class LocalDisplayAdapterTest {
}
/**
+ * Confirm that all local displays are not trusted, do not have their own focus, and do not
+ * steal top focus when config_localNotStealTopFocusDisplayPorts is empty:
+ */
+ @Test
+ public void testDisplayFlagsForNoConfigLocalNotStealTopFocusDisplayPorts() throws Exception {
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_C));
+ updateAvailableDisplays();
+
+ // config_localNotStealTopFocusDisplayPorts is null
+ mAdapter.registerLocked();
+
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // This should not have the flags
+ assertNotStealTopFocusFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+ PORT_A, false);
+ // This should not have the flags
+ assertNotStealTopFocusFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+ PORT_C, false);
+ }
+
+ private static void assertNotStealTopFocusFlag(
+ DisplayDeviceInfo info, int expectedPort, boolean shouldHaveFlags) {
+ final DisplayAddress.Physical address = (DisplayAddress.Physical) info.address;
+ assertNotNull(address);
+ assertEquals(expectedPort, address.getPort());
+ assertEquals(DISPLAY_MODEL, address.getModel());
+ assertEquals(shouldHaveFlags,
+ (info.flags & DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED) != 0);
+ assertEquals(shouldHaveFlags, (info.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
+ // display is always trusted since it is created by the system
+ assertEquals(true, (info.flags & DisplayDeviceInfo.FLAG_TRUSTED) != 0);
+ }
+
+ /**
* Confirm that external display uses physical density.
*/
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index cd365396c74b..bc04fd94c719 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -148,6 +148,7 @@ public class WallpaperManagerServiceTests {
private static ComponentName sImageWallpaperComponentName;
private static ComponentName sDefaultWallpaperComponent;
+ private static WallpaperDescription sDefaultWallpaperDescription;
private static ComponentName sFallbackWallpaperComponentName;
@@ -214,6 +215,8 @@ public class WallpaperManagerServiceTests {
} else {
sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService);
}
+ sDefaultWallpaperDescription = new WallpaperDescription.Builder().setComponent(
+ sDefaultWallpaperComponent).build();
sContext.addMockService(sImageWallpaperComponentName, sWallpaperService);
sContext.addMockService(TEST_WALLPAPER_COMPONENT, sWallpaperService);
@@ -489,11 +492,12 @@ public class WallpaperManagerServiceTests {
}
@Test
- @EnableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT)
+ @EnableFlags({Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT,
+ Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING})
public void testSaveLoadSettings_withoutWallpaperDescription()
throws IOException, XmlPullParserException {
WallpaperData expectedData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0);
- expectedData.setComponent(sDefaultWallpaperComponent);
+ expectedData.setDescription(sDefaultWallpaperDescription);
expectedData.primaryColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
expectedData.mWallpaperDimAmount = 0.5f;
@@ -529,11 +533,12 @@ public class WallpaperManagerServiceTests {
}
@Test
- @EnableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT)
+ @EnableFlags({Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT,
+ Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING})
public void testSaveLoadSettings_withWallpaperDescription()
throws IOException, XmlPullParserException {
WallpaperData expectedData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0);
- expectedData.setComponent(sDefaultWallpaperComponent);
+ expectedData.setDescription(sDefaultWallpaperDescription);
PersistableBundle content = new PersistableBundle();
content.putString("ckey", "cvalue");
WallpaperDescription description = new WallpaperDescription.Builder()
@@ -561,7 +566,8 @@ public class WallpaperManagerServiceTests {
}
@Test
- @DisableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT)
+ @DisableFlags({Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT,
+ Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING})
public void testSaveLoadSettings_legacyNextComponent()
throws IOException, XmlPullParserException {
WallpaperData systemWallpaperData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0);
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 1627f683cd3e..06958b81d846 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -25,7 +25,6 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
-import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
@@ -116,6 +115,7 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.UserTypeDetails;
import com.android.server.pm.UserTypeFactory;
+import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerService;
import com.google.common.collect.Range;
@@ -1563,11 +1563,11 @@ public class UserControllerTest {
// and the thread is still alive
assertTrue(threadStartUser.isAlive());
- // mock the binder response for the user switch completion
- ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
- verify(mInjector.mWindowManagerMock).lockNow(captor.capture());
- IRemoteCallback.Stub.asInterface(captor.getValue().getBinder(
- LOCK_ON_USER_SWITCH_CALLBACK)).sendResult(null);
+ // mock send the keyguard shown event
+ ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass(
+ ActivityTaskManagerInternal.ScreenObserver.class);
+ verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture());
+ captor.getValue().onKeyguardStateChanged(true);
// verify the switch now moves on...
Thread.sleep(1000);
@@ -1757,6 +1757,7 @@ public class UserControllerTest {
private final IStorageManager mStorageManagerMock;
private final UserManagerInternal mUserManagerInternalMock;
private final WindowManagerService mWindowManagerMock;
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final PowerManagerInternal mPowerManagerInternal;
private final AlarmManagerInternal mAlarmManagerInternal;
private final KeyguardManager mKeyguardManagerMock;
@@ -1778,6 +1779,7 @@ public class UserControllerTest {
mUserManagerMock = mock(UserManagerService.class);
mUserManagerInternalMock = mock(UserManagerInternal.class);
mWindowManagerMock = mock(WindowManagerService.class);
+ mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
mStorageManagerMock = mock(IStorageManager.class);
mPowerManagerInternal = mock(PowerManagerInternal.class);
mAlarmManagerInternal = mock(AlarmManagerInternal.class);
@@ -1841,6 +1843,11 @@ public class UserControllerTest {
}
@Override
+ ActivityTaskManagerInternal getActivityTaskManagerInternal() {
+ return mActivityTaskManagerInternal;
+ }
+
+ @Override
PowerManagerInternal getPowerManagerInternal() {
return mPowerManagerInternal;
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index ffcb96120b19..ab7b4da269db 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -73,6 +73,7 @@ import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.hardware.Sensor;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.IDisplayManager;
@@ -173,8 +174,7 @@ public class VirtualDeviceManagerServiceTest {
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
private static final int VIRTUAL_DEVICE_ID_1 = 42;
private static final int VIRTUAL_DEVICE_ID_2 = 43;
- private static final VirtualDisplayConfig VIRTUAL_DISPLAY_CONFIG =
- new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400).build();
+
private static final VirtualDpadConfig DPAD_CONFIG =
new VirtualDpadConfig.Builder()
.setVendorId(VENDOR_ID)
@@ -284,7 +284,12 @@ public class VirtualDeviceManagerServiceTest {
private Intent createRestrictedActivityBlockedIntent(Set<String> displayCategories,
String targetDisplayCategory) {
when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(),
- eq(VIRTUAL_DEVICE_OWNER_PACKAGE))).thenReturn(DISPLAY_ID_1);
+ eq(VIRTUAL_DEVICE_OWNER_PACKAGE)))
+ .thenAnswer(inv -> {
+ mLocalService.onVirtualDisplayCreated(
+ mDeviceImpl, DISPLAY_ID_1, inv.getArgument(1), inv.getArgument(3));
+ return DISPLAY_ID_1;
+ });
VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480,
420).setDisplayCategories(displayCategories).build();
mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback);
@@ -997,8 +1002,7 @@ public class VirtualDeviceManagerServiceTest {
public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
throws RemoteException {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
- assertThrows(IllegalStateException.class,
- () -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1));
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
@@ -1871,8 +1875,6 @@ public class VirtualDeviceManagerServiceTest {
}
private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId, int flags) {
- when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
- eq(virtualDevice), any(), any())).thenReturn(displayId);
final String uniqueId = UNIQUE_ID + displayId;
doAnswer(inv -> {
final DisplayInfo displayInfo = new DisplayInfo();
@@ -1880,7 +1882,22 @@ public class VirtualDeviceManagerServiceTest {
displayInfo.flags = flags;
return displayInfo;
}).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
- virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback);
+
+ when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
+ eq(virtualDevice), any(), any())).thenAnswer(inv -> {
+ mLocalService.onVirtualDisplayCreated(
+ virtualDevice, displayId, mVirtualDisplayCallback, inv.getArgument(3));
+ return displayId;
+ });
+
+ final int virtualDisplayFlags = (flags & Display.FLAG_TRUSTED) == 0
+ ? 0
+ : DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+ VirtualDisplayConfig virtualDisplayConfig =
+ new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400)
+ .setFlags(virtualDisplayFlags)
+ .build();
+ virtualDevice.createVirtualDisplay(virtualDisplayConfig, mVirtualDisplayCallback);
mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
}