summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt7
-rw-r--r--core/java/android/app/supervision/SupervisionAppService.java38
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/tests/FileSystemUtilsTest/OWNERS2
-rw-r--r--core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java14
-rw-r--r--libs/hwui/renderthread/ReliableSurface.cpp24
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt43
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt66
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java109
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java34
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java56
-rw-r--r--services/credentials/java/com/android/server/credentials/MetricUtilities.java8
-rw-r--r--services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java8
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java16
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java16
-rw-r--r--services/tests/displayservicetests/Android.bp1
-rw-r--r--services/tests/displayservicetests/AndroidManifest.xml1
-rw-r--r--services/tests/displayservicetests/AndroidTest.xml1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java244
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/EventDeliveryTestBase.java259
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/TopologyUpdateDeliveryTest.java243
-rw-r--r--services/tests/servicestests/Android.bp2
-rw-r--r--services/tests/servicestests/AndroidTest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java18
-rw-r--r--services/tests/servicestests/test-apps/TopologyTestApp/Android.bp38
-rw-r--r--services/tests/servicestests/test-apps/TopologyTestApp/AndroidManifest.xml27
-rw-r--r--services/tests/servicestests/test-apps/TopologyTestApp/OWNERS3
-rw-r--r--services/tests/servicestests/test-apps/TopologyTestApp/src/com/android/servicestests/apps/topologytestapp/TopologyUpdateActivity.java87
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java83
-rw-r--r--tools/aapt2/cmd/Convert.h14
-rw-r--r--tools/aapt2/cmd/Link.h9
-rw-r--r--tools/aapt2/cmd/Optimize.h14
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp17
-rw-r--r--tools/aapt2/format/binary/TableFlattener_test.cpp122
-rw-r--r--tools/aapt2/readme.md4
47 files changed, 1457 insertions, 471 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2ce3609d77e7..746035462329 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2940,6 +2940,13 @@ package android.app.smartspace.uitemplatedata {
package android.app.supervision {
+ @FlaggedApi("android.app.supervision.flags.enable_supervision_app_service") public class SupervisionAppService extends android.app.Service {
+ ctor public SupervisionAppService();
+ method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @FlaggedApi("android.app.supervision.flags.enable_supervision_app_service") public void onDisabled();
+ method @FlaggedApi("android.app.supervision.flags.enable_supervision_app_service") public void onEnabled();
+ }
+
@FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager {
method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public android.content.Intent createConfirmSupervisionCredentialsIntent();
method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSupervisionEnabled();
diff --git a/core/java/android/app/supervision/SupervisionAppService.java b/core/java/android/app/supervision/SupervisionAppService.java
index 4530be5c270a..93eb96204444 100644
--- a/core/java/android/app/supervision/SupervisionAppService.java
+++ b/core/java/android/app/supervision/SupervisionAppService.java
@@ -16,7 +16,11 @@
package android.app.supervision;
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.Service;
+import android.app.supervision.flags.Flags;
import android.content.Intent;
import android.os.IBinder;
@@ -26,31 +30,43 @@ import android.os.IBinder;
*
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE)
public class SupervisionAppService extends Service {
- private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() {
- @Override
- public void onEnabled() {
- SupervisionAppService.this.onEnabled();
- }
+ private final ISupervisionAppService mBinder =
+ new ISupervisionAppService.Stub() {
+ @Override
+ public void onEnabled() {
+ SupervisionAppService.this.onEnabled();
+ }
- @Override
- public void onDisabled() {
- SupervisionAppService.this.onDisabled();
- }
- };
+ @Override
+ public void onDisabled() {
+ SupervisionAppService.this.onDisabled();
+ }
+ };
+ @Nullable
@Override
- public final IBinder onBind(Intent intent) {
+ public final IBinder onBind(@Nullable Intent intent) {
return mBinder.asBinder();
}
/**
* Called when supervision is enabled.
+ *
+ * @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE)
public void onEnabled() {}
/**
* Called when supervision is disabled.
+ *
+ * @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE)
public void onDisabled() {}
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e47adc90fc7a..1a74fe6719e3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1219,6 +1219,8 @@
6 - Lock if keyguard enabled or go to sleep (doze)
7 - Dream if possible or go to sleep (doze)
8 - Go to glanceable hub or dream if possible, or sleep if neither is available (doze)
+ 9 - Go to dream if device is not dreaming, stop dream if device is dreaming, or sleep if
+ neither is available (doze)
-->
<integer name="config_shortPressOnPowerBehavior">1</integer>
diff --git a/core/tests/FileSystemUtilsTest/OWNERS b/core/tests/FileSystemUtilsTest/OWNERS
new file mode 100644
index 000000000000..74eeacfeb973
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/OWNERS
@@ -0,0 +1,2 @@
+waghpawan@google.com
+kaleshsingh@google.com
diff --git a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
index 208d74e49afe..dbfd3e8ccdaa 100644
--- a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
+++ b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
@@ -38,6 +38,8 @@ public class FileSystemUtilsTest extends BaseHostJUnit4Test {
private static final String PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM =
"app_with_4kb_elf_no_override.apk";
+ private static final int DEVICE_WAIT_TIMEOUT = 120000;
+
@Test
@AppModeFull
public void runPunchedApp_embeddedNativeLibs() throws DeviceNotAvailableException {
@@ -98,8 +100,20 @@ public class FileSystemUtilsTest extends BaseHostJUnit4Test {
@AppModeFull
public void runAppWith4KbLib_compatByAlignmentChecks()
throws DeviceNotAvailableException, TargetSetupError {
+ // make sure that device is available for UI test
+ prepareDevice();
// This test is expected to fail since compat is disabled in manifest
runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM,
"testPageSizeCompat_compatByAlignmentChecks");
}
+
+ private void prepareDevice() throws DeviceNotAvailableException {
+ // Verify that device is online before running test and enable root
+ getDevice().waitForDeviceAvailable(DEVICE_WAIT_TIMEOUT);
+ getDevice().enableAdbRoot();
+ getDevice().waitForDeviceAvailable(DEVICE_WAIT_TIMEOUT);
+
+ getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ getDevice().executeShellCommand("wm dismiss-keyguard");
+ }
}
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index 01e8010444c0..64d38b9ef466 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -149,25 +149,9 @@ ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer(int error) {
return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get());
}
- int width = -1;
- int result = mWindow->query(mWindow, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
- if (result != OK || width < 0) {
- ALOGW("Failed to query window default width: %s (%d) value=%d", strerror(-result), result,
- width);
- width = 1;
- }
-
- int height = -1;
- result = mWindow->query(mWindow, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
- if (result != OK || height < 0) {
- ALOGW("Failed to query window default height: %s (%d) value=%d", strerror(-result), result,
- height);
- height = 1;
- }
-
AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{
- .width = static_cast<uint32_t>(width),
- .height = static_cast<uint32_t>(height),
+ .width = 1,
+ .height = 1,
.layers = 1,
.format = mFormat,
.usage = mUsage,
@@ -176,9 +160,9 @@ ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer(int error) {
};
AHardwareBuffer* newBuffer;
- result = AHardwareBuffer_allocate(&desc, &newBuffer);
+ int result = AHardwareBuffer_allocate(&desc, &newBuffer);
- if (result != OK) {
+ if (result != NO_ERROR) {
// Allocate failed, that sucks
ALOGW("Failed to allocate scratch buffer, error=%d", result);
return nullptr;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index a67aea466c1c..0cd9c53c830f 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -238,6 +238,7 @@ void VulkanManager::setupDevice(skgpu::VulkanExtensions& grExtensions,
for (uint32_t i = 0; i < queueCount; i++) {
queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
queuePriorityProps[i].pNext = nullptr;
+ queueProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
queueProps[i].pNext = &queuePriorityProps[i];
}
mGetPhysicalDeviceQueueFamilyProperties2(mPhysicalDevice, &queueCount, queueProps.get());
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a4852d26ffe2..cea91a18ae32 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1856,10 +1856,11 @@ flag {
}
flag {
- name: "shade_header_fonts"
+ name: "shade_header_font_update"
namespace: "systemui"
description: "Updates the fonts of the shade header"
- bug: "393609724"
+ bug: "393609960"
+ is_fixed_read_only: true
}
flag {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 3ecf302204bc..67af7a54988e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
@@ -45,6 +46,8 @@ import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -65,8 +68,6 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-import java.util.Optional
-import java.util.function.Consumer
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -75,6 +76,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val applicationScope = kosmos.testScope.backgroundScope
+ private val shadeDisplayRepository = kosmos.fakeShadeDisplaysRepository
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var blurUtils: BlurUtils
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@@ -135,7 +137,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
windowRootViewBlurInteractor,
appZoomOutOptional,
applicationScope,
- dumpManager
+ dumpManager,
+ { shadeDisplayRepository },
)
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
@@ -355,6 +358,36 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun updateBlurCallback_shadeInExternalDisplay_doesSetZeroZoom() {
+ notificationShadeDepthController.onPanelExpansionChanged(
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
+ notificationShadeDepthController.addListener(listener)
+ shadeDisplayRepository.setDisplayId(1) // not default display.
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+ verify(wallpaperController).setNotificationShadeZoom(eq(0f))
+ verify(listener).onWallpaperZoomOutChanged(eq(0f))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun updateBlurCallback_shadeInDefaultDisplay_doesNotSetZeroZoom() {
+ notificationShadeDepthController.onPanelExpansionChanged(
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
+ notificationShadeDepthController.addListener(listener)
+ shadeDisplayRepository.setDisplayId(0) // shade is in default display
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+ verify(wallpaperController).setNotificationShadeZoom(floatThat { it != 0f })
+ verify(listener).onWallpaperZoomOutChanged(floatThat { it != 0f })
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_NOTIFICATION_SHADE_BLUR)
fun updateBlurCallback_setsOpaque_whenScrim() {
scrimVisibilityCaptor.value.accept(ScrimController.OPAQUE)
@@ -488,10 +521,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
private fun enableSplitShade() {
- `when` (shadeModeInteractor.isSplitShade).thenReturn(true)
+ `when`(shadeModeInteractor.isSplitShade).thenReturn(true)
}
private fun disableSplitShade() {
- `when` (shadeModeInteractor.isSplitShade).thenReturn(false)
+ `when`(shadeModeInteractor.isSplitShade).thenReturn(false)
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index ea7321627322..b8cd5bec2cbe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -208,4 +208,19 @@ public class PreviewPositionHelper {
}
mMatrix.postTranslate(translateX, translateY);
}
+
+ /**
+ * A factory that returns a new instance of the {@link PreviewPositionHelper}.
+ * <p>{@link PreviewPositionHelper} is a stateful helper, and hence when using it in distinct
+ * scenarios, prefer fetching an object using this factory</p>
+ * <p>Additionally, helpful for injecting mocks in tests</p>
+ */
+ public static class PreviewPositionHelperFactory {
+ /**
+ * Returns a new {@link PreviewPositionHelper} for use in a distinct scenario.
+ */
+ public PreviewPositionHelper create() {
+ return new PreviewPositionHelper();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6831c412d1e4..099a7f067482 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -140,6 +140,7 @@ import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -364,6 +365,7 @@ public class KeyguardViewMediator implements CoreStartable,
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
private final Lazy<ShadeController> mShadeController;
private final Lazy<CommunalSceneInteractor> mCommunalSceneInteractor;
+ private final Lazy<CommunalSettingsInteractor> mCommunalSettingsInteractor;
/*
* Records the user id on request to go away, for validation when WM calls back to start the
* exit animation.
@@ -1567,6 +1569,7 @@ public class KeyguardViewMediator implements CoreStartable,
KeyguardInteractor keyguardInteractor,
KeyguardTransitionBootInteractor transitionBootInteractor,
Lazy<CommunalSceneInteractor> communalSceneInteractor,
+ Lazy<CommunalSettingsInteractor> communalSettingsInteractor,
WindowManagerOcclusionManager wmOcclusionManager) {
mContext = context;
mUserTracker = userTracker;
@@ -1609,6 +1612,7 @@ public class KeyguardViewMediator implements CoreStartable,
mKeyguardInteractor = keyguardInteractor;
mTransitionBootInteractor = transitionBootInteractor;
mCommunalSceneInteractor = communalSceneInteractor;
+ mCommunalSettingsInteractor = communalSettingsInteractor;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -2429,9 +2433,18 @@ public class KeyguardViewMediator implements CoreStartable,
private void doKeyguardLocked(Bundle options) {
// If the power button behavior requests to open the glanceable hub.
if (options != null && options.getBoolean(EXTRA_TRIGGER_HUB)) {
- // Set the hub to show immediately when the SysUI window shows, then continue to lock
- // the device.
- mCommunalSceneInteractor.get().showHubFromPowerButton();
+ if (mCommunalSettingsInteractor.get().getAutoOpenEnabled().getValue()) {
+ // Set the hub to show immediately when the SysUI window shows, then continue to
+ // lock the device.
+ mCommunalSceneInteractor.get().showHubFromPowerButton();
+ } else {
+ // If the hub is not available, go to sleep instead of locking. This can happen
+ // because the power button behavior does not check all possible reasons the hub
+ // might be disabled.
+ mPM.goToSleep(android.os.SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ return;
+ }
}
int currentUserId = mSelectedUserInteractor.getSelectedUserId();
@@ -3765,13 +3778,7 @@ public class KeyguardViewMediator implements CoreStartable,
Log.d(TAG, "Status bar manager is disabled for visible background users");
}
} else {
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId());
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to force clear flags", e);
- }
+ statusBarServiceDisableForUser(flags, "Failed to force clear flags");
}
}
@@ -3807,18 +3814,29 @@ public class KeyguardViewMediator implements CoreStartable,
// Handled in StatusBarDisableFlagsInteractor.
if (!KeyguardWmStateRefactor.isEnabled()) {
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId());
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to set disable flags: " + flags, e);
- }
+ statusBarServiceDisableForUser(flags, "Failed to set disable flags: ");
}
}
}
}
+ private void statusBarServiceDisableForUser(int flags, String loggingContext) {
+ Runnable runnable = () -> {
+ try {
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId());
+ } catch (RemoteException e) {
+ Log.d(TAG, loggingContext + " " + flags, e);
+ }
+ };
+ if (com.android.systemui.Flags.bouncerUiRevamp()) {
+ mUiBgExecutor.execute(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
/**
* Handle message sent by {@link #resetStateLocked}
* @see #RESET
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
index ddccc5d9e96d..41d14b9e727f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
@@ -20,7 +20,16 @@ import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for reading or using the keyguard wm state refactor flag state. */
+/**
+ * Helper for reading or using the keyguard_wm_state_refactor flag state.
+ *
+ * keyguard_wm_state_refactor works both with and without flexiglass (scene_container), but
+ * flexiglass requires keyguard_wm_state_refactor. For this reason, this class will return isEnabled
+ * if either keyguard_wm_state_refactor OR scene_container are enabled. This enables us to roll out
+ * keyguard_wm_state_refactor independently of scene_container, while also ensuring that
+ * scene_container rolling out ahead of keyguard_wm_state_refactor causes code gated by
+ * KeyguardWmStateRefactor to be enabled as well.
+ */
@Suppress("NOTHING_TO_INLINE")
object KeyguardWmStateRefactor {
/** The aconfig flag name */
@@ -30,10 +39,9 @@ object KeyguardWmStateRefactor {
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
- /** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.keyguardWmStateRefactor()
+ get() = Flags.keyguardWmStateRefactor() || Flags.sceneContainer()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 6b1248b6983e..1fe6eb9ce7c8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -182,6 +183,7 @@ public interface KeyguardModule {
KeyguardInteractor keyguardInteractor,
KeyguardTransitionBootInteractor transitionBootInteractor,
Lazy<CommunalSceneInteractor> communalSceneInteractor,
+ Lazy<CommunalSettingsInteractor> communalSettingsInteractor,
WindowManagerOcclusionManager windowManagerOcclusionManager) {
return new KeyguardViewMediator(
context,
@@ -234,6 +236,7 @@ public interface KeyguardModule {
keyguardInteractor,
transitionBootInteractor,
communalSceneInteractor,
+ communalSettingsInteractor,
windowManagerOcclusionManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index b889c3e61837..9f04f69bd0bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -19,6 +19,8 @@ package com.android.systemui.qs.composefragment
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
+import android.graphics.Canvas
+import android.graphics.Path
import android.graphics.PointF
import android.graphics.Rect
import android.os.Bundle
@@ -125,7 +127,6 @@ import com.android.systemui.qs.composefragment.SceneKeys.debugName
import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams
-import com.android.systemui.qs.composefragment.ui.notificationScrimClip
import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings
import com.android.systemui.qs.composefragment.ui.toEditMode
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
@@ -241,7 +242,7 @@ constructor(
FrameLayoutTouchPassthrough(
context,
{ notificationScrimClippingParams.isEnabled },
- { notificationScrimClippingParams.params.top },
+ snapshotFlow { notificationScrimClippingParams.params },
// Only allow scrolling when we are fully expanded. That way, we don't intercept
// swipes in lockscreen (when somehow QS is receiving touches).
{ (scrollState.canScrollForward && viewModel.isQsFullyExpanded) || isCustomizing },
@@ -276,11 +277,6 @@ constructor(
}
}
.graphicsLayer { alpha = viewModel.viewAlpha }
- .thenIf(notificationScrimClippingParams.isEnabled) {
- Modifier.notificationScrimClip {
- notificationScrimClippingParams.params
- }
- }
.thenIf(!Flags.notificationShadeBlur()) {
Modifier.offset {
IntOffset(
@@ -1061,17 +1057,75 @@ private const val EDIT_MODE_TIME_MILLIS = 500
private class FrameLayoutTouchPassthrough(
context: Context,
private val clippingEnabledProvider: () -> Boolean,
- private val clippingTopProvider: () -> Int,
+ private val clippingParams: Flow<NotificationScrimClipParams>,
private val canScrollForwardQs: () -> Boolean,
private val emitMotionEventForFalsing: () -> Unit,
) : FrameLayout(context) {
+
+ init {
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ clippingParams.collect { currentClipParams = it }
+ }
+ }
+ }
+
+ private val currentClippingPath = Path()
+ private var lastWidth = -1
+ set(value) {
+ if (field != value) {
+ field = value
+ updateClippingPath()
+ }
+ }
+
+ private var currentClipParams = NotificationScrimClipParams()
+ set(value) {
+ if (field != value) {
+ field = value
+ updateClippingPath()
+ }
+ }
+
+ private fun updateClippingPath() {
+ currentClippingPath.rewind()
+ if (clippingEnabledProvider()) {
+ val right = width + currentClipParams.rightInset
+ val left = -currentClipParams.leftInset
+ val top = currentClipParams.top
+ val bottom = currentClipParams.bottom
+ currentClippingPath.addRoundRect(
+ left.toFloat(),
+ top.toFloat(),
+ right.toFloat(),
+ bottom.toFloat(),
+ currentClipParams.radius.toFloat(),
+ currentClipParams.radius.toFloat(),
+ Path.Direction.CW,
+ )
+ }
+ invalidate()
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ lastWidth = right - left
+ }
+
+ override fun dispatchDraw(canvas: Canvas) {
+ if (!currentClippingPath.isEmpty) {
+ canvas.clipOutPath(currentClippingPath)
+ }
+ super.dispatchDraw(canvas)
+ }
+
override fun isTransformedTouchPointInView(
x: Float,
y: Float,
child: View?,
outLocalPoint: PointF?,
): Boolean {
- return if (clippingEnabledProvider() && y + translationY > clippingTopProvider()) {
+ return if (clippingEnabledProvider() && y + translationY > currentClipParams.top) {
false
} else {
super.isTransformedTouchPointInView(x, y, child, outLocalPoint)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
deleted file mode 100644
index 3049a40f18c4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.composefragment.ui
-
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.geometry.CornerRadius
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.ClipOp
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.CompositingStrategy
-import androidx.compose.ui.graphics.drawscope.clipRect
-import androidx.compose.ui.graphics.graphicsLayer
-
-/**
- * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
- * ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)`
- * from the QS container.
- */
-fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier {
- return this.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
- .drawWithContent {
- drawContent()
- val params = clipParams()
- val left = -params.leftInset.toFloat()
- val right = size.width + params.rightInset.toFloat()
- val top = params.top.toFloat()
- val bottom = params.bottom.toFloat()
- val clipSize = Size(right - left, bottom - top)
- if (!clipSize.isEmpty()) {
- clipRect {
- drawRoundRect(
- color = Color.Black,
- cornerRadius = CornerRadius(params.radius.toFloat()),
- blendMode = BlendMode.Clear,
- topLeft = Offset(left, top),
- size = Size(right - left, bottom - top),
- )
- }
- }
- }
-}
-
-/** Params for [notificationScrimClip]. */
-data class NotificationScrimClipParams(
- val top: Int = 0,
- val bottom: Int = 0,
- val leftInset: Int = 0,
- val rightInset: Int = 0,
- val radius: Int = 0,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt
new file mode 100644
index 000000000000..db320d3b9f1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.ui
+
+/** Params for [notificationScrimClip]. */
+data class NotificationScrimClipParams(
+ val top: Int = 0,
+ val bottom: Int = 0,
+ val leftInset: Int = 0,
+ val rightInset: Int = 0,
+ val radius: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index fce5a16bd85d..e292bcf1f7a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -24,6 +24,7 @@ import android.util.IndentingPrintWriter
import android.util.Log
import android.util.MathUtils
import android.view.Choreographer
+import android.view.Display
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -42,7 +43,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
@@ -52,6 +55,7 @@ import com.android.systemui.util.WallpaperController
import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
+import dagger.Lazy
import java.io.PrintWriter
import java.util.Optional
import javax.inject.Inject
@@ -83,6 +87,7 @@ constructor(
private val appZoomOutOptional: Optional<AppZoomOut>,
@Application private val applicationScope: CoroutineScope,
dumpManager: DumpManager,
+ private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>,
) : ShadeExpansionListener, Dumpable {
companion object {
private const val WAKE_UP_ANIMATION_ENABLED = true
@@ -228,6 +233,14 @@ constructor(
private data class WakeAndUnlockBlurData(val radius: Float, val useZoom: Boolean = true)
+ private val isShadeOnDefaultDisplay: Boolean
+ get() =
+ if (ShadeWindowGoesAround.isEnabled) {
+ shadeDisplaysRepository.get().displayId.value == Display.DEFAULT_DISPLAY
+ } else {
+ true
+ }
+
/** Blur radius of the wake and unlock animation on this frame, and whether to zoom out. */
private var wakeAndUnlockBlurData = WakeAndUnlockBlurData(0f)
set(value) {
@@ -265,9 +278,14 @@ constructor(
var blur = shadeRadius.toInt()
// If the blur comes from waking up, we don't want to zoom out the background
val zoomOut =
- if (shadeRadius != wakeAndUnlockBlurData.radius || wakeAndUnlockBlurData.useZoom)
- blurRadiusToZoomOut(blurRadius = shadeRadius)
- else 0f
+ when {
+ // When the shade is in another display, we don't want to zoom out the background.
+ // Only the default display is supported right now.
+ !isShadeOnDefaultDisplay -> 0f
+ shadeRadius != wakeAndUnlockBlurData.radius || wakeAndUnlockBlurData.useZoom ->
+ blurRadiusToZoomOut(blurRadius = shadeRadius)
+ else -> 0f
+ }
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
if (!Flags.notificationShadeBlur()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 206654abcaaa..9b314f25e02b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -1500,6 +1500,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mKosmos.getKeyguardInteractor(),
mKeyguardTransitionBootInteractor,
mKosmos::getCommunalSceneInteractor,
+ mKosmos::getCommunalSettingsInteractor,
mock(WindowManagerOcclusionManager.class));
mViewMediator.mUserChangedCallback = mUserTrackerCallback;
mViewMediator.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
index 86f7966d4ada..d6b778fe2bc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
@@ -35,8 +35,14 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.activityTransitionAnimator
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.concurrency.fakeExecutor
@@ -81,8 +87,11 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
/** Kotlin version of KeyguardViewMediatorTest to allow for coroutine testing. */
@SmallTest
@@ -152,6 +161,7 @@ class KeyguardViewMediatorTestKt : SysuiTestCase() {
keyguardInteractor,
keyguardTransitionBootInteractor,
{ communalSceneInteractor },
+ { communalSettingsInteractor },
mock<WindowManagerOcclusionManager>(),
)
}
@@ -164,6 +174,10 @@ class KeyguardViewMediatorTestKt : SysuiTestCase() {
@Test
fun doKeyguardTimeout_changesCommunalScene() =
kosmos.runTest {
+ // Hub is enabled and hub condition is active.
+ setCommunalV2Enabled(true)
+ enableHubOnCharging()
+
// doKeyguardTimeout message received.
val timeoutOptions = Bundle()
timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
@@ -174,4 +188,56 @@ class KeyguardViewMediatorTestKt : SysuiTestCase() {
assertThat(communalSceneRepository.currentScene.value)
.isEqualTo(CommunalScenes.Communal)
}
+
+ @Test
+ fun doKeyguardTimeout_communalNotAvailable_sleeps() =
+ kosmos.runTest {
+ // Hub disabled.
+ setCommunalV2Enabled(false)
+
+ // doKeyguardTimeout message received.
+ val timeoutOptions = Bundle()
+ timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
+ underTest.doKeyguardTimeout(timeoutOptions)
+ testableLooper.processAllMessages()
+
+ // Sleep is requested.
+ verify(powerManager)
+ .goToSleep(anyOrNull(), eq(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON), eq(0))
+
+ // Hub scene is not changed.
+ assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
+ }
+
+ @Test
+ fun doKeyguardTimeout_hubConditionNotActive_sleeps() =
+ kosmos.runTest {
+ // Communal enabled, but hub condition set to never.
+ setCommunalV2Enabled(true)
+ disableHubShowingAutomatically()
+
+ // doKeyguardTimeout message received.
+ val timeoutOptions = Bundle()
+ timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
+ underTest.doKeyguardTimeout(timeoutOptions)
+ testableLooper.processAllMessages()
+
+ // Sleep is requested.
+ verify(powerManager)
+ .goToSleep(anyOrNull(), eq(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON), eq(0))
+
+ // Hub scene is not changed.
+ assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
+ }
+
+ private fun Kosmos.enableHubOnCharging() {
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
+ batteryRepository.fake.setDevicePluggedIn(true)
+ }
+
+ private fun Kosmos.disableHubShowingAutomatically() {
+ communalSettingsInteractor.setSuppressionReasons(
+ listOf(SuppressionReason.ReasonUnknown(FEATURE_AUTO_OPEN))
+ )
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index 0b9c45de6e40..60343e9e81e5 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -152,9 +152,20 @@ public class AutoclickController extends BaseEventStreamTransformation {
if (direction == AutoclickScrollPanel.DIRECTION_EXIT) {
return;
}
- // For direction buttons, perform scroll action immediately.
- if (hovered && direction != AutoclickScrollPanel.DIRECTION_NONE) {
- handleScroll(direction);
+
+ // Handle all non-exit buttons when hovered.
+ if (hovered) {
+ // Clear the indicator.
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.cancel();
+ if (mAutoclickIndicatorView != null) {
+ mAutoclickIndicatorView.clearIndicator();
+ }
+ }
+ // Perform scroll action.
+ if (direction != DIRECTION_NONE) {
+ handleScroll(direction);
+ }
}
}
};
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index a28069bbf050..95e58e1a7300 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -684,8 +684,9 @@ public final class DisplayManagerService extends SystemService {
final var backupManager = new BackupManager(mContext);
Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> topologyChangedCallback =
update -> {
- if (mInputManagerInternal != null) {
- mInputManagerInternal.setDisplayTopology(update.second);
+ DisplayTopologyGraph graph = update.second;
+ if (mInputManagerInternal != null && graph != null) {
+ mInputManagerInternal.setDisplayTopology(graph);
}
deliverTopologyUpdate(update.first);
};
@@ -3647,7 +3648,7 @@ public final class DisplayManagerService extends SystemService {
private void deliverTopologyUpdate(DisplayTopology topology) {
if (DEBUG) {
- Slog.d(TAG, "Delivering topology update");
+ Slog.d(TAG, "Delivering topology update: " + topology);
}
if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
Trace.instant(Trace.TRACE_TAG_POWER, "deliverTopologyUpdate");
@@ -4209,13 +4210,18 @@ public final class DisplayManagerService extends SystemService {
public boolean mWifiDisplayScanRequested;
- // A single pending event.
+ // A single pending display event.
private record Event(int displayId, @DisplayEvent int event) { };
- // The list of pending events. This is null until there is a pending event to be saved.
- // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ // The list of pending display events. This is null until there is a pending event to be
+ // saved. This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ @GuardedBy("mCallback")
+ @Nullable
+ private ArrayList<Event> mPendingDisplayEvents;
+
@GuardedBy("mCallback")
- private ArrayList<Event> mPendingEvents;
+ @Nullable
+ private DisplayTopology mPendingTopology;
// Process states: a process is ready to receive events if it is neither cached nor
// frozen.
@@ -4285,7 +4291,10 @@ public final class DisplayManagerService extends SystemService {
*/
@GuardedBy("mCallback")
private boolean hasPendingAndIsReadyLocked() {
- return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty() && mAlive;
+ boolean pendingDisplayEvents = mPendingDisplayEvents != null
+ && !mPendingDisplayEvents.isEmpty();
+ boolean pendingTopology = mPendingTopology != null;
+ return isReadyLocked() && (pendingDisplayEvents || pendingTopology) && mAlive;
}
/**
@@ -4366,7 +4375,8 @@ public final class DisplayManagerService extends SystemService {
// occurs as the client is transitioning to ready but pending events have not
// been dispatched. The new event must be added to the pending list to
// preserve event ordering.
- if (!isReadyLocked() || (mPendingEvents != null && !mPendingEvents.isEmpty())) {
+ if (!isReadyLocked() || (mPendingDisplayEvents != null
+ && !mPendingDisplayEvents.isEmpty())) {
// The client is interested in the event but is not ready to receive it.
// Put the event on the pending list.
addDisplayEvent(displayId, event);
@@ -4453,13 +4463,13 @@ public final class DisplayManagerService extends SystemService {
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
@GuardedBy("mCallback")
private void addDisplayEvent(int displayId, int event) {
- if (mPendingEvents == null) {
- mPendingEvents = new ArrayList<>();
+ if (mPendingDisplayEvents == null) {
+ mPendingDisplayEvents = new ArrayList<>();
}
- if (!mPendingEvents.isEmpty()) {
+ if (!mPendingDisplayEvents.isEmpty()) {
// Ignore redundant events. Further optimization is possible by merging adjacent
// events.
- Event last = mPendingEvents.get(mPendingEvents.size() - 1);
+ Event last = mPendingDisplayEvents.get(mPendingDisplayEvents.size() - 1);
if (last.displayId == displayId && last.event == event) {
if (DEBUG) {
Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event
@@ -4468,12 +4478,13 @@ public final class DisplayManagerService extends SystemService {
return;
}
}
- mPendingEvents.add(new Event(displayId, event));
+ mPendingDisplayEvents.add(new Event(displayId, event));
}
/**
* @return {@code false} if RemoteException happens; otherwise {@code true} for
- * success.
+ * success. This returns true even if the update was deferred because the remote client is
+ * cached or frozen.
*/
boolean notifyTopologyUpdateAsync(DisplayTopology topology) {
if ((mInternalEventFlagsMask.get()
@@ -4490,6 +4501,18 @@ public final class DisplayManagerService extends SystemService {
// The client is not interested in this event, so do nothing.
return true;
}
+
+ if (deferDisplayEventsWhenFrozen()) {
+ synchronized (mCallback) {
+ // Save the new update if the client frozen or cached (not ready).
+ if (!isReadyLocked()) {
+ // The client is interested in the update but is not ready to receive it.
+ mPendingTopology = topology;
+ return true;
+ }
+ }
+ }
+
return transmitTopologyUpdate(topology);
}
@@ -4514,37 +4537,54 @@ public final class DisplayManagerService extends SystemService {
// would be unusual to do so. The method returns true on success.
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
public boolean dispatchPending() {
- Event[] pending;
+ Event[] pendingDisplayEvents = null;
+ DisplayTopology pendingTopology;
synchronized (mCallback) {
- if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
+ if (!mAlive) {
return true;
}
if (!isReadyLocked()) {
return false;
}
- pending = new Event[mPendingEvents.size()];
- pending = mPendingEvents.toArray(pending);
- mPendingEvents.clear();
+
+ if (mPendingDisplayEvents != null && !mPendingDisplayEvents.isEmpty()) {
+ pendingDisplayEvents = new Event[mPendingDisplayEvents.size()];
+ pendingDisplayEvents = mPendingDisplayEvents.toArray(pendingDisplayEvents);
+ mPendingDisplayEvents.clear();
+ }
+
+ pendingTopology = mPendingTopology;
+ mPendingTopology = null;
}
try {
- for (int i = 0; i < pending.length; i++) {
- Event displayEvent = pending[i];
- if (DEBUG) {
- Slog.d(TAG, "Send pending display event #" + i + " "
- + displayEvent.displayId + "/"
- + displayEvent.event + " to " + mUid + "/" + mPid);
- }
+ if (pendingDisplayEvents != null) {
+ for (int i = 0; i < pendingDisplayEvents.length; i++) {
+ Event displayEvent = pendingDisplayEvents[i];
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " "
+ + displayEvent.displayId + "/"
+ + displayEvent.event + " to " + mUid + "/" + mPid);
+ }
+
+ if (!shouldReceiveRefreshRateWithChangeUpdate(displayEvent.event)) {
+ continue;
+ }
- if (!shouldReceiveRefreshRateWithChangeUpdate(displayEvent.event)) {
- continue;
+ transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
}
+ }
- transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
+ if (pendingTopology != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending topology: " + pendingTopology
+ + " to " + mUid + "/" + mPid);
+ }
+ mCallback.onTopologyChanged(pendingTopology);
}
+
return true;
} catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify process "
- + mPid + " that display topology changed, assuming it died.", ex);
+ Slog.w(TAG, "Failed to notify process " + mPid + ", assuming it died.", ex);
binderDied();
return false;
@@ -4556,11 +4596,12 @@ public final class DisplayManagerService extends SystemService {
if (deferDisplayEventsWhenFrozen()) {
final String fmt =
"mPid=%d mUid=%d mWifiDisplayScanRequested=%s"
- + " cached=%s frozen=%s pending=%d";
+ + " cached=%s frozen=%s pendingDisplayEvents=%d pendingTopology=%b";
synchronized (mCallback) {
return formatSimple(fmt,
mPid, mUid, mWifiDisplayScanRequested, mCached, mFrozen,
- (mPendingEvents == null) ? 0 : mPendingEvents.size());
+ (mPendingDisplayEvents == null) ? 0 : mPendingDisplayEvents.size(),
+ mPendingTopology != null);
}
} else {
final String fmt =
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 58cf29b59961..c174451e8f5b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -192,9 +192,15 @@ public class MediaSessionService extends SystemService implements Monitor {
private final Map<Integer, Set<MediaSessionRecordImpl>> mUserEngagedSessionsForFgs =
new HashMap<>();
- /* Maps uid with all media notifications associated to it */
+ /**
+ * Maps UIDs to their associated media notifications: UID -> (Notification ID ->
+ * {@link android.service.notification.StatusBarNotification}).
+ * Each UID maps to a collection of notifications, identified by their
+ * {@link android.service.notification.StatusBarNotification#getId()}.
+ */
@GuardedBy("mLock")
- private final Map<Integer, Set<StatusBarNotification>> mMediaNotifications = new HashMap<>();
+ private final Map<Integer, Map<String, StatusBarNotification>> mMediaNotifications =
+ new HashMap<>();
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
@@ -737,7 +743,8 @@ public class MediaSessionService extends SystemService implements Monitor {
}
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
- for (StatusBarNotification sbn : mMediaNotifications.getOrDefault(uid, Set.of())) {
+ for (StatusBarNotification sbn : mMediaNotifications.getOrDefault(uid,
+ Map.of()).values()) {
if (mediaSessionRecord.isLinkedToNotification(sbn.getNotification())) {
setFgsActiveLocked(mediaSessionRecord, sbn);
return;
@@ -771,7 +778,7 @@ public class MediaSessionService extends SystemService implements Monitor {
int uid, MediaSessionRecordImpl record) {
synchronized (mLock) {
for (StatusBarNotification sbn :
- mMediaNotifications.getOrDefault(uid, Set.of())) {
+ mMediaNotifications.getOrDefault(uid, Map.of()).values()) {
if (record.isLinkedToNotification(sbn.getNotification())) {
return sbn;
}
@@ -794,7 +801,8 @@ public class MediaSessionService extends SystemService implements Monitor {
for (MediaSessionRecordImpl record :
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
for (StatusBarNotification sbn :
- mMediaNotifications.getOrDefault(uid, Set.of())) {
+ mMediaNotifications.getOrDefault(uid, Map.of()).values()) {
+ //
if (record.isLinkedToNotification(sbn.getNotification())) {
// A user engaged session linked with a media notification is found.
// We shouldn't call stop FGS in this case.
@@ -3262,8 +3270,12 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
synchronized (mLock) {
- mMediaNotifications.putIfAbsent(uid, new HashSet<>());
- mMediaNotifications.get(uid).add(sbn);
+ Map<String, StatusBarNotification> notifications = mMediaNotifications.get(uid);
+ if (notifications == null) {
+ notifications = new HashMap<>();
+ mMediaNotifications.put(uid, notifications);
+ }
+ notifications.put(sbn.getKey(), sbn);
MediaSessionRecordImpl userEngagedRecord =
getUserEngagedMediaSessionRecordForNotification(uid, postedNotification);
if (userEngagedRecord != null) {
@@ -3287,10 +3299,10 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
synchronized (mLock) {
- Set<StatusBarNotification> uidMediaNotifications = mMediaNotifications.get(uid);
- if (uidMediaNotifications != null) {
- uidMediaNotifications.remove(sbn);
- if (uidMediaNotifications.isEmpty()) {
+ Map<String, StatusBarNotification> notifications = mMediaNotifications.get(uid);
+ if (notifications != null) {
+ notifications.remove(sbn.getKey());
+ if (notifications.isEmpty()) {
mMediaNotifications.remove(uid);
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index fcd1452b8702..2744721c3a46 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -311,6 +311,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static final int SHORT_PRESS_POWER_LOCK_OR_SLEEP = 6;
static final int SHORT_PRESS_POWER_DREAM_OR_SLEEP = 7;
static final int SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP = 8;
+ static final int SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP = 9;
// must match: config_LongPressOnPowerBehavior in config.xml
// The config value can be overridden using Settings.Global.POWER_BUTTON_LONG_PRESS
@@ -1234,8 +1235,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
break;
}
case SHORT_PRESS_POWER_DREAM_OR_SLEEP: {
- attemptToDreamFromShortPowerButtonPress(
- true,
+ attemptToDreamOrAwakeFromShortPowerButtonPress(
+ /* isScreenOn */ true,
+ /* awakeWhenDream */ false,
+ /* noDreamAction */
() -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
break;
}
@@ -1269,13 +1272,22 @@ public class PhoneWindowManager implements WindowManagerPolicy {
lockNow(options);
} else {
// If the hub cannot be run, attempt to dream instead.
- attemptToDreamFromShortPowerButtonPress(
+ attemptToDreamOrAwakeFromShortPowerButtonPress(
/* isScreenOn */ true,
+ /* awakeWhenDream */ false,
/* noDreamAction */
() -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
}
break;
}
+ case SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP: {
+ attemptToDreamOrAwakeFromShortPowerButtonPress(
+ /* isScreenOn */ true,
+ /* awakeWhenDream */ true,
+ /* noDreamAction */
+ () -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
+ break;
+ }
}
}
}
@@ -1319,15 +1331,18 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
/**
- * Attempt to dream from a power button press.
+ * Attempt to dream, awake or sleep from a power button press.
*
* @param isScreenOn Whether the screen is currently on.
+ * @param awakeWhenDream When it's set to {@code true}, awake the device from dreaming.
+ * Otherwise, go to sleep.
* @param noDreamAction The action to perform if dreaming is not possible.
*/
- private void attemptToDreamFromShortPowerButtonPress(
- boolean isScreenOn, Runnable noDreamAction) {
+ private void attemptToDreamOrAwakeFromShortPowerButtonPress(
+ boolean isScreenOn, boolean awakeWhenDream, Runnable noDreamAction) {
if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP
- && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP) {
+ && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP
+ && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP) {
// If the power button behavior isn't one that should be able to trigger the dream, give
// up.
noDreamAction.run();
@@ -1335,9 +1350,24 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
- if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
- Slog.d(TAG, "Can't start dreaming when attempting to dream from short power"
- + " press (isScreenOn=" + isScreenOn + ")");
+ if (dreamManagerInternal == null) {
+ Slog.d(TAG,
+ "Can't access dream manager dreaming when attempting to start or stop dream "
+ + "from short power press (isScreenOn="
+ + isScreenOn + ", awakeWhenDream=" + awakeWhenDream + ")");
+ noDreamAction.run();
+ return;
+ }
+
+ if (!dreamManagerInternal.canStartDreaming(isScreenOn)) {
+ if (awakeWhenDream && dreamManagerInternal.isDreaming()) {
+ dreamManagerInternal.stopDream(false /*immediate*/, "short press power" /*reason*/);
+ return;
+ }
+ Slog.d(TAG,
+ "Can't start dreaming and the device is not dreaming when attempting to start "
+ + "or stop dream from short power press (isScreenOn="
+ + isScreenOn + ", awakeWhenDream=" + awakeWhenDream + ")");
noDreamAction.run();
return;
}
@@ -2312,6 +2342,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return ActivityManager.getService();
}
+ LockPatternUtils getLockPatternUtils() {
+ return new LockPatternUtils(mContext);
+ }
+
ButtonOverridePermissionChecker getButtonOverridePermissionChecker() {
return new ButtonOverridePermissionChecker();
}
@@ -2360,7 +2394,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mAccessibilityShortcutController = injector.getAccessibilityShortcutController(
mContext, new Handler(), mCurrentUserId);
mGlobalActionsFactory = injector.getGlobalActionsFactory();
- mLockPatternUtils = new LockPatternUtils(mContext);
+ mLockPatternUtils = injector.getLockPatternUtils();
mLogger = new MetricsLogger();
Resources res = mContext.getResources();
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index ac4aac694c3a..11edb93dffea 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -383,7 +383,9 @@ public class MetricUtilities {
/* api_name */
initialPhaseMetric.getApiName(),
/* primary_candidates_indicated */
- candidatePrimaryProviderList
+ candidatePrimaryProviderList,
+ /* api_prepared */
+ initialPhaseMetric.hasApiUsedPrepareFlow()
);
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e);
@@ -442,7 +444,9 @@ public class MetricUtilities {
/* autofill_session_id */
initialPhaseMetric.getAutofillSessionId(),
/* autofill_request_id */
- initialPhaseMetric.getAutofillRequestId()
+ initialPhaseMetric.getAutofillRequestId(),
+ /* api_prepared */
+ initialPhaseMetric.hasApiUsedPrepareFlow()
);
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during initial metric emit: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index d60807c7b001..2d4360edf3a8 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -27,6 +27,7 @@ import android.credentials.GetCredentialRequest;
import android.credentials.IGetCredentialCallback;
import android.credentials.IPrepareGetCredentialCallback;
import android.credentials.PrepareGetCredentialResponseInternal;
+import android.credentials.flags.Flags;
import android.credentials.selection.GetCredentialProviderData;
import android.credentials.selection.ProviderData;
import android.credentials.selection.RequestInfo;
@@ -60,7 +61,12 @@ public class PrepareGetRequestSession extends GetRequestSession {
int numTypes = (request.getCredentialOptions().stream()
.map(CredentialOption::getType).collect(
Collectors.toSet())).size(); // Dedupe type strings
- mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
+ if (!Flags.fixMetricDuplicationEmits()) {
+ mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
+ } else {
+ mRequestSessionMetric.collectGetFlowInitialMetricInfo(request,
+ /*isApiPrepared=*/ true);
+ }
mPrepareGetCredentialCallback = prepareGetCredentialCallback;
Slog.i(TAG, "PrepareGetRequestSession constructed.");
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 8a4e86c440b3..811b97a5bf03 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -55,6 +55,9 @@ public class InitialPhaseMetric {
// The request id of autofill if the request is from autofill, defaults to -1
private int mAutofillRequestId = -1;
+ // Indicates if this API call used the prepare flow, defaults to false
+ private boolean mApiUsedPrepareFlow = false;
+
public InitialPhaseMetric(int sessionIdTrackOne) {
mSessionIdCaller = sessionIdTrackOne;
@@ -173,4 +176,17 @@ public class InitialPhaseMetric {
public int[] getUniqueRequestCounts() {
return mRequestCounts.values().stream().mapToInt(Integer::intValue).toArray();
}
+
+ /* ------ API Prepared ------ */
+
+ public void setApiUsedPrepareFlow(boolean apiUsedPrepareFlow) {
+ mApiUsedPrepareFlow = apiUsedPrepareFlow;
+ }
+
+ /**
+ * @return a boolean indicating if this API call utilized a prepare flow
+ */
+ public boolean hasApiUsedPrepareFlow() {
+ return mApiUsedPrepareFlow;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 619a56846e95..dc1747f803ea 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -225,6 +225,22 @@ public class RequestSessionMetric {
}
/**
+ * Collects initializations for Get flow metrics.
+ *
+ * @param request the get credential request containing information to parse for metrics
+ * @param isApiPrepared indicates this API flow utilized the 'prepare' flow
+ */
+ public void collectGetFlowInitialMetricInfo(GetCredentialRequest request,
+ boolean isApiPrepared) {
+ try {
+ collectGetFlowInitialMetricInfo(request);
+ mInitialPhaseMetric.setApiUsedPrepareFlow(isApiPrepared);
+ } catch (Exception e) {
+ Slog.i(TAG, "Unexpected error collecting get flow initial metric: " + e);
+ }
+ }
+
+ /**
* During browsing, where multiple entries can be selected, this collects the browsing phase
* metric information. This is emitted together with the final phase, and the recursive path
* with authentication entries, which may occur in rare circumstances, are captured.
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index 36ea24195789..c85053d13e68 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -51,6 +51,7 @@ android_test {
data: [
":DisplayManagerTestApp",
+ ":TopologyTestApp",
],
certificate: "platform",
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 205ff058275a..76f219b7433b 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.MANAGE_USB" />
+ <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
<!-- Permissions needed for DisplayTransformManagerTest -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/AndroidTest.xml b/services/tests/displayservicetests/AndroidTest.xml
index f3697bbffd5c..2fe37233870f 100644
--- a/services/tests/displayservicetests/AndroidTest.xml
+++ b/services/tests/displayservicetests/AndroidTest.xml
@@ -28,6 +28,7 @@
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="DisplayManagerTestApp.apk" />
+ <option name="test-file-name" value="TopologyTestApp.apk" />
</target_preparer>
<option name="test-tag" value="DisplayServiceTests" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
index 1f45792e5097..bf4b61347bab 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
@@ -16,7 +16,6 @@
package com.android.server.display;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.util.DisplayMetrics.DENSITY_HIGH;
@@ -27,19 +26,11 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
-import android.app.ActivityManager;
-import android.app.Instrumentation;
-import android.content.Context;
import android.content.Intent;
-import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
-import android.os.BinderProxy;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
-import android.os.Messenger;
-import android.platform.test.annotations.AppModeSdkSandbox;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -48,10 +39,7 @@ import android.util.SparseArray;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.TestUtils;
import com.android.server.am.Flags;
import org.junit.After;
@@ -63,9 +51,7 @@ import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
-import java.io.IOException;
import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -73,8 +59,7 @@ import java.util.concurrent.TimeUnit;
* Tests that applications can receive display events correctly.
*/
@RunWith(Parameterized.class)
-@AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
-public class DisplayEventDeliveryTest {
+public class DisplayEventDeliveryTest extends EventDeliveryTestBase {
private static final String TAG = "DisplayEventDeliveryTest";
@Rule
@@ -85,37 +70,17 @@ public class DisplayEventDeliveryTest {
private static final int WIDTH = 720;
private static final int HEIGHT = 480;
- private static final int MESSAGE_LAUNCHED = 1;
- private static final int MESSAGE_CALLBACK = 2;
-
private static final int DISPLAY_ADDED = 1;
private static final int DISPLAY_CHANGED = 2;
private static final int DISPLAY_REMOVED = 3;
- private static final long DISPLAY_EVENT_TIMEOUT_MSEC = 100;
- private static final long TEST_FAILURE_TIMEOUT_MSEC = 10000;
-
private static final String TEST_PACKAGE =
"com.android.servicestests.apps.displaymanagertestapp";
private static final String TEST_ACTIVITY = TEST_PACKAGE + ".DisplayEventActivity";
private static final String TEST_DISPLAYS = "DISPLAYS";
- private static final String TEST_MESSENGER = "MESSENGER";
private final Object mLock = new Object();
- private Instrumentation mInstrumentation;
- private Context mContext;
- private DisplayManager mDisplayManager;
- private ActivityManager mActivityManager;
- private ActivityManager.OnUidImportanceListener mUidImportanceListener;
- private CountDownLatch mLatchActivityLaunch;
- private CountDownLatch mLatchActivityCached;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private Messenger mMessenger;
- private int mPid;
- private int mUid;
-
/**
* Array of DisplayBundle. The test handler uses it to check if certain display events have
* been sent to DisplayEventActivity.
@@ -167,7 +132,7 @@ public class DisplayEventDeliveryTest {
*/
public void assertNoDisplayEvents() {
try {
- assertNull(mExpectations.poll(DISPLAY_EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
+ assertNull(mExpectations.poll(EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
@@ -239,37 +204,17 @@ public class DisplayEventDeliveryTest {
}
@Before
- public void setUp() throws Exception {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mContext = mInstrumentation.getContext();
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
- mLatchActivityLaunch = new CountDownLatch(1);
- mLatchActivityCached = new CountDownLatch(1);
- mActivityManager = mContext.getSystemService(ActivityManager.class);
- mUidImportanceListener = (uid, importance) -> {
- if (uid == mUid && importance == IMPORTANCE_CACHED) {
- Log.d(TAG, "Listener " + uid + " becomes " + importance);
- mLatchActivityCached.countDown();
- }
- };
- SystemUtil.runWithShellPermissionIdentity(() ->
- mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
- IMPORTANCE_CACHED));
+ public void setUp() {
+ super.setUp();
// The lock is not functionally necessary but eliminates lint error messages.
synchronized (mLock) {
mDisplayBundles = new SparseArray<>();
}
- mHandlerThread = new HandlerThread("handler");
- mHandlerThread.start();
- mHandler = new TestHandler(mHandlerThread.getLooper());
- mMessenger = new Messenger(mHandler);
- mPid = 0;
}
@After
public void tearDown() throws Exception {
- mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
- mHandlerThread.quitSafely();
+ super.tearDown();
synchronized (mLock) {
for (int i = 0; i < mDisplayBundles.size(); i++) {
DisplayBundle bundle = mDisplayBundles.valueAt(i);
@@ -278,7 +223,31 @@ public class DisplayEventDeliveryTest {
}
mDisplayBundles.clear();
}
- SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + TEST_PACKAGE);
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected Handler getHandler(Looper looper) {
+ return new TestHandler(looper);
+ }
+
+ @Override
+ protected String getTestPackage() {
+ return TEST_PACKAGE;
+ }
+
+ @Override
+ protected String getTestActivity() {
+ return TEST_ACTIVITY;
+ }
+
+ @Override
+ protected void putExtra(Intent intent) {
+ intent.putExtra(TEST_DISPLAYS, mDisplayCount);
}
/**
@@ -291,42 +260,8 @@ public class DisplayEventDeliveryTest {
}
/**
- * Return true if the freezer is enabled on this platform and if freezer notifications are
- * supported. It is not enough to test that the freezer notification feature is enabled
- * because some devices do not have the necessary kernel support.
- */
- private boolean isAppFreezerEnabled() {
- try {
- return mActivityManager.getService().isAppFreezerEnabled()
- && android.os.Flags.binderFrozenStateChangeCallback()
- && BinderProxy.isFrozenStateChangeCallbackSupported();
- } catch (Exception e) {
- Log.e(TAG, "isAppFreezerEnabled() failed: " + e);
- return false;
- }
- }
-
- private void waitForProcessFreeze(int pid, long timeoutMs) {
- // TODO: Add a listener to monitor freezer state changes.
- SystemUtil.runWithShellPermissionIdentity(() -> {
- TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
- (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
- () -> mActivityManager.isProcessFrozen(pid));
- });
- }
-
- private void waitForProcessUnfreeze(int pid, long timeoutMs) {
- // TODO: Add a listener to monitor freezer state changes.
- SystemUtil.runWithShellPermissionIdentity(() -> {
- TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
- (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
- () -> !mActivityManager.isProcessFrozen(pid));
- });
- }
-
- /**
- * Create virtual displays, change their configurations and release them. The number of
- * displays is set by the {@link #mDisplays} variable.
+ * Create virtual displays, change their configurations and release them. The number of
+ * displays is set by the {@link #data()} parameter.
*/
private void testDisplayEventsInternal(boolean cached, boolean frozen) {
Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + cached + " " + frozen);
@@ -445,110 +380,6 @@ public class DisplayEventDeliveryTest {
}
/**
- * Launch the test activity that would listen to display events. Return its process ID.
- */
- private int launchTestActivity() {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
- intent.putExtra(TEST_MESSENGER, mMessenger);
- intent.putExtra(TEST_DISPLAYS, mDisplayCount);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- SystemUtil.runWithShellPermissionIdentity(
- () -> {
- mContext.startActivity(intent);
- },
- android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
- waitLatch(mLatchActivityLaunch);
-
- try {
- String cmd = "pidof " + TEST_PACKAGE;
- String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
- return Integer.parseInt(result.trim());
- } catch (IOException e) {
- fail("failed to get pid of test package");
- return 0;
- } catch (NumberFormatException e) {
- fail("failed to parse pid " + e);
- return 0;
- }
- }
-
- /**
- * Bring the test activity back to top
- */
- private void bringTestActivityTop() {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- SystemUtil.runWithShellPermissionIdentity(
- () -> {
- mContext.startActivity(intent);
- },
- android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
- }
-
- /**
- * Bring the test activity into cached mode by launching another 2 apps
- */
- private void makeTestActivityCached() {
- // Launch another activity to bring the test activity into background
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClass(mContext, SimpleActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-
- // Launch another activity to bring the test activity into cached mode
- Intent intent2 = new Intent(Intent.ACTION_MAIN);
- intent2.setClass(mContext, SimpleActivity2.class);
- intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- SystemUtil.runWithShellPermissionIdentity(
- () -> {
- mInstrumentation.startActivitySync(intent);
- mInstrumentation.startActivitySync(intent2);
- },
- android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
- waitLatch(mLatchActivityCached);
- }
-
- // Sleep, ignoring interrupts.
- private void pause(int s) {
- try { Thread.sleep(s * 1000); } catch (Exception e) { }
- }
-
- /**
- * Freeze the test activity.
- */
- private void makeTestActivityFrozen(int pid) {
- // The delay here is meant to allow pending binder transactions to drain. A process
- // cannot be frozen if it has pending binder transactions, and attempting to freeze such a
- // process more than a few times will result in the system killing the process.
- pause(5);
- try {
- String cmd = "am freeze --sticky ";
- SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
- } catch (IOException e) {
- fail(e.toString());
- }
- // Wait for the freeze to complete in the kernel and for the frozen process
- // notification to settle out.
- waitForProcessFreeze(pid, 5 * 1000);
- }
-
- /**
- * Freeze the test activity.
- */
- private void makeTestActivityUnfrozen(int pid) {
- try {
- String cmd = "am unfreeze --sticky ";
- SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
- } catch (IOException e) {
- fail(e.toString());
- }
- // Wait for the freeze to complete in the kernel and for the frozen process
- // notification to settle out.
- waitForProcessUnfreeze(pid, 5 * 1000);
- }
-
- /**
* Create a virtual display
*
* @param name The name of the new virtual display
@@ -560,15 +391,4 @@ public class DisplayEventDeliveryTest {
VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
/* flags: a public virtual display that another app can access */);
}
-
- /**
- * Wait for CountDownLatch with timeout
- */
- private void waitLatch(CountDownLatch latch) {
- try {
- latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/EventDeliveryTestBase.java b/services/tests/displayservicetests/src/com/android/server/display/EventDeliveryTestBase.java
new file mode 100644
index 000000000000..2911b9bb35c7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/EventDeliveryTestBase.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.BinderProxy;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Messenger;
+import android.platform.test.annotations.AppModeSdkSandbox;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
+public abstract class EventDeliveryTestBase {
+ protected static final int MESSAGE_LAUNCHED = 1;
+ protected static final int MESSAGE_CALLBACK = 2;
+
+ protected static final long EVENT_TIMEOUT_MSEC = 100;
+ protected static final long TEST_FAILURE_TIMEOUT_MSEC = 10000;
+
+ private static final String TEST_MESSENGER = "MESSENGER";
+
+ private Instrumentation mInstrumentation;
+ private Context mContext;
+ protected DisplayManager mDisplayManager;
+ private ActivityManager mActivityManager;
+ private ActivityManager.OnUidImportanceListener mUidImportanceListener;
+ protected CountDownLatch mLatchActivityLaunch;
+ private CountDownLatch mLatchActivityCached;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private Messenger mMessenger;
+ protected int mPid;
+ protected int mUid;
+
+ protected abstract String getTag();
+
+ protected abstract Handler getHandler(Looper looper);
+
+ protected abstract String getTestPackage();
+
+ protected abstract String getTestActivity();
+
+ protected abstract void putExtra(Intent intent);
+
+ protected void setUp() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getContext();
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ mLatchActivityLaunch = new CountDownLatch(1);
+ mLatchActivityCached = new CountDownLatch(1);
+ mActivityManager = mContext.getSystemService(ActivityManager.class);
+ mUidImportanceListener = (uid, importance) -> {
+ if (uid == mUid && importance == IMPORTANCE_CACHED) {
+ Log.d(getTag(), "Listener " + uid + " becomes " + importance);
+ mLatchActivityCached.countDown();
+ }
+ };
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
+ IMPORTANCE_CACHED));
+ mHandlerThread = new HandlerThread("handler");
+ mHandlerThread.start();
+ mHandler = getHandler(mHandlerThread.getLooper());
+ mMessenger = new Messenger(mHandler);
+ mPid = 0;
+ }
+
+ protected void tearDown() throws Exception {
+ mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
+ mHandlerThread.quitSafely();
+ SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + getTestPackage());
+ }
+
+ /**
+ * Return true if the freezer is enabled on this platform and if freezer notifications are
+ * supported. It is not enough to test that the freezer notification feature is enabled
+ * because some devices do not have the necessary kernel support.
+ */
+ protected boolean isAppFreezerEnabled() {
+ try {
+ return ActivityManager.getService().isAppFreezerEnabled()
+ && android.os.Flags.binderFrozenStateChangeCallback()
+ && BinderProxy.isFrozenStateChangeCallbackSupported();
+ } catch (Exception e) {
+ Log.e(getTag(), "isAppFreezerEnabled() failed: " + e);
+ return false;
+ }
+ }
+
+ private void waitForProcessFreeze(int pid, long timeoutMs) {
+ // TODO: Add a listener to monitor freezer state changes.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ TestUtils.waitUntil(
+ "Timed out waiting for test process to be frozen; pid=" + pid,
+ (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
+ () -> mActivityManager.isProcessFrozen(pid));
+ });
+ }
+
+ private void waitForProcessUnfreeze(int pid, long timeoutMs) {
+ // TODO: Add a listener to monitor freezer state changes.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
+ (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
+ () -> !mActivityManager.isProcessFrozen(pid));
+ });
+ }
+
+ /**
+ * Launch the test activity that would listen to events. Return its process ID.
+ */
+ protected int launchTestActivity() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(getTestPackage(), getTestActivity());
+ intent.putExtra(TEST_MESSENGER, mMessenger);
+ putExtra(intent);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> {
+ mContext.startActivity(intent);
+ },
+ android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
+ waitLatch(mLatchActivityLaunch);
+
+ try {
+ String cmd = "pidof " + getTestPackage();
+ String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
+ return Integer.parseInt(result.trim());
+ } catch (IOException e) {
+ fail("failed to get pid of test package");
+ return 0;
+ } catch (NumberFormatException e) {
+ fail("failed to parse pid " + e);
+ return 0;
+ }
+ }
+
+ /**
+ * Bring the test activity back to top
+ */
+ protected void bringTestActivityTop() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(getTestPackage(), getTestActivity());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> {
+ mContext.startActivity(intent);
+ },
+ android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
+ }
+
+
+ /**
+ * Bring the test activity into cached mode by launching another 2 apps
+ */
+ protected void makeTestActivityCached() {
+ // Launch another activity to bring the test activity into background
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(mContext, SimpleActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+
+ // Launch another activity to bring the test activity into cached mode
+ Intent intent2 = new Intent(Intent.ACTION_MAIN);
+ intent2.setClass(mContext, SimpleActivity2.class);
+ intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> {
+ mInstrumentation.startActivitySync(intent);
+ mInstrumentation.startActivitySync(intent2);
+ },
+ android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
+ waitLatch(mLatchActivityCached);
+ }
+
+ // Sleep, ignoring interrupts.
+ private void pause(int s) {
+ try {
+ Thread.sleep(s * 1000L);
+ } catch (Exception ignored) { }
+ }
+
+ /**
+ * Freeze the test activity.
+ */
+ protected void makeTestActivityFrozen(int pid) {
+ // The delay here is meant to allow pending binder transactions to drain. A process
+ // cannot be frozen if it has pending binder transactions, and attempting to freeze such a
+ // process more than a few times will result in the system killing the process.
+ pause(5);
+ try {
+ String cmd = "am freeze --sticky ";
+ SystemUtil.runShellCommand(mInstrumentation, cmd + getTestPackage());
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ // Wait for the freeze to complete in the kernel and for the frozen process
+ // notification to settle out.
+ waitForProcessFreeze(pid, 5 * 1000);
+ }
+
+ /**
+ * Freeze the test activity.
+ */
+ protected void makeTestActivityUnfrozen(int pid) {
+ try {
+ String cmd = "am unfreeze --sticky ";
+ SystemUtil.runShellCommand(mInstrumentation, cmd + getTestPackage());
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ // Wait for the freeze to complete in the kernel and for the frozen process
+ // notification to settle out.
+ waitForProcessUnfreeze(pid, 5 * 1000);
+ }
+
+ /**
+ * Wait for CountDownLatch with timeout
+ */
+ private void waitLatch(CountDownLatch latch) {
+ try {
+ latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/TopologyUpdateDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/TopologyUpdateDeliveryTest.java
new file mode 100644
index 000000000000..5fd248dba53f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/TopologyUpdateDeliveryTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Intent;
+import android.hardware.display.DisplayTopology;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests that applications can receive topology updates correctly.
+ */
+public class TopologyUpdateDeliveryTest extends EventDeliveryTestBase {
+ private static final String TAG = TopologyUpdateDeliveryTest.class.getSimpleName();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String TEST_PACKAGE = "com.android.servicestests.apps.topologytestapp";
+ private static final String TEST_ACTIVITY = TEST_PACKAGE + ".TopologyUpdateActivity";
+
+ // Topology updates we expect to receive before timeout
+ private final LinkedBlockingQueue<DisplayTopology> mExpectations = new LinkedBlockingQueue<>();
+
+ /**
+ * Add the received topology update from the test activity to the queue
+ *
+ * @param topology The corresponding topology update
+ */
+ private void addTopologyUpdate(DisplayTopology topology) {
+ Log.d(TAG, "Received " + topology);
+ mExpectations.offer(topology);
+ }
+
+ /**
+ * Assert that there isn't any unexpected display event from the test activity
+ */
+ private void assertNoTopologyUpdates() {
+ try {
+ assertNull(mExpectations.poll(EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Wait for the expected topology update from the test activity
+ *
+ * @param expect The expected topology update
+ */
+ private void waitTopologyUpdate(DisplayTopology expect) {
+ while (true) {
+ try {
+ DisplayTopology update = mExpectations.poll(TEST_FAILURE_TIMEOUT_MSEC,
+ TimeUnit.MILLISECONDS);
+ assertNotNull(update);
+ if (expect.equals(update)) {
+ Log.d(TAG, "Found " + update);
+ return;
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private class TestHandler extends Handler {
+ TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ case MESSAGE_LAUNCHED:
+ mPid = msg.arg1;
+ mUid = msg.arg2;
+ Log.d(TAG, "Launched " + mPid + " " + mUid);
+ mLatchActivityLaunch.countDown();
+ break;
+ case MESSAGE_CALLBACK:
+ DisplayTopology topology = (DisplayTopology) msg.obj;
+ Log.d(TAG, "Callback " + topology);
+ addTopologyUpdate(topology);
+ break;
+ default:
+ fail("Unexpected value: " + msg.what);
+ break;
+ }
+ }
+ }
+
+ @Before
+ public void setUp() {
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected Handler getHandler(Looper looper) {
+ return new TestHandler(looper);
+ }
+
+ @Override
+ protected String getTestPackage() {
+ return TEST_PACKAGE;
+ }
+
+ @Override
+ protected String getTestActivity() {
+ return TEST_ACTIVITY;
+ }
+
+ @Override
+ protected void putExtra(Intent intent) { }
+
+ private void testTopologyUpdateInternal(boolean cached, boolean frozen) {
+ Log.d(TAG, "Start test testTopologyUpdate " + cached + " " + frozen);
+ // Launch activity and start listening to topology updates
+ int pid = launchTestActivity();
+
+ // The test activity in cached or frozen mode won't receive the pending topology updates.
+ if (cached) {
+ makeTestActivityCached();
+ }
+ if (frozen) {
+ makeTestActivityFrozen(pid);
+ }
+
+ // Change the topology
+ int primaryDisplayId = 3;
+ DisplayTopology.TreeNode root = new DisplayTopology.TreeNode(primaryDisplayId,
+ /* width= */ 600, /* height= */ 400, DisplayTopology.TreeNode.POSITION_LEFT,
+ /* offset= */ 0);
+ DisplayTopology.TreeNode child = new DisplayTopology.TreeNode(/* displayId= */ 1,
+ /* width= */ 800, /* height= */ 600, DisplayTopology.TreeNode.POSITION_LEFT,
+ /* offset= */ 0);
+ root.addChild(child);
+ DisplayTopology topology = new DisplayTopology(root, primaryDisplayId);
+ mDisplayManager.setDisplayTopology(topology);
+
+ if (cached || frozen) {
+ assertNoTopologyUpdates();
+ } else {
+ waitTopologyUpdate(topology);
+ }
+
+ // Unfreeze the test activity, if it was frozen.
+ if (frozen) {
+ makeTestActivityUnfrozen(pid);
+ }
+
+ if (cached || frozen) {
+ // Always ensure the test activity is not cached.
+ bringTestActivityTop();
+
+ // The test activity becomes non-cached and should receive the pending topology updates
+ waitTopologyUpdate(topology);
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY)
+ public void testTopologyUpdate() {
+ testTopologyUpdateInternal(false, false);
+ }
+
+ /**
+ * The app is moved to cached and the test verifies that no updates are delivered to the cached
+ * app.
+ */
+ @Test
+ @RequiresFlagsEnabled(com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY)
+ public void testTopologyUpdateCached() {
+ testTopologyUpdateInternal(true, false);
+ }
+
+ /**
+ * The app is frozen and the test verifies that no updates are delivered to the frozen app.
+ */
+ @RequiresFlagsEnabled({com.android.server.am.Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN,
+ com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY})
+ @Test
+ public void testTopologyUpdateFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ testTopologyUpdateInternal(false, true);
+ }
+
+ /**
+ * The app is cached and frozen and the test verifies that no updates are delivered to the app.
+ */
+ @RequiresFlagsEnabled({com.android.server.am.Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN,
+ com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY})
+ @Test
+ public void testTopologyUpdateCachedFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ testTopologyUpdateInternal(true, true);
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index d702cae248a9..067fba9893e5 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -31,6 +31,7 @@ android_test {
"test-apps/SuspendTestApp/src/**/*.java",
"test-apps/DisplayManagerTestApp/src/**/*.java",
+ "test-apps/TopologyTestApp/src/**/*.java",
],
static_libs: [
@@ -141,6 +142,7 @@ android_test {
data: [
":DisplayManagerTestApp",
+ ":TopologyTestApp",
":SimpleServiceTestApp1",
":SimpleServiceTestApp2",
":SimpleServiceTestApp3",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 5298251b79f7..9a4983482522 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -36,6 +36,7 @@
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="DisplayManagerTestApp.apk" />
+ <option name="test-file-name" value="TopologyTestApp.apk" />
<option name="test-file-name" value="FrameworksServicesTests.apk" />
<option name="test-file-name" value="SuspendTestApp.apk" />
<option name="test-file-name" value="SimpleServiceTestApp1.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index 99c922ca30c4..df77866b5e7f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -866,6 +867,23 @@ public class AutoclickControllerTest {
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void scrollPanelController_directionalButtonsHideIndicator() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Create a spy on the real object to verify method calls.
+ AutoclickIndicatorView spyIndicatorView = spy(mController.mAutoclickIndicatorView);
+ mController.mAutoclickIndicatorView = spyIndicatorView;
+
+ // Simulate hover on direction button.
+ mController.mScrollPanelController.onHoverButtonChange(
+ AutoclickScrollPanel.DIRECTION_UP, true);
+
+ // Verify clearIndicator was called.
+ verify(spyIndicatorView).clearIndicator();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() {
MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
mController.setNext(motionEventCaptor);
diff --git a/services/tests/servicestests/test-apps/TopologyTestApp/Android.bp b/services/tests/servicestests/test-apps/TopologyTestApp/Android.bp
new file mode 100644
index 000000000000..dcf9cc216687
--- /dev/null
+++ b/services/tests/servicestests/test-apps/TopologyTestApp/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "TopologyTestApp",
+
+ srcs: ["**/*.java"],
+
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/services/tests/servicestests/test-apps/TopologyTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/TopologyTestApp/AndroidManifest.xml
new file mode 100644
index 000000000000..dad2315148df
--- /dev/null
+++ b/services/tests/servicestests/test-apps/TopologyTestApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.topologytestapp">
+
+ <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
+
+ <application android:label="TopologyUpdateTestApp">
+ <activity android:name="com.android.servicestests.apps.topologytestapp.TopologyUpdateActivity"
+ android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/TopologyTestApp/OWNERS b/services/tests/servicestests/test-apps/TopologyTestApp/OWNERS
new file mode 100644
index 000000000000..e9557f84f8fb
--- /dev/null
+++ b/services/tests/servicestests/test-apps/TopologyTestApp/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 345010
+
+include /services/core/java/com/android/server/display/OWNERS
diff --git a/services/tests/servicestests/test-apps/TopologyTestApp/src/com/android/servicestests/apps/topologytestapp/TopologyUpdateActivity.java b/services/tests/servicestests/test-apps/TopologyTestApp/src/com/android/servicestests/apps/topologytestapp/TopologyUpdateActivity.java
new file mode 100644
index 000000000000..b35ba3c2c60c
--- /dev/null
+++ b/services/tests/servicestests/test-apps/TopologyTestApp/src/com/android/servicestests/apps/topologytestapp/TopologyUpdateActivity.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.servicestests.apps.topologytestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayTopology;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.function.Consumer;
+
+/**
+ * A simple activity listening to topology updates
+ */
+public class TopologyUpdateActivity extends Activity {
+ public static final int MESSAGE_LAUNCHED = 1;
+ public static final int MESSAGE_CALLBACK = 2;
+
+ private static final String TAG = TopologyUpdateActivity.class.getSimpleName();
+
+ private static final String TEST_MESSENGER = "MESSENGER";
+
+ private Messenger mMessenger;
+ private DisplayManager mDisplayManager;
+ private final Consumer<DisplayTopology> mTopologyListener = this::callback;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ mMessenger = intent.getParcelableExtra(TEST_MESSENGER, Messenger.class);
+ mDisplayManager = getApplicationContext().getSystemService(DisplayManager.class);
+ mDisplayManager.registerTopologyListener(getMainExecutor(), mTopologyListener);
+ launched();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mDisplayManager.unregisterTopologyListener(mTopologyListener);
+ }
+
+ private void launched() {
+ try {
+ Message msg = Message.obtain();
+ msg.what = MESSAGE_LAUNCHED;
+ msg.arg1 = android.os.Process.myPid();
+ msg.arg2 = Process.myUid();
+ Log.d(TAG, "Launched");
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ private void callback(DisplayTopology topology) {
+ try {
+ Message msg = Message.obtain();
+ msg.what = MESSAGE_CALLBACK;
+ msg.obj = topology;
+ Log.d(TAG, "Msg " + topology);
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 68229688c238..f3d5e39ec127 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -35,6 +35,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.policy.PhoneWindowManager.EXTRA_TRIGGER_HUB;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP;
import static com.google.common.truth.Truth.assertThat;
@@ -64,6 +65,7 @@ import android.view.contentprotection.flags.Flags;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.LocalServiceKeeperRule;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.input.InputManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.keyguard.KeyguardServiceDelegate;
@@ -120,6 +122,8 @@ public class PhoneWindowManagerTests {
private DisplayPolicy mDisplayPolicy;
@Mock
private KeyguardServiceDelegate mKeyguardServiceDelegate;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
@Before
public void setUp() {
@@ -254,6 +258,7 @@ public class PhoneWindowManagerTests {
@Test
public void powerPress_hubOrDreamOrSleep_goesToSleepFromDream() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
initPhoneWindowManager();
// Set power button behavior.
@@ -275,6 +280,7 @@ public class PhoneWindowManagerTests {
@Test
public void powerPress_hubOrDreamOrSleep_hubAvailableLocks() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
mContext.getTestablePermissions().setPermission(android.Manifest.permission.DEVICE_POWER,
PERMISSION_GRANTED);
initPhoneWindowManager();
@@ -303,6 +309,7 @@ public class PhoneWindowManagerTests {
@Test
public void powerPress_hubOrDreamOrSleep_hubNotAvailableDreams() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
initPhoneWindowManager();
// Set power button behavior.
@@ -323,6 +330,77 @@ public class PhoneWindowManagerTests {
verify(mDreamManagerInternal).requestDream();
}
+ @Test
+ public void powerPress_dreamOrAwakeOrSleep_awakeFromDream() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Can not dream when device is dreaming.
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(false);
+ // Device is dreaming.
+ when(mDreamManagerInternal.isDreaming()).thenReturn(true);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Dream is stopped.
+ verify(mDreamManagerInternal)
+ .stopDream(false /*immediate*/, "short press power" /*reason*/);
+ }
+
+ @Test
+ public void powerPress_dreamOrAwakeOrSleep_canNotDreamGoToSleep() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Can not dream for other reasons.
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(false);
+ // Device is not dreaming.
+ when(mDreamManagerInternal.isDreaming()).thenReturn(false);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Device goes to sleep.
+ verify(mPowerManager).goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ }
+
+ @Test
+ public void powerPress_dreamOrAwakeOrSleep_dreamFromActive() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Can dream when active.
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(true);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Dream is requested.
+ verify(mDreamManagerInternal).requestDream();
+ }
+
private void initPhoneWindowManager() {
mPhoneWindowManager.mDefaultDisplayPolicy = mDisplayPolicy;
mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class);
@@ -346,6 +424,11 @@ public class PhoneWindowManagerTests {
return mKeyguardServiceDelegate;
}
+ @Override
+ LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
/**
* {@code WindowWakeUpPolicy} registers a local service in its constructor, easier to just
* mock it out so we don't have to unregister it after every test.
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index 9452e588953e..5576ec0b882f 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -38,14 +38,14 @@ class ConvertCommand : public Command {
"--enable-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
- "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
- "the APK is O+",
+ "Only applies sparse encoding if minSdk of the APK is >= 32",
&enable_sparse_encoding_);
- AddOptionalSwitch("--force-sparse-encoding",
- "Enables encoding sparse entries using a binary search tree.\n"
- "This decreases APK size at the cost of resource retrieval performance.\n"
- "Applies sparse encoding to all resources regardless of minSdk.",
- &force_sparse_encoding_);
+ AddOptionalSwitch(
+ "--force-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding if minSdk of the APK is >= 32 or is not set",
+ &force_sparse_encoding_);
AddOptionalSwitch(
"--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 977978834fcd..54a8c8625eb5 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -164,9 +164,12 @@ class LinkCommand : public Command {
AddOptionalSwitch("--no-resource-removal", "Disables automatic removal of resources without\n"
"defaults. Use this only when building runtime resource overlay packages.",
&options_.no_resource_removal);
- AddOptionalSwitch("--enable-sparse-encoding",
- "This decreases APK size at the cost of resource retrieval performance.",
- &options_.use_sparse_encoding);
+ AddOptionalSwitch(
+ "--enable-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding if minSdk of the APK is >= 32",
+ &options_.use_sparse_encoding);
AddOptionalSwitch("--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
&options_.table_flattener_options.use_compact_entries);
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 012b0f230ca2..a8f547e3d96c 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -108,14 +108,14 @@ class OptimizeCommand : public Command {
"--enable-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
- "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
- "the APK is O+",
+ "Only applies sparse encoding if minSdk of the APK is >= 32",
&options_.enable_sparse_encoding);
- AddOptionalSwitch("--force-sparse-encoding",
- "Enables encoding sparse entries using a binary search tree.\n"
- "This decreases APK size at the cost of resource retrieval performance.\n"
- "Applies sparse encoding to all resources regardless of minSdk.",
- &options_.force_sparse_encoding);
+ AddOptionalSwitch(
+ "--force-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding if minSdk of the APK is >= 32 or is not set",
+ &options_.force_sparse_encoding);
AddOptionalSwitch(
"--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 50144ae816b6..d19c9f2d5d75 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -197,13 +197,16 @@ class PackageFlattener {
bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled ||
sparse_entries_ == SparseEntriesMode::Forced;
- if (sparse_entries_ == SparseEntriesMode::Forced ||
- (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0)) {
- // Sparse encode if forced or sdk version is not set in context and config.
- } else {
- // Otherwise, only sparse encode if the entries will be read on platforms S_V2+.
- sparse_encode = sparse_encode && (context_->GetMinSdkVersion() >= SDK_S_V2);
- }
+ // Only sparse encode if the entries will be read on platforms S_V2+. Sparse encoding
+ // is not supported on older platforms (b/197642721, b/197976367).
+ //
+ // We also allow sparse encoding for minSdk is 0 (not set) if sparse encoding is forced,
+ // in order to support Bundletool's usage of aapt2 where minSdk is not set in splits.
+ bool meets_min_sdk_requirement_for_sparse_encoding =
+ (context_->GetMinSdkVersion() >= SDK_S_V2) ||
+ (context_->GetMinSdkVersion() == 0 && sparse_entries_ == SparseEntriesMode::Forced);
+
+ sparse_encode = sparse_encode && meets_min_sdk_requirement_for_sparse_encoding;
// Only sparse encode if the offsets are representable in 2 bytes.
sparse_encode = sparse_encode && short_offsets;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 9156b96b67ec..0e8aae14a350 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -15,6 +15,7 @@
*/
#include "format/binary/TableFlattener.h"
+#include <string>
#include "android-base/stringprintf.h"
#include "androidfw/TypeWrappers.h"
@@ -326,6 +327,28 @@ static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
return table;
}
+static void CheckSparseEntries(IAaptContext* context, const ConfigDescription& sparse_config,
+ const std::string& sparse_contents) {
+ ResourceTable sparse_table;
+ BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
+ sparse_contents.data(), sparse_contents.size());
+ ASSERT_TRUE(parser.Parse());
+
+ auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
+ sparse_config);
+ ASSERT_THAT(value, NotNull());
+ EXPECT_EQ(0u, value->value.data);
+
+ ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
+ sparse_config),
+ IsNull());
+
+ value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
+ sparse_config);
+ ASSERT_THAT(value, NotNull());
+ EXPECT_EQ(4u, value->value.data);
+}
+
TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
@@ -347,29 +370,56 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2) {
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
- // Attempt to parse the sparse contents.
+ CheckSparseEntries(context.get(), sparse_config, sparse_contents);
+}
- ResourceTable sparse_table;
- BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
- sparse_contents.data(), sparse_contents.size());
- ASSERT_TRUE(parser.Parse());
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2AndForced) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetMinSdkVersion(SDK_S_V2)
+ .Build();
- auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(0u, value->value.data);
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
- ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
- sparse_config),
- IsNull());
+ TableFlattenerOptions options;
+ options.sparse_entries = SparseEntriesMode::Forced;
- value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(4u, value->value.data);
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+
+ CheckSparseEntries(context.get(), sparse_config, sparse_contents);
}
-TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionSV2) {
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetMinSdkVersion(SDK_LOLLIPOP)
+ .Build();
+
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+ TableFlattenerOptions options;
+ options.sparse_entries = SparseEntriesMode::Enabled;
+
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2AndConfigSdkVersionSV2) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
.SetPackageId(0x01)
@@ -391,7 +441,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionSV2) {
EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
}
-TEST_F(TableFlattenerTest, FlattenSparseEntryRegardlessOfMinSdkWhenForced) {
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2AndForced) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
.SetPackageId(0x01)
@@ -410,7 +460,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryRegardlessOfMinSdkWhenForced) {
std::string sparse_contents;
ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
- EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
}
TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) {
@@ -429,28 +479,28 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) {
std::string sparse_contents;
ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
- EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
+}
- // Attempt to parse the sparse contents.
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSetAndForced) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build();
- ResourceTable sparse_table;
- BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
- sparse_contents.data(), sparse_contents.size());
- ASSERT_TRUE(parser.Parse());
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
- auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(0u, value->value.data);
+ TableFlattenerOptions options;
+ options.sparse_entries = SparseEntriesMode::Forced;
- ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
- sparse_config),
- IsNull());
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
- value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(4u, value->value.data);
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+
+ CheckSparseEntries(context.get(), sparse_config, sparse_contents);
}
TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 6bdbaaed9858..413f817ea8fd 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -5,6 +5,10 @@
2017. This README will be updated more frequently in the future.
- Added a new flag `--no-compress-fonts`. This can significantly speed up loading fonts from APK
assets, at the cost of increasing the storage size of the APK.
+- Changed the behavior of `--enable-sparse-encoding`. Sparse encoding is only applied if the
+ minSdkVersion is >= 32.
+- Changed the behavior of `--force-sparse-encoding`. Sparse encoding is only applied if the
+ minSdkVersion is >= 32 or is not set.
## Version 2.19
- Added navigation resource type.