summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/AppOpsManager.java9
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig7
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java7
-rw-r--r--core/java/android/view/WindowManager.java10
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig8
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java2
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java4
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt59
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt116
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandler.kt57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java134
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt3
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt6
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt6
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt77
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandlerTest.kt101
-rw-r--r--location/java/android/location/GnssMeasurement.java4
-rw-r--r--location/java/android/location/GnssMeasurementsEvent.java8
-rw-r--r--location/java/com/android/internal/location/GpsNetInitiatedHandler.java14
-rw-r--r--packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt18
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java39
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java87
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java215
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java99
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java30
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt9
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt56
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt74
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java5
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt3
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacyTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt)0
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java12
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java2
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastSkipPolicy.java202
-rw-r--r--services/core/java/com/android/server/am/broadcasts_flags.aconfig11
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java6
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java26
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java4
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java5
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java196
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java133
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java52
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java46
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastSkipPolicyTest.java305
-rw-r--r--services/tests/wmtests/src/com/android/server/TransitionSubject.java79
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java8
-rw-r--r--telephony/java/android/telephony/euicc/EuiccManager.java9
-rw-r--r--tools/processors/view_inspector/OWNERS3
82 files changed, 1969 insertions, 773 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 3bd6451b4c95..248f191cb8b8 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3437,6 +3437,15 @@ public class AppOpsManager {
}
/**
+ * Returns whether the provided {@code op} is a valid op code or not.
+ *
+ * @hide
+ */
+ public static boolean isValidOp(int op) {
+ return op >= 0 && op < sAppOpInfos.length;
+ }
+
+ /**
* @hide
*/
public static int strDebugOpToOp(String op) {
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 67ade79e1b94..0085e4f42397 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -143,3 +143,10 @@ flag {
is_fixed_read_only: true
bug: "370928384"
}
+
+flag {
+ name: "device_aware_settings_override"
+ namespace: "virtual_devices"
+ description: "Settings override for virtual devices"
+ bug: "371801645"
+}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 953ee08800cf..5b5360e1ff01 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -485,6 +485,9 @@ public final class ContextHubManager {
/**
* Returns the list of ContextHubInfo objects describing the available Context Hubs.
*
+ * To find the list of hubs that include all Hubs (including both Context Hubs and Vendor Hubs),
+ * use the {@link #getHubs()} method instead.
+ *
* @return the list of ContextHubInfo objects
*
* @see ContextHubInfo
@@ -499,8 +502,8 @@ public final class ContextHubManager {
}
/**
- * Returns the list of HubInfo objects describing the available hubs (including ContextHub and
- * VendorHub). This method is primarily used for debugging purposes as most clients care about
+ * Returns the list of HubInfo objects describing the available hubs (including Context Hubs and
+ * Vendor Hubs). This method is primarily used for debugging purposes as most clients care about
* endpoints and services more than hubs.
*
* @return the list of HubInfo objects
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 24647f459ab5..196ae5e59fa7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -625,6 +625,12 @@ public interface WindowManager extends ViewManager {
int TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH = (1 << 14); // 0x4000
/**
+ * Transition flag: Indicates that aod is showing hidden by entering doze
+ * @hide
+ */
+ int TRANSIT_FLAG_AOD_APPEARING = (1 << 15); // 0x8000
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
@@ -643,6 +649,7 @@ public interface WindowManager extends ViewManager {
TRANSIT_FLAG_KEYGUARD_OCCLUDING,
TRANSIT_FLAG_KEYGUARD_UNOCCLUDING,
TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH,
+ TRANSIT_FLAG_AOD_APPEARING,
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionFlags {}
@@ -659,7 +666,8 @@ public interface WindowManager extends ViewManager {
(TRANSIT_FLAG_KEYGUARD_GOING_AWAY
| TRANSIT_FLAG_KEYGUARD_APPEARING
| TRANSIT_FLAG_KEYGUARD_OCCLUDING
- | TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
+ | TRANSIT_FLAG_KEYGUARD_UNOCCLUDING
+ | TRANSIT_FLAG_AOD_APPEARING);
/**
* Remove content mode: Indicates remove content mode is currently not defined.
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 49a11cab1de9..80a9cbc2f859 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -235,6 +235,14 @@ flag {
}
flag {
+ name: "request_rectangle_with_source"
+ namespace: "accessibility"
+ description: "Request rectangle on screen with source parameter"
+ bug: "391877896"
+ is_exported: true
+}
+
+flag {
name: "restore_a11y_secure_settings_on_hsum_device"
namespace: "accessibility"
description: "Grab the a11y settings and send the settings restored broadcast for current visible foreground user"
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 118edc29f378..fa7b74f37ed0 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -242,7 +242,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
@Override
public void onNullBinding(ComponentName name) {
- enqueueDeferredUnbindServiceMessage();
+ unbindNow();
}
@Override
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
index 69c04807c604..7ee22f30ace0 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
@@ -157,7 +157,7 @@ public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable
@Override
public void writeToParcel(Parcel dest, int flags) {
- sForInternedString.parcel(this.name, dest, flags);
+ dest.writeString(this.name);
dest.writeInt(this.getIcon());
dest.writeInt(this.getLabelRes());
dest.writeCharSequence(this.getNonLocalizedLabel());
@@ -175,7 +175,7 @@ public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable
// We use the boot classloader for all classes that we load.
final ClassLoader boot = Object.class.getClassLoader();
//noinspection ConstantConditions
- this.name = sForInternedString.unparcel(in);
+ this.name = in.readString();
this.icon = in.readInt();
this.labelRes = in.readInt();
this.nonLocalizedLabel = in.readCharSequence();
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 7018ebcbe9f4..5a180d7358dd 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -82,7 +82,7 @@ oneway interface IStatusBar
* Notify system UI the immersive mode changed. This shall be removed when client immersive is
* enabled.
*/
- void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode);
+ void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode, int windowType);
void dismissKeyboardShortcutsMenu();
void toggleKeyboardShortcutsMenu(int deviceId);
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 7b5831376dc0..14c15210252a 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -19,7 +19,9 @@ package com.android.wm.shell.bubbles.bar
import android.animation.AnimatorTestRule
import android.content.Context
import android.content.pm.LauncherApps
+import android.graphics.Insets
import android.graphics.PointF
+import android.graphics.Rect
import android.os.Handler
import android.os.UserManager
import android.view.IWindowManager
@@ -61,6 +63,7 @@ import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -80,6 +83,10 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class BubbleBarLayerViewTest {
+ companion object {
+ const val SCREEN_WIDTH = 2000
+ const val SCREEN_HEIGHT = 1000
+ }
@get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
@@ -111,6 +118,16 @@ class BubbleBarLayerViewTest {
bubblePositioner = BubblePositioner(context, windowManager)
bubblePositioner.setShowingInBubbleBar(true)
+ val deviceConfig =
+ DeviceConfig(
+ windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
+ isLargeScreen = true,
+ isSmallTablet = false,
+ isLandscape = true,
+ isRtl = false,
+ insets = Insets.of(10, 20, 30, 40)
+ )
+ bubblePositioner.update(deviceConfig)
testBubblesList = mutableListOf()
val bubbleData = mock<BubbleData>()
@@ -313,6 +330,48 @@ class BubbleBarLayerViewTest {
assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
}
+ @Test
+ fun testUpdateExpandedView_updateLocation() {
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+ val bubble = createBubble("first")
+
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.showExpandedView(bubble)
+ }
+ waitForExpandedViewAnimation()
+
+ val previousX = bubble.bubbleBarExpandedView!!.x
+
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.updateExpandedView()
+ }
+
+ assertThat(bubble.bubbleBarExpandedView!!.x).isNotEqualTo(previousX)
+ }
+
+ @Test
+ fun testUpdatedExpandedView_updateLocation_skipWhileAnimating() {
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+ val bubble = createBubble("first")
+
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.showExpandedView(bubble)
+ }
+ waitForExpandedViewAnimation()
+
+ val previousX = bubble.bubbleBarExpandedView!!.x
+ bubble.bubbleBarExpandedView!!.isAnimating = true
+
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.updateExpandedView()
+ }
+
+ // Expanded view is not updated while animating
+ assertThat(bubble.bubbleBarExpandedView!!.x).isEqualTo(previousX)
+ }
+
private fun createBubble(key: String): Bubble {
val bubbleTaskView = FakeBubbleTaskViewFactory(context, mainExecutor).create()
val bubbleBarExpandedView =
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 00c446c3da60..7acad5054e98 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -374,7 +374,7 @@ public class DesktopModeStatus {
* of the display's root [TaskDisplayArea] is set to WINDOWING_MODE_FREEFORM.
*/
public static boolean enterDesktopByDefaultOnFreeformDisplay(@NonNull Context context) {
- if (!Flags.enterDesktopByDefaultOnFreeformDisplays()) {
+ if (!DesktopExperienceFlags.ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS.isTrue()) {
return false;
}
return SystemProperties.getBoolean(ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP,
@@ -387,7 +387,7 @@ public class DesktopModeStatus {
* screen.
*/
public static boolean shouldMaximizeWhenDragToTopEdge(@NonNull Context context) {
- if (!Flags.enableDragToMaximize()) {
+ if (!DesktopExperienceFlags.ENABLE_DRAG_TO_MAXIMIZE.isTrue()) {
return false;
}
return SystemProperties.getBoolean(ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 53551387230c..26c362611518 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -147,10 +147,9 @@ class ActivityEmbeddingAnimationAdapter {
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
// Update the surface position and alpha.
- if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
- && mAnimation.getExtensionEdges() != 0x0
+ if (mAnimation.getExtensionEdges() != 0x0
&& !(mChange.hasFlags(FLAG_TRANSLUCENT)
- && mChange.getActivityComponent() != null)) {
+ && mChange.getActivityComponent() != null)) {
// Extend non-translucent activities
t.setEdgeExtensionEffect(mLeash, mAnimation.getExtensionEdges());
}
@@ -189,8 +188,7 @@ class ActivityEmbeddingAnimationAdapter {
@CallSuper
void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
onAnimationUpdate(t, mAnimation.getDuration());
- if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
- && mAnimation.getExtensionEdges() != 0x0) {
+ if (mAnimation.getExtensionEdges() != 0x0) {
t.setEdgeExtensionEffect(mLeash, /* edge */ 0);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index c3e783ddf4f1..85b7ac27daa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -20,11 +20,9 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
-import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
-import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
@@ -143,10 +141,6 @@ class ActivityEmbeddingAnimationRunner {
// ending states.
prepareForJumpCut(info, startTransaction);
} else {
- if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
- addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
- postStartTransactionCallbacks, adapters);
- }
addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
duration = Math.max(duration, adapter.getDurationHint());
@@ -329,34 +323,6 @@ class ActivityEmbeddingAnimationRunner {
}
}
- /** Adds edge extension to the surfaces that have such an animation property. */
- private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks,
- @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
- for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
- final Animation animation = adapter.mAnimation;
- if (animation.getExtensionEdges() == 0) {
- continue;
- }
- if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)
- && adapter.mChange.getActivityComponent() != null) {
- // Skip edge extension for translucent activity.
- continue;
- }
- final TransitionInfo.Change change = adapter.mChange;
- if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) {
- // Need to screenshot after startTransaction is applied otherwise activity
- // may not be visible or ready yet.
- postStartTransactionCallbacks.add(
- t -> edgeExtendWindow(change, animation, t, finishTransaction));
- } else {
- // Can screenshot now (before startTransaction is applied)
- edgeExtendWindow(change, animation, startTransaction, finishTransaction);
- }
- }
- }
-
/** Adds background color to the transition if any animation has such a property. */
private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 29837dc04423..677c21c96f4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -473,7 +473,7 @@ public class BubbleBarLayerView extends FrameLayout
/** Updates the expanded view size and position. */
public void updateExpandedView() {
- if (mExpandedView == null || mExpandedBubble == null) return;
+ if (mExpandedView == null || mExpandedBubble == null || mExpandedView.isAnimating()) return;
boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
mPositioner.getBubbleBarExpandedViewBounds(mPositioner.isBubbleBarOnLeft(),
isOverflowExpanded, mTempRect);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 59acdc574434..48fadc02ff1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -100,6 +100,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
+import com.android.wm.shell.desktopmode.DragToDisplayTransitionHandler;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.OverviewToDesktopTransitionObserver;
@@ -770,7 +771,8 @@ public abstract class WMShellModule {
DesksOrganizer desksOrganizer,
DesksTransitionObserver desksTransitionObserver,
UserProfileContexts userProfileContexts,
- DesktopModeCompatPolicy desktopModeCompatPolicy) {
+ DesktopModeCompatPolicy desktopModeCompatPolicy,
+ DragToDisplayTransitionHandler dragToDisplayTransitionHandler) {
return new DesktopTasksController(
context,
shellInit,
@@ -808,7 +810,8 @@ public abstract class WMShellModule {
desksOrganizer,
desksTransitionObserver,
userProfileContexts,
- desktopModeCompatPolicy);
+ desktopModeCompatPolicy,
+ dragToDisplayTransitionHandler);
}
@WMSingleton
@@ -934,6 +937,12 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static DragToDisplayTransitionHandler provideDragToDisplayTransitionHandler() {
+ return new DragToDisplayTransitionHandler();
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopModeKeyGestureHandler> provideDesktopModeKeyGestureHandler(
Context context,
Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
index c9a63ff818f5..e89aafe267ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
@@ -27,9 +27,9 @@ import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERN
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.DesktopExperienceFlags
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
@@ -47,31 +47,9 @@ class DesktopDisplayModeController(
) {
fun refreshDisplayWindowingMode() {
- if (!Flags.enableDisplayWindowingModeSwitching()) return
- // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
- val isExtendedDisplayEnabled =
- 0 !=
- Settings.Global.getInt(
- context.contentResolver,
- DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
- 0,
- )
- if (!isExtendedDisplayEnabled) {
- // No action needed in mirror or projected mode.
- return
- }
+ if (!DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) return
- val hasNonDefaultDisplay =
- rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId ->
- displayId != DEFAULT_DISPLAY
- }
- val targetDisplayWindowingMode =
- if (hasNonDefaultDisplay) {
- WINDOWING_MODE_FREEFORM
- } else {
- // Use the default display windowing mode when no non-default display.
- windowManager.getWindowingMode(DEFAULT_DISPLAY)
- }
+ val targetDisplayWindowingMode = getTargetWindowingModeForDefaultDisplay()
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
@@ -111,6 +89,25 @@ class DesktopDisplayModeController(
transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
}
+ private fun getTargetWindowingModeForDefaultDisplay(): Int {
+ if (isExtendedDisplayEnabled() && hasExternalDisplay()) {
+ return WINDOWING_MODE_FREEFORM
+ }
+ return windowManager.getWindowingMode(DEFAULT_DISPLAY)
+ }
+
+ // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
+ private fun isExtendedDisplayEnabled() =
+ 0 !=
+ Settings.Global.getInt(
+ context.contentResolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+ 0,
+ )
+
+ private fun hasExternalDisplay() =
+ rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY }
+
private fun logV(msg: String, vararg arguments: Any?) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 04e609ec3820..03423ba3b96a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -1007,6 +1007,21 @@ class DesktopRepository(
fun saveBoundsBeforeFullImmersive(taskId: Int, bounds: Rect) =
boundsBeforeFullImmersiveByTaskId.set(taskId, Rect(bounds))
+ /** Returns the current state of the desktop, formatted for usage by remote clients. */
+ fun getDeskDisplayStateForRemote(): Array<DisplayDeskState> =
+ desktopData
+ .desksSequence()
+ .groupBy { it.displayId }
+ .map { (displayId, desks) ->
+ val activeDeskId = desktopData.getActiveDesk(displayId)?.deskId
+ DisplayDeskState().apply {
+ this.displayId = displayId
+ this.activeDeskId = activeDeskId ?: INVALID_DESK_ID
+ this.deskIds = desks.map { it.deskId }.toIntArray()
+ }
+ }
+ .toTypedArray()
+
/** TODO: b/389960283 - consider updating only the changing desks. */
private fun updatePersistentRepository(displayId: Int) {
val desks = desktopData.desksSequence(displayId).map { desk -> desk.deepCopy() }.toList()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 180d069f359d..fca5084b65bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -205,6 +205,7 @@ class DesktopTasksController(
private val desksTransitionObserver: DesksTransitionObserver,
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
+ private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
@@ -811,7 +812,7 @@ class DesktopTasksController(
willExitDesktop(
triggerTaskId = taskInfo.taskId,
displayId = displayId,
- forceToFullscreen = false,
+ forceExitDesktop = false,
)
taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
val desktopExitRunnable =
@@ -884,7 +885,7 @@ class DesktopTasksController(
snapEventHandler.removeTaskIfTiled(displayId, taskId)
taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
- val willExitDesktop = willExitDesktop(taskId, displayId, forceToFullscreen = false)
+ val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
val desktopExitRunnable =
performDesktopExitCleanUp(
wct = wct,
@@ -977,7 +978,7 @@ class DesktopTasksController(
) {
logV("moveToFullscreenWithAnimation taskId=%d", task.taskId)
val wct = WindowContainerTransaction()
- val willExitDesktop = willExitDesktop(task.taskId, task.displayId, forceToFullscreen = true)
+ val willExitDesktop = willExitDesktop(task.taskId, task.displayId, forceExitDesktop = true)
val deactivationRunnable = addMoveToFullscreenChanges(wct, task, willExitDesktop)
// We are moving a freeform task to fullscreen, put the home task under the fullscreen task.
@@ -996,7 +997,14 @@ class DesktopTasksController(
deactivationRunnable?.invoke(transition)
// handles case where we are moving to full screen without closing all DW tasks.
- if (!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)) {
+ if (
+ !taskRepository.isOnlyVisibleNonClosingTask(task.taskId)
+ // This callback is already invoked by |addMoveToFullscreenChanges| when one of these
+ // flags is enabled.
+ &&
+ !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue &&
+ !Flags.enableDesktopWindowingPip()
+ ) {
desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
FULLSCREEN_ANIMATION_DURATION
)
@@ -1893,16 +1901,24 @@ class DesktopTasksController(
private fun willExitDesktop(
triggerTaskId: Int,
displayId: Int,
- forceToFullscreen: Boolean,
+ forceExitDesktop: Boolean,
): Boolean {
+ if (
+ forceExitDesktop &&
+ (Flags.enableDesktopWindowingPip() ||
+ DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue)
+ ) {
+ // |forceExitDesktop| is true when the callers knows we'll exit desktop, such as when
+ // explicitly going fullscreen, so there's no point in checking the desktop state.
+ return true
+ }
if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
if (!taskRepository.isOnlyVisibleNonClosingTask(triggerTaskId, displayId)) {
return false
}
} else if (
Flags.enableDesktopWindowingPip() &&
- taskRepository.isMinimizedPipPresentInDisplay(displayId) &&
- !forceToFullscreen
+ taskRepository.isMinimizedPipPresentInDisplay(displayId)
) {
return false
} else {
@@ -2295,7 +2311,7 @@ class DesktopTasksController(
willExitDesktop(
triggerTaskId = task.taskId,
displayId = task.displayId,
- forceToFullscreen = true,
+ forceExitDesktop = true,
),
)
wct.reorder(task.token, true)
@@ -2328,7 +2344,7 @@ class DesktopTasksController(
willExitDesktop(
triggerTaskId = task.taskId,
displayId = task.displayId,
- forceToFullscreen = true,
+ forceExitDesktop = true,
),
)
return wct
@@ -2433,7 +2449,7 @@ class DesktopTasksController(
willExitDesktop(
triggerTaskId = task.taskId,
displayId = task.displayId,
- forceToFullscreen = true,
+ forceExitDesktop = true,
),
)
}
@@ -2471,7 +2487,7 @@ class DesktopTasksController(
willExitDesktop(
triggerTaskId = task.taskId,
displayId = task.displayId,
- forceToFullscreen = true,
+ forceExitDesktop = true,
),
)
}
@@ -3173,25 +3189,24 @@ class DesktopTasksController(
val wct = WindowContainerTransaction()
wct.setBounds(taskInfo.token, destinationBounds)
- // TODO: b/362720497 - reparent to a specific desk within the target display.
- // Reparent task if it has been moved to a new display.
- if (Flags.enableConnectedDisplaysWindowDrag()) {
- val newDisplayId = motionEvent.getDisplayId()
- if (newDisplayId != taskInfo.getDisplayId()) {
- val displayAreaInfo =
- rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId)
- if (displayAreaInfo == null) {
- logW(
- "Task reparent cannot find DisplayAreaInfo for displayId=%d",
- newDisplayId,
- )
- } else {
- wct.reparent(taskInfo.token, displayAreaInfo.token, /* onTop= */ true)
- }
+ val newDisplayId = motionEvent.getDisplayId()
+ val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId)
+ val isCrossDisplayDrag =
+ Flags.enableConnectedDisplaysWindowDrag() &&
+ newDisplayId != taskInfo.getDisplayId() &&
+ displayAreaInfo != null
+ val handler =
+ if (isCrossDisplayDrag) {
+ dragToDisplayTransitionHandler
+ } else {
+ null
}
+ if (isCrossDisplayDrag) {
+ // TODO: b/362720497 - reparent to a specific desk within the target display.
+ wct.reparent(taskInfo.token, displayAreaInfo.token, /* onTop= */ true)
}
- transitions.startTransition(TRANSIT_CHANGE, wct, null)
+ transitions.startTransition(TRANSIT_CHANGE, wct, handler)
releaseVisualIndicator()
}
@@ -3613,27 +3628,11 @@ class DesktopTasksController(
controller,
{ c ->
run {
- c.taskRepository.addDeskChangeListener(
- deskChangeListener,
- c.mainExecutor,
- )
- c.taskRepository.addVisibleTasksListener(
- visibleTasksListener,
- c.mainExecutor,
- )
- c.taskbarDesktopTaskListener = taskbarDesktopTaskListener
- c.desktopModeEnterExitTransitionListener =
- desktopModeEntryExitTransitionListener
- }
- },
- { c ->
- run {
- c.taskRepository.removeDeskChangeListener(deskChangeListener)
- c.taskRepository.removeVisibleTasksListener(visibleTasksListener)
- c.taskbarDesktopTaskListener = null
- c.desktopModeEnterExitTransitionListener = null
+ syncInitialState(c)
+ registerListeners(c)
}
},
+ { c -> run { unregisterListeners(c) } },
)
}
@@ -3729,6 +3728,31 @@ class DesktopTasksController(
c.startLaunchIntentTransition(intent, options, displayId)
}
}
+
+ private fun syncInitialState(c: DesktopTasksController) {
+ remoteListener.call { l ->
+ // TODO: b/393962589 - implement desks limit.
+ val canCreateDesks = true
+ l.onListenerConnected(
+ c.taskRepository.getDeskDisplayStateForRemote(),
+ canCreateDesks,
+ )
+ }
+ }
+
+ private fun registerListeners(c: DesktopTasksController) {
+ c.taskRepository.addDeskChangeListener(deskChangeListener, c.mainExecutor)
+ c.taskRepository.addVisibleTasksListener(visibleTasksListener, c.mainExecutor)
+ c.taskbarDesktopTaskListener = taskbarDesktopTaskListener
+ c.desktopModeEnterExitTransitionListener = desktopModeEntryExitTransitionListener
+ }
+
+ private fun unregisterListeners(c: DesktopTasksController) {
+ c.taskRepository.removeDeskChangeListener(deskChangeListener)
+ c.taskRepository.removeVisibleTasksListener(visibleTasksListener)
+ c.taskbarDesktopTaskListener = null
+ c.desktopModeEnterExitTransitionListener = null
+ }
}
private fun logV(msg: String, vararg arguments: Any?) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandler.kt
new file mode 100644
index 000000000000..d51576a5148e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandler.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode
+
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.transition.Transitions
+
+/** Handles the transition to drag a window to another display by dragging the caption. */
+class DragToDisplayTransitionHandler : Transitions.TransitionHandler {
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? {
+ return null
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ for (change in info.changes) {
+ val sc = change.leash
+ val endBounds = change.endAbsBounds
+ val endPosition = change.endRelOffset
+ startTransaction
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+ finishTransaction
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+ }
+
+ startTransaction.apply()
+ finishCallback.onTransitionFinished(null)
+ return true
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index e9c6adec75d7..3652a1661f28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -67,7 +67,6 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
import static com.android.wm.shell.transition.DefaultSurfaceAnimator.buildSurfaceAnimation;
-import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
import static com.android.wm.shell.transition.TransitionAnimationHelper.isCoveredByOpaqueFullscreenChange;
@@ -543,21 +542,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
backgroundColorForTransition);
if (!isTask && a.getExtensionEdges() != 0x0) {
- if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
- startTransaction.setEdgeExtensionEffect(
- change.getLeash(), a.getExtensionEdges());
- finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0);
- } else {
- if (!TransitionUtil.isOpeningType(mode)) {
- // Can screenshot now (before startTransaction is applied)
- edgeExtendWindow(change, a, startTransaction, finishTransaction);
- } else {
- // Need to screenshot after startTransaction is applied otherwise
- // activity may not be visible or ready yet.
- postStartTransactionCallbacks
- .add(t -> edgeExtendWindow(change, a, t, finishTransaction));
- }
- }
+ startTransaction.setEdgeExtensionEffect(
+ change.getLeash(), a.getExtensionEdges());
+ finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0);
}
final Rect clipRect = TransitionUtil.isClosingType(mode)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 7984bcedc4e5..edfb56019a60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -26,7 +26,6 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
-import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
@@ -39,20 +38,10 @@ import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.animation.Animation;
-import android.view.animation.Transformation;
-import android.window.ScreenCapture;
import android.window.TransitionInfo;
import com.android.internal.R;
@@ -317,129 +306,6 @@ public class TransitionAnimationHelper {
}
/**
- * Adds edge extension surface to the given {@code change} for edge extension animation.
- */
- public static void edgeExtendWindow(@NonNull TransitionInfo.Change change,
- @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction) {
- // Do not create edge extension surface for transfer starting window change.
- // The app surface could be empty thus nothing can draw on the hardware renderer, which will
- // block this thread when calling Surface#unlockCanvasAndPost.
- if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- return;
- }
- final Transformation transformationAtStart = new Transformation();
- a.getTransformationAt(0, transformationAtStart);
- final Transformation transformationAtEnd = new Transformation();
- a.getTransformationAt(1, transformationAtEnd);
-
- // We want to create an extension surface that is the maximal size and the animation will
- // take care of cropping any part that overflows.
- final Insets maxExtensionInsets = Insets.min(
- transformationAtStart.getInsets(), transformationAtEnd.getInsets());
-
- final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
- change.getEndAbsBounds().height());
- final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
- change.getEndAbsBounds().width());
- if (maxExtensionInsets.left < 0) {
- final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.left, targetSurfaceHeight);
- final int xPos = maxExtensionInsets.left;
- final int yPos = 0;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Left Edge Extension", startTransaction, finishTransaction);
- }
-
- if (maxExtensionInsets.top < 0) {
- final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.top);
- final int xPos = 0;
- final int yPos = maxExtensionInsets.top;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Top Edge Extension", startTransaction, finishTransaction);
- }
-
- if (maxExtensionInsets.right < 0) {
- final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
- targetSurfaceWidth, targetSurfaceHeight);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.right, targetSurfaceHeight);
- final int xPos = targetSurfaceWidth;
- final int yPos = 0;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Right Edge Extension", startTransaction, finishTransaction);
- }
-
- if (maxExtensionInsets.bottom < 0) {
- final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
- targetSurfaceWidth, targetSurfaceHeight);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.bottom);
- final int xPos = maxExtensionInsets.left;
- final int yPos = targetSurfaceHeight;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Bottom Edge Extension", startTransaction, finishTransaction);
- }
- }
-
- /**
- * Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension
- * animation.
- */
- private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend,
- @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos,
- @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction) {
- final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
- .setName(layerName)
- .setParent(surfaceToExtend)
- .setHidden(true)
- .setCallsite("TransitionAnimationHelper#createExtensionSurface")
- .setOpaque(true)
- .setBufferSize(extensionRect.width(), extensionRect.height())
- .build();
-
- final ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend)
- .setSourceCrop(edgeBounds)
- .setFrameScale(1)
- .setPixelFormat(PixelFormat.RGBA_8888)
- .setChildrenOnly(true)
- .setAllowProtected(false)
- .setCaptureSecureLayers(true)
- .build();
- final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
- ScreenCapture.captureLayers(captureArgs);
-
- if (edgeBuffer == null) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Failed to capture edge of window.");
- return null;
- }
-
- final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
- Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
- final Paint paint = new Paint();
- paint.setShader(shader);
-
- final Surface surface = new Surface(edgeExtensionLayer);
- final Canvas c = surface.lockHardwareCanvas();
- c.drawRect(extensionRect, paint);
- surface.unlockCanvasAndPost(c);
- surface.release();
-
- startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
- startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
- startTransaction.setVisibility(edgeExtensionLayer, true);
- finishTransaction.remove(edgeExtensionLayer);
-
- return edgeExtensionLayer;
- }
-
- /**
* Returns whether there is an opaque fullscreen Change positioned in front of the given Change
* in the given TransitionInfo.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index c6cb62d153ac..1b0e0f70ed21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -363,10 +363,11 @@ class MultiDisplayVeiledResizeTaskPositioner(
dragEventListeners.remove(dragEventListener)
}
- override fun onTopologyChanged(topology: DisplayTopology) {
+ override fun onTopologyChanged(topology: DisplayTopology?) {
// TODO: b/383069173 - Cancel window drag when topology changes happen during drag.
displayIds.clear()
+ if (topology == null) return
val displayBounds = topology.getAbsoluteBounds()
displayIds.addAll(List(displayBounds.size()) { displayBounds.keyAt(it) })
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index 28008393da84..d82c06691e46 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -18,9 +18,9 @@ package com.android.wm.shell.scenarios
import android.tools.NavBar
import android.tools.Rotation
-import com.android.internal.R
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import org.junit.After
import org.junit.Assume
import org.junit.Before
@@ -42,8 +42,8 @@ constructor(
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
// Skip the test when the drag-to-maximize is enabled on this device.
- Assume.assumeFalse(Flags.enableDragToMaximize() &&
- instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
+ Assume.assumeFalse(
+ DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(instrumentation.context))
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
testApp.enterDesktopMode(wmHelper, device)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
index 60a0fb547909..675b63cf56bb 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
@@ -23,12 +23,12 @@ import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
-import com.android.internal.R
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import org.junit.After
import org.junit.Assume
import org.junit.Before
@@ -54,8 +54,8 @@ constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
// Skip the test when the drag-to-maximize is disabled on this device.
- Assume.assumeTrue(Flags.enableDragToMaximize() &&
- instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
+ Assume.assumeTrue(
+ DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(instrumentation.context))
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
ChangeDisplayOrientationRule.setRotation(rotation)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt
index 81c46f13b384..b9a5e4a95e36 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt
@@ -25,6 +25,7 @@ import android.tools.Rotation
import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import android.util.DisplayMetrics
+import android.window.DesktopExperienceFlags
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
@@ -64,7 +65,7 @@ constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- Assume.assumeTrue(Flags.enableDisplayWindowingModeSwitching())
+ Assume.assumeTrue(DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue)
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
ChangeDisplayOrientationRule.setRotation(rotation)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
index 0ff7230f6e0c..f0c97d359a16 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
@@ -101,7 +101,7 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
private fun testDisplayWindowingModeSwitch(
defaultWindowingMode: Int,
extendedDisplayEnabled: Boolean,
- expectTransition: Boolean,
+ expectToSwitch: Boolean,
) {
defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(defaultWindowingMode)
@@ -113,10 +113,14 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
settingsSession.use {
connectExternalDisplay()
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ if (expectToSwitch) {
+ // Assumes [connectExternalDisplay] properly triggered the switching transition.
+ // Will verify the transition later along with [disconnectExternalDisplay].
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ }
disconnectExternalDisplay()
- if (expectTransition) {
+ if (expectToSwitch) {
val arg = argumentCaptor<WindowContainerTransaction>()
verify(transitions, times(2))
.startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
@@ -139,7 +143,7 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
testDisplayWindowingModeSwitch(
defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
extendedDisplayEnabled = false,
- expectTransition = false,
+ expectToSwitch = false,
)
}
@@ -148,7 +152,7 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
testDisplayWindowingModeSwitch(
defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
extendedDisplayEnabled = true,
- expectTransition = true,
+ expectToSwitch = true,
)
}
@@ -157,7 +161,7 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
testDisplayWindowingModeSwitch(
defaultWindowingMode = WINDOWING_MODE_FREEFORM,
extendedDisplayEnabled = true,
- expectTransition = false,
+ expectToSwitch = false,
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index ac1deec53bf6..04acaef344eb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -261,6 +261,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var mockDisplayContext: Context
+ @Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -431,6 +432,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
desksTransitionsObserver,
userProfileContexts,
desktopModeCompatPolicy,
+ dragToDisplayTransitionHandler,
)
@After
@@ -2069,6 +2071,21 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToFullscreen_fromDeskWithMultipleTasks_deactivatesDesk() {
+ val deskId = 1
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+
+ controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ verify(desksOrganizer).deactivateDesk(wct, deskId = deskId)
+ }
+
+ @Test
fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -2278,7 +2295,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ )
fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
@@ -2305,29 +2325,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity_multiDesksEnabled() {
- val homeTask = setUpHomeTask()
- val task1 = setUpFreeformTask()
- // Setup task2
- setUpFreeformTask()
-
- val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
- assertNotNull(tdaInfo).configuration.windowConfiguration.windowingMode =
- WINDOWING_MODE_FULLSCREEN
-
- controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- val task1Change = assertNotNull(wct.changes[task1.token.asBinder()])
- assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
- verify(desktopModeEnterExitTransitionListener)
- .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
- // Does not remove wallpaper activity, as desktop still has a visible desktop task
- wct.assertWithoutHop(ReorderPredicate(wallpaperToken, toTop = false))
- }
-
- @Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
controller.moveToFullscreen(999, transitionSource = UNKNOWN)
verifyExitDesktopWCTNotExecuted()
@@ -4455,7 +4452,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ )
fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
@@ -4480,27 +4480,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity_multiDesksEnabled() {
- val homeTask = setUpHomeTask()
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
-
- task1.isFocused = false
- task2.isFocused = true
- task3.isFocused = false
- controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
- assertThat(taskChange.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- // Does not remove wallpaper activity
- wct.assertWithoutHop(ReorderPredicate(wallpaperToken, toTop = null))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveFocusedTaskToFullscreen_multipleVisibleTasks_fullscreenOverHome_multiDesksEnabled() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
@@ -5031,7 +5010,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Mockito.argThat { wct ->
return@argThat wct.hierarchyOps[0].isReparent
},
- eq(null),
+ eq(dragToDisplayTransitionHandler),
)
}
@@ -5225,6 +5204,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ )
fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandlerTest.kt
new file mode 100644
index 000000000000..51c302983fd0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandlerTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import com.android.wm.shell.transition.Transitions
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for {@link DragToDisplayTransitionHandler}
+ *
+ * Usage: atest WMShellUnitTests:DragToDisplayTransitionHandlerTest
+ */
+class DragToDisplayTransitionHandlerTest {
+ private lateinit var handler: DragToDisplayTransitionHandler
+ private val mockTransition: IBinder = mock()
+ private val mockRequestInfo: TransitionRequestInfo = mock()
+ private val mockTransitionInfo: TransitionInfo = mock()
+ private val mockStartTransaction: SurfaceControl.Transaction = mock()
+ private val mockFinishTransaction: SurfaceControl.Transaction = mock()
+ private val mockFinishCallback: Transitions.TransitionFinishCallback = mock()
+
+ @Before
+ fun setUp() {
+ handler = DragToDisplayTransitionHandler()
+ whenever(mockStartTransaction.setWindowCrop(any(), any(), any()))
+ .thenReturn(mockStartTransaction)
+ whenever(mockFinishTransaction.setWindowCrop(any(), any(), any()))
+ .thenReturn(mockFinishTransaction)
+ }
+
+ @Test
+ fun handleRequest_anyRequest_returnsNull() {
+ val result = handler.handleRequest(mockTransition, mockRequestInfo)
+ assert(result == null)
+ }
+
+ @Test
+ fun startAnimation_verifyTransformationsApplied() {
+ val mockChange1 = mock<TransitionInfo.Change>()
+ val leash1 = mock<SurfaceControl>()
+ val endBounds1 = Rect(0, 0, 50, 50)
+ val endPosition1 = Point(5, 5)
+
+ whenever(mockChange1.leash).doReturn(leash1)
+ whenever(mockChange1.endAbsBounds).doReturn(endBounds1)
+ whenever(mockChange1.endRelOffset).doReturn(endPosition1)
+
+ val mockChange2 = mock<TransitionInfo.Change>()
+ val leash2 = mock<SurfaceControl>()
+ val endBounds2 = Rect(100, 100, 200, 150)
+ val endPosition2 = Point(15, 25)
+
+ whenever(mockChange2.leash).doReturn(leash2)
+ whenever(mockChange2.endAbsBounds).doReturn(endBounds2)
+ whenever(mockChange2.endRelOffset).doReturn(endPosition2)
+
+ whenever(mockTransitionInfo.changes).doReturn(listOf(mockChange1, mockChange2))
+
+ handler.startAnimation(
+ mockTransition,
+ mockTransitionInfo,
+ mockStartTransaction,
+ mockFinishTransaction,
+ mockFinishCallback,
+ )
+
+ verify(mockStartTransaction).setWindowCrop(leash1, endBounds1.width(), endBounds1.height())
+ verify(mockStartTransaction)
+ .setPosition(leash1, endPosition1.x.toFloat(), endPosition1.y.toFloat())
+ verify(mockStartTransaction).setWindowCrop(leash2, endBounds2.width(), endBounds2.height())
+ verify(mockStartTransaction)
+ .setPosition(leash2, endPosition2.x.toFloat(), endPosition2.y.toFloat())
+ verify(mockStartTransaction).apply()
+ verify(mockFinishCallback).onTransitionFinished(null)
+ }
+}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index 200d4ef86146..6ae73a2e6fe5 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1484,6 +1484,10 @@ public final class GnssMeasurement implements Parcelable {
* in an open sky test - the important aspect of this output is that changes in this value are
* indicative of changes on input signal power in the frequency band for this measurement.
*
+ * <p> This field is part of the GnssMeasurement object so it is only reported when the GNSS
+ * measurement is reported. E.g., when a GNSS signal is too weak to be acquired, the AGC value
+ * is not reported.
+ *
* <p> The value is only available if {@link #hasAutomaticGainControlLevelDb()} is {@code true}
*
* @deprecated Use {@link GnssMeasurementsEvent#getGnssAutomaticGainControls()} instead.
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index 4fc2ee8b7fb0..8cdfd013130a 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -158,6 +158,14 @@ public final class GnssMeasurementsEvent implements Parcelable {
/**
* Gets the collection of {@link GnssAutomaticGainControl} associated with the
* current event.
+ *
+ * <p>This field must be reported when the GNSS measurement engine is running, even when the
+ * GnssMeasurement or GnssClock fields are not reported yet. E.g., when a GNSS signal is too
+ * weak to be acquired, the AGC value must still be reported.
+ *
+ * <p>For devices that do not support this field, an empty collection is returned. In that case,
+ * please use {@link GnssMeasurement#hasAutomaticGainControlLevelDb()}
+ * and {@link GnssMeasuremen#getAutomaticGainControlLevelDb()}.
*/
@NonNull
public Collection<GnssAutomaticGainControl> getGnssAutomaticGainControls() {
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index 8b6194fa66f5..fb89973bcc11 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -28,7 +28,6 @@ import android.telephony.emergency.EmergencyNumber;
import android.util.Log;
import com.android.internal.annotations.KeepForWeakReference;
-import com.android.internal.telephony.flags.Flags;
import java.util.concurrent.TimeUnit;
@@ -146,17 +145,12 @@ public class GpsNetInitiatedHandler {
< emergencyExtensionMillis);
boolean isInEmergencyCallback = false;
boolean isInEmergencySmsMode = false;
- if (!Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
+ }
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) {
isInEmergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
- } else {
- PackageManager pm = mContext.getPackageManager();
- if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
- isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
- }
- if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) {
- isInEmergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
- }
}
return mIsInEmergencyCall || isInEmergencyCallback || isInEmergencyExtension
|| isInEmergencySmsMode;
diff --git a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
index 9d037e91a86f..806580b10cc8 100644
--- a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
+++ b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
@@ -17,20 +17,20 @@
package com.android.settingslib.widget
import android.content.Context
-import android.os.Build
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
-import androidx.annotation.RequiresApi
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.android.settingslib.widget.preference.intro.R
-class IntroPreference @JvmOverloads constructor(
+class IntroPreference
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
+ defStyleRes: Int = 0,
) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
private var isCollapsable: Boolean = true
@@ -66,9 +66,9 @@ class IntroPreference @JvmOverloads constructor(
/**
* Sets whether the summary is collapsable.
+ *
* @param collapsable True if the summary should be collapsable, false otherwise.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setCollapsable(collapsable: Boolean) {
isCollapsable = collapsable
minLines = if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
@@ -77,9 +77,9 @@ class IntroPreference @JvmOverloads constructor(
/**
* Sets the minimum number of lines to display when collapsed.
+ *
* @param lines The minimum number of lines.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setMinLines(lines: Int) {
minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
notifyChanged()
@@ -87,9 +87,9 @@ class IntroPreference @JvmOverloads constructor(
/**
* Sets the action when clicking on the hyperlink in the text.
+ *
* @param listener The click listener for hyperlink.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setHyperlinkListener(listener: View.OnClickListener) {
if (hyperlinkListener != listener) {
hyperlinkListener = listener
@@ -99,9 +99,9 @@ class IntroPreference @JvmOverloads constructor(
/**
* Sets the action when clicking on the learn more view.
+ *
* @param listener The click listener for learn more.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setLearnMoreAction(listener: View.OnClickListener) {
if (learnMoreListener != listener) {
learnMoreListener = listener
@@ -111,9 +111,9 @@ class IntroPreference @JvmOverloads constructor(
/**
* Sets the text of learn more view.
+ *
* @param text The text of learn more.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setLearnMoreText(text: CharSequence) {
if (!TextUtils.equals(learnMoreText, text)) {
learnMoreText = text
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 1cb8005ddae0..02bef9fd2fb2 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -59,8 +59,6 @@ class PreferenceScreenBindingHelper(
private val preferenceHierarchy: PreferenceHierarchy,
) : KeyedDataObservable<String>() {
- private val mainExecutor = HandlerExecutor.main
-
private val preferenceLifecycleContext =
object : PreferenceLifecycleContext(context) {
override val lifecycleScope: LifecycleCoroutineScope
@@ -88,11 +86,11 @@ class PreferenceScreenBindingHelper(
private val preferences: ImmutableMap<String, PreferenceHierarchyNode>
private val dependencies: ImmutableMultimap<String, String>
private val lifecycleAwarePreferences: Array<PreferenceLifecycleProvider>
- private val storages = mutableMapOf<String, KeyedObservable<String>>()
+ private val observables = mutableMapOf<String, KeyedObservable<String>>()
private val preferenceObserver: KeyedObserver<String?>
- private val storageObserver =
+ private val observer =
KeyedObserver<String> { key, reason ->
if (DataChangeReason.isDataChange(reason)) {
notifyChange(key, PreferenceChangeReason.VALUE)
@@ -133,15 +131,19 @@ class PreferenceScreenBindingHelper(
this.dependencies = dependenciesBuilder.build()
this.lifecycleAwarePreferences = lifecycleAwarePreferences.toTypedArray()
+ val executor = HandlerExecutor.main
preferenceObserver = KeyedObserver { key, reason -> onPreferenceChange(key, reason) }
- addObserver(preferenceObserver, mainExecutor)
+ addObserver(preferenceObserver, executor)
preferenceScreen.forEachRecursively {
- it.preferenceDataStore?.findKeyValueStore()?.let { keyValueStore ->
- val key = it.key
- storages[key] = keyValueStore
- keyValueStore.addObserver(key, storageObserver, mainExecutor)
- }
+ val key = it.key ?: return@forEachRecursively
+ @Suppress("UNCHECKED_CAST")
+ val observable =
+ it.preferenceDataStore?.findKeyValueStore()
+ ?: (preferences[key]?.metadata as? KeyedObservable<String>)
+ ?: return@forEachRecursively
+ observables[key] = observable
+ observable.addObserver(key, observer, executor)
}
}
@@ -212,7 +214,7 @@ class PreferenceScreenBindingHelper(
fun onDestroy() {
removeObserver(preferenceObserver)
- for ((key, storage) in storages) storage.removeObserver(key, storageObserver)
+ for ((key, observable) in observables) observable.removeObserver(key, observer)
for (preference in lifecycleAwarePreferences) {
preference.onDestroy(preferenceLifecycleContext)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index ebd5a1deffd2..3625c002e9d8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -29,6 +29,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
+import android.os.UserManager;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -37,6 +38,8 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
+import com.android.settingslib.flags.Flags;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.Collection;
import java.util.HashMap;
@@ -65,6 +68,7 @@ public class BluetoothEventManager {
private final android.os.Handler mReceiverHandler;
private final UserHandle mUserHandle;
private final Context mContext;
+ private boolean mIsWorkProfile = false;
interface Handler {
void onReceive(Context context, Intent intent, BluetoothDevice device);
@@ -140,6 +144,9 @@ public class BluetoothEventManager {
addHandler(BluetoothAdapter.ACTION_AUTO_ON_STATE_CHANGED, new AutoOnStateChangedHandler());
registerAdapterIntentReceiver();
+
+ UserManager userManager = context.getSystemService(UserManager.class);
+ mIsWorkProfile = userManager != null && userManager.isManagedProfile();
}
/** Register to start receiving callbacks for Bluetooth events. */
@@ -220,20 +227,32 @@ public class BluetoothEventManager {
callback.onProfileConnectionStateChanged(device, state, bluetoothProfile);
}
+ if (mIsWorkProfile) {
+ Log.d(TAG, "Skip profileConnectionStateChanged for audio sharing, work profile");
+ return;
+ }
+
+ LocalBluetoothLeBroadcast broadcast = mBtManager == null ? null
+ : mBtManager.getProfileManager().getLeAudioBroadcastProfile();
+ LocalBluetoothLeBroadcastAssistant assistant = mBtManager == null ? null
+ : mBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
// Trigger updateFallbackActiveDeviceIfNeeded when ASSISTANT profile disconnected when
// audio sharing is enabled.
if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
&& state == BluetoothAdapter.STATE_DISCONNECTED
- && BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
- LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
- if (profileManager != null
- && profileManager.getLeAudioBroadcastProfile() != null
- && profileManager.getLeAudioBroadcastProfile().isProfileReady()
- && profileManager.getLeAudioBroadcastAssistantProfile() != null
- && profileManager.getLeAudioBroadcastAssistantProfile().isProfileReady()) {
- Log.d(TAG, "updateFallbackActiveDeviceIfNeeded, ASSISTANT profile disconnected");
- profileManager.getLeAudioBroadcastProfile().updateFallbackActiveDeviceIfNeeded();
- }
+ && BluetoothUtils.isAudioSharingUIAvailable(mContext)
+ && broadcast != null && assistant != null && broadcast.isProfileReady()
+ && assistant.isProfileReady()) {
+ Log.d(TAG, "updateFallbackActiveDeviceIfNeeded, ASSISTANT profile disconnected");
+ broadcast.updateFallbackActiveDeviceIfNeeded();
+ }
+ // Dispatch handleOnProfileStateChanged to local broadcast profile
+ if (Flags.promoteAudioSharingForSecondAutoConnectedLeaDevice()
+ && broadcast != null
+ && state == BluetoothAdapter.STATE_CONNECTED) {
+ Log.d(TAG, "dispatchProfileConnectionStateChanged to local broadcast profile");
+ var unused = ThreadUtils.postOnBackgroundThread(
+ () -> broadcast.handleProfileConnected(device, bluetoothProfile, mBtManager));
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 31948e49b4ce..e78a69239334 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -719,6 +719,30 @@ public class BluetoothUtils {
}
}
+ /** Check if the {@link CachedBluetoothDevice} is a media device */
+ @WorkerThread
+ public static boolean isMediaDevice(@Nullable CachedBluetoothDevice cachedDevice) {
+ if (cachedDevice == null) return false;
+ return cachedDevice.getProfiles().stream()
+ .anyMatch(
+ profile ->
+ profile instanceof A2dpProfile
+ || profile instanceof HearingAidProfile
+ || profile instanceof LeAudioProfile
+ || profile instanceof HeadsetProfile);
+ }
+
+ /** Check if the {@link CachedBluetoothDevice} supports LE Audio profile */
+ @WorkerThread
+ public static boolean isLeAudioSupported(@Nullable CachedBluetoothDevice cachedDevice) {
+ if (cachedDevice == null) return false;
+ return cachedDevice.getProfiles().stream()
+ .anyMatch(
+ profile ->
+ profile instanceof LeAudioProfile
+ && profile.isEnabled(cachedDevice.getDevice()));
+ }
+
/** Returns if the broadcast is on-going. */
@WorkerThread
public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index f18a2da27a70..08f7806207db 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -54,6 +54,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.WorkerThread;
import com.android.settingslib.R;
import com.android.settingslib.flags.Flags;
@@ -64,6 +65,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -107,6 +109,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
private static final String SETTINGS_PKG = "com.android.settings";
private static final String SYSUI_PKG = "com.android.systemui";
private static final String TAG = "LocalBluetoothLeBroadcast";
+ private static final String AUTO_REJOIN_BROADCAST_TAG = "REJOIN_LE_BROADCAST_ID";
private static final boolean DEBUG = BluetoothUtils.D;
private static final String VALID_PASSWORD_CHARACTERS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,"
@@ -120,6 +123,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
// Order of this profile in device profiles list
private static final int ORDINAL = 1;
static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
+ private static final int JUST_BOND_MILLIS_THRESHOLD = 30000; // 30s
private static final Uri[] SETTINGS_URIS =
new Uri[] {
Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_NAME),
@@ -1283,4 +1287,87 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
UserManager userManager = context.getSystemService(UserManager.class);
return userManager != null && userManager.isManagedProfile();
}
+
+ /** Handle profile connected for {@link CachedBluetoothDevice}. */
+ @WorkerThread
+ public void handleProfileConnected(@NonNull CachedBluetoothDevice cachedDevice,
+ int bluetoothProfile, @Nullable LocalBluetoothManager btManager) {
+ if (!Flags.promoteAudioSharingForSecondAutoConnectedLeaDevice()) {
+ Log.d(TAG, "Skip handleProfileConnected, flag off");
+ return;
+ }
+ if (!SYSUI_PKG.equals(mContext.getPackageName())) {
+ Log.d(TAG, "Skip handleProfileConnected, not a valid caller");
+ return;
+ }
+ if (!BluetoothUtils.isMediaDevice(cachedDevice)) {
+ Log.d(TAG, "Skip handleProfileConnected, not a media device");
+ return;
+ }
+ Timestamp bondTimestamp = cachedDevice.getBondTimestamp();
+ if (bondTimestamp != null) {
+ long diff = System.currentTimeMillis() - bondTimestamp.getTime();
+ if (diff <= JUST_BOND_MILLIS_THRESHOLD) {
+ Log.d(TAG, "Skip handleProfileConnected, just bond within " + diff);
+ return;
+ }
+ }
+ if (!isEnabled(null)) {
+ Log.d(TAG, "Skip handleProfileConnected, not broadcasting");
+ return;
+ }
+ BluetoothDevice device = cachedDevice.getDevice();
+ if (device == null) {
+ Log.d(TAG, "Skip handleProfileConnected, null device");
+ return;
+ }
+ // TODO: sync source in a reasonable place
+ if (BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(device, btManager)) {
+ Log.d(TAG, "Skip handleProfileConnected, already has source");
+ return;
+ }
+ if (isAutoRejoinDevice(device)) {
+ Log.d(TAG, "Skip handleProfileConnected, auto rejoin device");
+ return;
+ }
+ boolean isLeAudioSupported = BluetoothUtils.isLeAudioSupported(cachedDevice);
+ // For eligible (LE audio) remote device, we only check assistant profile connected.
+ if (isLeAudioSupported
+ && bluetoothProfile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
+ Log.d(TAG, "Skip handleProfileConnected, lea sink, not the assistant profile");
+ return;
+ }
+ boolean isFirstConnectedProfile = isFirstConnectedProfile(cachedDevice, bluetoothProfile);
+ // For ineligible (classic) remote device, we only check its first connected profile.
+ if (!isLeAudioSupported && !isFirstConnectedProfile) {
+ Log.d(TAG, "Skip handleProfileConnected, classic sink, not the first profile");
+ return;
+ }
+
+ Intent intent = new Intent(
+ LocalBluetoothLeBroadcast.ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED);
+ intent.putExtra(LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE, device);
+ intent.setPackage(SETTINGS_PKG);
+ Log.d(TAG, "notify device connected, device = " + device.getAnonymizedAddress());
+
+ mContext.sendBroadcast(intent);
+ }
+
+ private boolean isAutoRejoinDevice(@Nullable BluetoothDevice bluetoothDevice) {
+ String metadataValue = BluetoothUtils.getFastPairCustomizedField(bluetoothDevice,
+ AUTO_REJOIN_BROADCAST_TAG);
+ return getLatestBroadcastId() != UNKNOWN_VALUE_PLACEHOLDER && Objects.equals(metadataValue,
+ String.valueOf(getLatestBroadcastId()));
+ }
+
+ private boolean isFirstConnectedProfile(@Nullable CachedBluetoothDevice cachedDevice,
+ int bluetoothProfile) {
+ if (cachedDevice == null) return false;
+ return cachedDevice.getProfiles().stream()
+ .noneMatch(
+ profile ->
+ profile.getProfileId() != bluetoothProfile
+ && profile.getConnectionStatus(cachedDevice.getDevice())
+ == BluetoothProfile.STATE_CONNECTED);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java
index ae17acb5104b..8bb41ccf9600 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java
@@ -16,8 +16,8 @@
package com.android.settingslib.qrcode;
+import android.annotation.NonNull;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
@@ -75,12 +75,29 @@ public class QrCamera extends Handler {
@VisibleForTesting
Camera mCamera;
+ Camera.CameraInfo mCameraInfo;
+
+ /**
+ * The size of the preview image as requested to camera, e.g. 1920x1080.
+ */
private Size mPreviewSize;
+
+ /**
+ * Whether the preview image would be displayed in "portrait" (width less
+ * than height) orientation in current display orientation.
+ *
+ * Note that we don't distinguish between a rotation of 90 degrees or 270
+ * degrees here, since we center crop all the preview.
+ *
+ * TODO: Handle external camera / multiple display, this likely requires
+ * migrating to newer Camera2 API.
+ */
+ private boolean mPreviewInPortrait;
+
private WeakReference<Context> mContext;
private ScannerCallback mScannerCallback;
private MultiFormatReader mReader;
private DecodingTask mDecodeTask;
- private int mCameraOrientation;
@VisibleForTesting
Camera.Parameters mParameters;
@@ -152,8 +169,14 @@ public class QrCamera extends Handler {
* @param previewSize Is the preview size set by camera
* @param cameraOrientation Is the orientation of current Camera
* @return The rectangle would like to crop from the camera preview shot.
+ * @deprecated This is no longer used, and the frame position is
+ * automatically calculated from the preview size and the
+ * background View size.
*/
- Rect getFramePosition(Size previewSize, int cameraOrientation);
+ @Deprecated
+ default @NonNull Rect getFramePosition(@NonNull Size previewSize, int cameraOrientation) {
+ throw new AssertionError("getFramePosition shouldn't be used");
+ }
/**
* Sets the transform to associate with preview area.
@@ -172,6 +195,41 @@ public class QrCamera extends Handler {
boolean isValid(String qrCode);
}
+ private boolean setPreviewDisplayOrientation() {
+ if (mContext.get() == null) {
+ return false;
+ }
+
+ final WindowManager winManager =
+ (WindowManager) mContext.get().getSystemService(Context.WINDOW_SERVICE);
+ final int rotation = winManager.getDefaultDisplay().getRotation();
+ int degrees = 0;
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ degrees = 0;
+ break;
+ case Surface.ROTATION_90:
+ degrees = 90;
+ break;
+ case Surface.ROTATION_180:
+ degrees = 180;
+ break;
+ case Surface.ROTATION_270:
+ degrees = 270;
+ break;
+ }
+ int rotateDegrees = 0;
+ if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ rotateDegrees = (mCameraInfo.orientation + degrees) % 360;
+ rotateDegrees = (360 - rotateDegrees) % 360; // compensate the mirror
+ } else {
+ rotateDegrees = (mCameraInfo.orientation - degrees + 360) % 360;
+ }
+ mCamera.setDisplayOrientation(rotateDegrees);
+ mPreviewInPortrait = (rotateDegrees == 90 || rotateDegrees == 270);
+ return true;
+ }
+
@VisibleForTesting
void setCameraParameter() {
mParameters = mCamera.getParameters();
@@ -195,37 +253,39 @@ public class QrCamera extends Handler {
mCamera.setParameters(mParameters);
}
- private boolean startPreview() {
- if (mContext.get() == null) {
- return false;
- }
+ /**
+ * Set transform matrix to crop and center the preview picture.
+ */
+ private void setTransformationMatrix() {
+ final Size previewDisplaySize = rotateIfPortrait(mPreviewSize);
+ final Size viewSize = mScannerCallback.getViewSize();
+ final Rect cropRegion = calculateCenteredCrop(previewDisplaySize, viewSize);
- final WindowManager winManager =
- (WindowManager) mContext.get().getSystemService(Context.WINDOW_SERVICE);
- final int rotation = winManager.getDefaultDisplay().getRotation();
- int degrees = 0;
- switch (rotation) {
- case Surface.ROTATION_0:
- degrees = 0;
- break;
- case Surface.ROTATION_90:
- degrees = 90;
- break;
- case Surface.ROTATION_180:
- degrees = 180;
- break;
- case Surface.ROTATION_270:
- degrees = 270;
- break;
- }
- final int rotateDegrees = (mCameraOrientation - degrees + 360) % 360;
- mCamera.setDisplayOrientation(rotateDegrees);
+ // Note that strictly speaking, since the preview is mirrored in front
+ // camera case, we should also mirror the crop region here. But since
+ // we're cropping at the center, mirroring would result in the same
+ // crop region other than small off-by-one error from floating point
+ // calculation and wouldn't be noticeable.
+
+ // Calculate transformation matrix.
+ float scaleX = previewDisplaySize.getWidth() / (float) cropRegion.width();
+ float scaleY = previewDisplaySize.getHeight() / (float) cropRegion.height();
+ float translateX = -cropRegion.left / (float) cropRegion.width() * viewSize.getWidth();
+ float translateY = -cropRegion.top / (float) cropRegion.height() * viewSize.getHeight();
+
+ // Set the transform matrix.
+ final Matrix matrix = new Matrix();
+ matrix.setScale(scaleX, scaleY);
+ matrix.postTranslate(translateX, translateY);
+ mScannerCallback.setTransform(matrix);
+ }
+
+ private void startPreview() {
mCamera.startPreview();
if (Camera.Parameters.FOCUS_MODE_AUTO.equals(mParameters.getFocusMode())) {
mCamera.autoFocus(/* Camera.AutoFocusCallback */ null);
sendMessageDelayed(obtainMessage(MSG_AUTO_FOCUS), AUTOFOCUS_INTERVAL_MS);
}
- return true;
}
private class DecodingTask extends AsyncTask<Void, Void, String> {
@@ -300,7 +360,7 @@ public class QrCamera extends Handler {
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
releaseCamera();
mCamera = Camera.open(i);
- mCameraOrientation = cameraInfo.orientation;
+ mCameraInfo = cameraInfo;
break;
}
}
@@ -309,7 +369,7 @@ public class QrCamera extends Handler {
Camera.getCameraInfo(0, cameraInfo);
releaseCamera();
mCamera = Camera.open(0);
- mCameraOrientation = cameraInfo.orientation;
+ mCameraInfo = cameraInfo;
}
} catch (RuntimeException e) {
Log.e(TAG, "Fail to open camera: " + e);
@@ -323,11 +383,12 @@ public class QrCamera extends Handler {
throw new IOException("Cannot find available camera");
}
mCamera.setPreviewTexture(surface);
+ if (!setPreviewDisplayOrientation()) {
+ throw new IOException("Lost context");
+ }
setCameraParameter();
setTransformationMatrix();
- if (!startPreview()) {
- throw new IOException("Lost contex");
- }
+ startPreview();
} catch (IOException ioe) {
Log.e(TAG, "Fail to startPreview camera: " + ioe);
mCamera = null;
@@ -345,32 +406,30 @@ public class QrCamera extends Handler {
}
}
- /** Set transform matrix to crop and center the preview picture */
- private void setTransformationMatrix() {
- final boolean isPortrait = mContext.get().getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_PORTRAIT;
-
- final int previewWidth = isPortrait ? mPreviewSize.getWidth() : mPreviewSize.getHeight();
- final int previewHeight = isPortrait ? mPreviewSize.getHeight() : mPreviewSize.getWidth();
- final float ratioPreview = (float) getRatio(previewWidth, previewHeight);
-
- // Calculate transformation matrix.
- float scaleX = 1.0f;
- float scaleY = 1.0f;
- if (previewWidth > previewHeight) {
- scaleY = scaleX / ratioPreview;
+ /**
+ * Calculates the crop region in `previewSize` to have the same aspect
+ * ratio as `viewSize` and center aligned.
+ */
+ private Rect calculateCenteredCrop(Size previewSize, Size viewSize) {
+ final double previewRatio = getRatio(previewSize);
+ final double viewRatio = getRatio(viewSize);
+ int width;
+ int height;
+ if (previewRatio > viewRatio) {
+ width = previewSize.getWidth();
+ height = (int) Math.round(width * viewRatio);
} else {
- scaleX = scaleY / ratioPreview;
+ height = previewSize.getHeight();
+ width = (int) Math.round(height / viewRatio);
}
-
- // Set the transform matrix.
- final Matrix matrix = new Matrix();
- matrix.setScale(scaleX, scaleY);
- mScannerCallback.setTransform(matrix);
+ final int left = (previewSize.getWidth() - width) / 2;
+ final int top = (previewSize.getHeight() - height) / 2;
+ return new Rect(left, top, left + width, top + height);
}
private QrYuvLuminanceSource getFrameImage(byte[] imageData) {
- final Rect frame = mScannerCallback.getFramePosition(mPreviewSize, mCameraOrientation);
+ final Size viewSize = mScannerCallback.getViewSize();
+ final Rect frame = calculateCenteredCrop(mPreviewSize, rotateIfPortrait(viewSize));
final QrYuvLuminanceSource image = new QrYuvLuminanceSource(imageData,
mPreviewSize.getWidth(), mPreviewSize.getHeight());
return (QrYuvLuminanceSource)
@@ -398,17 +457,18 @@ public class QrCamera extends Handler {
*/
private Size getBestPreviewSize(Camera.Parameters parameters) {
final double minRatioDiffPercent = 0.1;
- final Size windowSize = mScannerCallback.getViewSize();
- final double winRatio = getRatio(windowSize.getWidth(), windowSize.getHeight());
+ final Size viewSize = rotateIfPortrait(mScannerCallback.getViewSize());
+ final double viewRatio = getRatio(viewSize);
double bestChoiceRatio = 0;
Size bestChoice = new Size(0, 0);
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
- double ratio = getRatio(size.width, size.height);
+ final Size newSize = toAndroidSize(size);
+ final double ratio = getRatio(newSize);
if (size.height * size.width > bestChoice.getWidth() * bestChoice.getHeight()
- && (Math.abs(bestChoiceRatio - winRatio) / winRatio > minRatioDiffPercent
- || Math.abs(ratio - winRatio) / winRatio <= minRatioDiffPercent)) {
- bestChoice = new Size(size.width, size.height);
- bestChoiceRatio = getRatio(size.width, size.height);
+ && (Math.abs(bestChoiceRatio - viewRatio) / viewRatio > minRatioDiffPercent
+ || Math.abs(ratio - viewRatio) / viewRatio <= minRatioDiffPercent)) {
+ bestChoice = newSize;
+ bestChoiceRatio = ratio;
}
}
return bestChoice;
@@ -419,25 +479,26 @@ public class QrCamera extends Handler {
* picture size and aspect ratio to choose the best one.
*/
private Size getBestPictureSize(Camera.Parameters parameters) {
- final Camera.Size previewSize = parameters.getPreviewSize();
- final double previewRatio = getRatio(previewSize.width, previewSize.height);
+ final Size previewSize = mPreviewSize;
+ final double previewRatio = getRatio(previewSize);
List<Size> bestChoices = new ArrayList<>();
final List<Size> similarChoices = new ArrayList<>();
// Filter by ratio
- for (Camera.Size size : parameters.getSupportedPictureSizes()) {
- double ratio = getRatio(size.width, size.height);
+ for (Camera.Size picSize : parameters.getSupportedPictureSizes()) {
+ final Size size = toAndroidSize(picSize);
+ final double ratio = getRatio(size);
if (ratio == previewRatio) {
- bestChoices.add(new Size(size.width, size.height));
+ bestChoices.add(size);
} else if (Math.abs(ratio - previewRatio) < MAX_RATIO_DIFF) {
- similarChoices.add(new Size(size.width, size.height));
+ similarChoices.add(size);
}
}
if (bestChoices.size() == 0 && similarChoices.size() == 0) {
Log.d(TAG, "No proper picture size, return default picture size");
Camera.Size defaultPictureSize = parameters.getPictureSize();
- return new Size(defaultPictureSize.width, defaultPictureSize.height);
+ return toAndroidSize(defaultPictureSize);
}
if (bestChoices.size() == 0) {
@@ -447,7 +508,7 @@ public class QrCamera extends Handler {
// Get the best by area
int bestAreaDifference = Integer.MAX_VALUE;
Size bestChoice = null;
- final int previewArea = previewSize.width * previewSize.height;
+ final int previewArea = previewSize.getWidth() * previewSize.getHeight();
for (Size size : bestChoices) {
int areaDifference = Math.abs(size.getWidth() * size.getHeight() - previewArea);
if (areaDifference < bestAreaDifference) {
@@ -458,8 +519,20 @@ public class QrCamera extends Handler {
return bestChoice;
}
- private double getRatio(double x, double y) {
- return (x < y) ? x / y : y / x;
+ private Size rotateIfPortrait(Size size) {
+ if (mPreviewInPortrait) {
+ return new Size(size.getHeight(), size.getWidth());
+ } else {
+ return size;
+ }
+ }
+
+ private double getRatio(Size size) {
+ return size.getHeight() / (double) size.getWidth();
+ }
+
+ private Size toAndroidSize(Camera.Size size) {
+ return new Size(size.width, size.height);
}
@VisibleForTesting
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index b86f4b3715b5..eac6923473b1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -23,7 +23,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -38,12 +37,14 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.TelephonyManager;
import com.android.settingslib.R;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settingslib.utils.ThreadUtils;
import org.junit.Before;
import org.junit.Rule;
@@ -54,6 +55,8 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
@@ -61,7 +64,7 @@ import java.util.Collections;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowBluetoothAdapter.class})
+@Config(shadows = {ShadowBluetoothAdapter.class, BluetoothEventManagerTest.ShadowThreadUtils.class})
public class BluetoothEventManagerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -100,6 +103,8 @@ public class BluetoothEventManagerTest {
private BluetoothUtils.ErrorListener mErrorListener;
@Mock
private LocalBluetoothLeBroadcast mBroadcast;
+ @Mock
+ private UserManager mUserManager;
private Context mContext;
private Intent mIntent;
@@ -130,6 +135,7 @@ public class BluetoothEventManagerTest {
mCachedDevice1 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1);
mCachedDevice2 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2);
mCachedDevice3 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3);
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
BluetoothUtils.setErrorListener(mErrorListener);
}
@@ -196,6 +202,7 @@ public class BluetoothEventManagerTest {
* callback.
*/
@Test
+ @EnableFlags(Flags.FLAG_PROMOTE_AUDIO_SHARING_FOR_SECOND_AUTO_CONNECTED_LEA_DEVICE)
public void dispatchProfileConnectionStateChanged_registerCallback_shouldDispatchCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
@@ -208,10 +215,12 @@ public class BluetoothEventManagerTest {
/**
* dispatchProfileConnectionStateChanged should not call {@link
- * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when audio sharing flag is off.
+ * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded and
+ * {@link LocalBluetoothLeBroadcast}#handleProfileConnected when audio sharing flag is off.
*/
@Test
- public void dispatchProfileConnectionStateChanged_flagOff_noUpdateFallbackDevice() {
+ @EnableFlags(Flags.FLAG_PROMOTE_AUDIO_SHARING_FOR_SECOND_AUTO_CONNECTED_LEA_DEVICE)
+ public void dispatchProfileConnectionStateChanged_flagOff_noCallToBroadcastProfile() {
setUpAudioSharing(/* enableFlag= */ false, /* enableFeature= */ true, /* enableProfile= */
true, /* workProfile= */ false);
mBluetoothEventManager.dispatchProfileConnectionStateChanged(
@@ -219,16 +228,19 @@ public class BluetoothEventManagerTest {
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
- verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, never()).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, never()).handleProfileConnected(any(), anyInt(), any());
}
/**
* dispatchProfileConnectionStateChanged should not call {@link
- * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when the device does not
- * support audio sharing.
+ * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded and
+ * {@link LocalBluetoothLeBroadcast}#handleProfileConnected when the device does not support
+ * audio sharing.
*/
@Test
- public void dispatchProfileConnectionStateChanged_notSupport_noUpdateFallbackDevice() {
+ @EnableFlags(Flags.FLAG_PROMOTE_AUDIO_SHARING_FOR_SECOND_AUTO_CONNECTED_LEA_DEVICE)
+ public void dispatchProfileConnectionStateChanged_notSupport_noCallToBroadcastProfile() {
setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ false, /* enableProfile= */
true, /* workProfile= */ false);
mBluetoothEventManager.dispatchProfileConnectionStateChanged(
@@ -236,7 +248,8 @@ public class BluetoothEventManagerTest {
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
- verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, never()).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, never()).handleProfileConnected(any(), anyInt(), any());
}
/**
@@ -245,6 +258,7 @@ public class BluetoothEventManagerTest {
* not ready.
*/
@Test
+ @EnableFlags(Flags.FLAG_PROMOTE_AUDIO_SHARING_FOR_SECOND_AUTO_CONNECTED_LEA_DEVICE)
public void dispatchProfileConnectionStateChanged_profileNotReady_noUpdateFallbackDevice() {
setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
false, /* workProfile= */ false);
@@ -253,7 +267,7 @@ public class BluetoothEventManagerTest {
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
- verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, never()).updateFallbackActiveDeviceIfNeeded();
}
/**
@@ -262,6 +276,7 @@ public class BluetoothEventManagerTest {
* other than LE_AUDIO_BROADCAST_ASSISTANT or state other than STATE_DISCONNECTED.
*/
@Test
+ @EnableFlags(Flags.FLAG_PROMOTE_AUDIO_SHARING_FOR_SECOND_AUTO_CONNECTED_LEA_DEVICE)
public void dispatchProfileConnectionStateChanged_notAssistantProfile_noUpdateFallbackDevice() {
setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
true, /* workProfile= */ false);
@@ -270,16 +285,17 @@ public class BluetoothEventManagerTest {
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO);
- verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, never()).updateFallbackActiveDeviceIfNeeded();
}
/**
* dispatchProfileConnectionStateChanged should not call {@link
- * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when triggered for
- * work profile.
+ * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded and
+ * {@link LocalBluetoothLeBroadcast}#handleProfileConnected when triggered for work profile.
*/
@Test
- public void dispatchProfileConnectionStateChanged_workProfile_noUpdateFallbackDevice() {
+ @EnableFlags(Flags.FLAG_PROMOTE_AUDIO_SHARING_FOR_SECOND_AUTO_CONNECTED_LEA_DEVICE)
+ public void dispatchProfileConnectionStateChanged_workProfile_noCallToBroadcastProfile() {
setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
true, /* workProfile= */ true);
mBluetoothEventManager.dispatchProfileConnectionStateChanged(
@@ -287,7 +303,8 @@ public class BluetoothEventManagerTest {
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
- verify(mBroadcast).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, never()).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, never()).handleProfileConnected(any(), anyInt(), any());
}
/**
@@ -296,7 +313,8 @@ public class BluetoothEventManagerTest {
* disconnected and audio sharing is enabled.
*/
@Test
- public void dispatchProfileConnectionStateChanged_audioSharing_updateFallbackDevice() {
+ @EnableFlags(Flags.FLAG_PROMOTE_AUDIO_SHARING_FOR_SECOND_AUTO_CONNECTED_LEA_DEVICE)
+ public void dispatchProfileConnectionStateChanged_assistDisconnected_updateFallbackDevice() {
setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
true, /* workProfile= */ false);
mBluetoothEventManager.dispatchProfileConnectionStateChanged(
@@ -305,6 +323,27 @@ public class BluetoothEventManagerTest {
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
verify(mBroadcast).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, never()).handleProfileConnected(any(), anyInt(), any());
+ }
+
+ /**
+ * dispatchProfileConnectionStateChanged should call {@link
+ * LocalBluetoothLeBroadcast}#handleProfileConnected when assistant profile is connected and
+ * audio sharing is enabled.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_PROMOTE_AUDIO_SHARING_FOR_SECOND_AUTO_CONNECTED_LEA_DEVICE)
+ public void dispatchProfileConnectionStateChanged_assistConnected_handleStateChanged() {
+ setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
+ true, /* workProfile= */ false);
+ mBluetoothEventManager.dispatchProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ verify(mBroadcast, never()).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast).handleProfileConnected(mCachedBluetoothDevice,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mBtManager);
}
private void setUpAudioSharing(boolean enableFlag, boolean enableFeature,
@@ -325,13 +364,19 @@ public class BluetoothEventManagerTest {
LocalBluetoothLeBroadcastAssistant assistant =
mock(LocalBluetoothLeBroadcastAssistant.class);
when(assistant.isProfileReady()).thenReturn(enableProfile);
- LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
- when(profileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
- when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
- when(mBtManager.getProfileManager()).thenReturn(profileManager);
- UserManager userManager = mock(UserManager.class);
- when(mContext.getSystemService(UserManager.class)).thenReturn(userManager);
- when(userManager.isManagedProfile()).thenReturn(workProfile);
+ when(mLocalProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
+ when(mLocalProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
+ when(mUserManager.isManagedProfile()).thenReturn(workProfile);
+ if (workProfile) {
+ mBluetoothEventManager =
+ new BluetoothEventManager(
+ mLocalAdapter,
+ mBtManager,
+ mCachedDeviceManager,
+ mContext,
+ /* handler= */ null,
+ /* userHandle= */ null);
+ }
}
@Test
@@ -665,4 +710,12 @@ public class BluetoothEventManagerTest {
verify(mBluetoothCallback).onAutoOnStateChanged(anyInt());
}
+
+ @Implements(value = ThreadUtils.class)
+ public static class ShadowThreadUtils {
+ @Implementation
+ protected static void postOnBackgroundThread(Runnable runnable) {
+ runnable.run();
+ }
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 0325c0ec7915..b7814127b716 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -1349,6 +1349,36 @@ public class BluetoothUtilsTest {
}
@Test
+ public void isMediaDevice_returnsFalse() {
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(ImmutableList.of(mAssistant));
+ assertThat(BluetoothUtils.isMediaDevice(mCachedBluetoothDevice)).isFalse();
+ }
+
+ @Test
+ public void isMediaDevice_returnsTrue() {
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(ImmutableList.of(mLeAudioProfile));
+ assertThat(BluetoothUtils.isMediaDevice(mCachedBluetoothDevice)).isTrue();
+ }
+
+ @Test
+ public void isLeAudioSupported_returnsFalse() {
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(ImmutableList.of(mLeAudioProfile));
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(false);
+
+ assertThat(BluetoothUtils.isLeAudioSupported(mCachedBluetoothDevice)).isFalse();
+ }
+
+ @Test
+ public void isLeAudioSupported_returnsTrue() {
+ when(mCachedBluetoothDevice.getProfiles()).thenReturn(ImmutableList.of(mLeAudioProfile));
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+ assertThat(BluetoothUtils.isLeAudioSupported(mCachedBluetoothDevice)).isTrue();
+ }
+
+ @Test
public void isTemporaryBondDevice_hasMetadata_returnsTrue() {
when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
.thenReturn(TEMP_BOND_METADATA.getBytes());
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index aad1276d76e5..654478af3fb0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -28,6 +28,7 @@ import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockSettings
+import com.android.systemui.shared.clocks.FlexClockController.Companion.AXIS_PRESETS
import com.android.systemui.shared.clocks.FlexClockController.Companion.getDefaultAxes
private val TAG = DefaultClockProvider::class.simpleName
@@ -98,16 +99,16 @@ class DefaultClockProvider(
throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
}
- val fontAxes =
- if (!isClockReactiveVariantsEnabled) listOf()
- else getDefaultAxes(settings).merge(settings.axes)
return ClockPickerConfig(
settings.clockId ?: DEFAULT_CLOCK_ID,
resources.getString(R.string.clock_default_name),
resources.getString(R.string.clock_default_description),
resources.getDrawable(R.drawable.clock_default_thumbnail, null),
isReactiveToTone = true,
- axes = fontAxes,
+ axes =
+ if (!isClockReactiveVariantsEnabled) emptyList()
+ else getDefaultAxes(settings).merge(settings.axes),
+ axisPresets = if (!isClockReactiveVariantsEnabled) emptyList() else AXIS_PRESETS,
)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index ac1c5a8dfaf3..1a1033ba42e0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -132,7 +132,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
listOf(
GSFAxes.WEIGHT.toClockAxis(
type = AxisType.Float,
- currentValue = 400f,
+ currentValue = 475f,
name = "Weight",
description = "Glyph Weight",
),
@@ -161,5 +161,59 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
GSFAxes.ROUND.toClockAxisSetting(100f),
GSFAxes.SLANT.toClockAxisSetting(0f),
)
+
+ val AXIS_PRESETS =
+ listOf(
+ FONT_AXES.map { it.toSetting() },
+ LEGACY_FLEX_SETTINGS,
+ listOf( // Porcelain
+ GSFAxes.WEIGHT.toClockAxisSetting(500f),
+ GSFAxes.WIDTH.toClockAxisSetting(100f),
+ GSFAxes.ROUND.toClockAxisSetting(0f),
+ GSFAxes.SLANT.toClockAxisSetting(0f),
+ ),
+ listOf( // Midnight
+ GSFAxes.WEIGHT.toClockAxisSetting(300f),
+ GSFAxes.WIDTH.toClockAxisSetting(100f),
+ GSFAxes.ROUND.toClockAxisSetting(100f),
+ GSFAxes.SLANT.toClockAxisSetting(-10f),
+ ),
+ listOf( // Sterling
+ GSFAxes.WEIGHT.toClockAxisSetting(1000f),
+ GSFAxes.WIDTH.toClockAxisSetting(100f),
+ GSFAxes.ROUND.toClockAxisSetting(0f),
+ GSFAxes.SLANT.toClockAxisSetting(0f),
+ ),
+ listOf( // Smoky Green
+ GSFAxes.WEIGHT.toClockAxisSetting(150f),
+ GSFAxes.WIDTH.toClockAxisSetting(50f),
+ GSFAxes.ROUND.toClockAxisSetting(0f),
+ GSFAxes.SLANT.toClockAxisSetting(0f),
+ ),
+ listOf( // Iris
+ GSFAxes.WEIGHT.toClockAxisSetting(500f),
+ GSFAxes.WIDTH.toClockAxisSetting(100f),
+ GSFAxes.ROUND.toClockAxisSetting(100f),
+ GSFAxes.SLANT.toClockAxisSetting(0f),
+ ),
+ listOf( // Margarita
+ GSFAxes.WEIGHT.toClockAxisSetting(300f),
+ GSFAxes.WIDTH.toClockAxisSetting(30f),
+ GSFAxes.ROUND.toClockAxisSetting(100f),
+ GSFAxes.SLANT.toClockAxisSetting(-10f),
+ ),
+ listOf( // Raspberry
+ GSFAxes.WEIGHT.toClockAxisSetting(700f),
+ GSFAxes.WIDTH.toClockAxisSetting(140f),
+ GSFAxes.ROUND.toClockAxisSetting(100f),
+ GSFAxes.SLANT.toClockAxisSetting(-7f),
+ ),
+ listOf( // Ultra Blue
+ GSFAxes.WEIGHT.toClockAxisSetting(850f),
+ GSFAxes.WIDTH.toClockAxisSetting(130f),
+ GSFAxes.ROUND.toClockAxisSetting(0f),
+ GSFAxes.SLANT.toClockAxisSetting(0f),
+ ),
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
index e53155de653d..ed73d89db2c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
@@ -21,6 +21,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalSmartspaceRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -28,12 +30,12 @@ import com.android.systemui.communal.domain.interactor.communalSettingsInteracto
import com.android.systemui.communal.domain.interactor.setCommunalEnabled
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,46 +44,64 @@ import org.junit.runner.RunWith
@EnableFlags(FLAG_COMMUNAL_HUB)
@RunWith(AndroidJUnit4::class)
class CommunalOngoingContentStartableTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private val mediaRepository = kosmos.fakeCommunalMediaRepository
- private val smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
+ private var showUmoOnHub = true
- private lateinit var underTest: CommunalOngoingContentStartable
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ CommunalOngoingContentStartable(
+ bgScope = applicationCoroutineScope,
+ communalInteractor = communalInteractor,
+ communalMediaRepository = communalMediaRepository,
+ communalSettingsInteractor = communalSettingsInteractor,
+ communalSmartspaceRepository = communalSmartspaceRepository,
+ showUmoOnHub = showUmoOnHub,
+ )
+ }
@Before
fun setUp() {
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
- underTest =
- CommunalOngoingContentStartable(
- bgScope = kosmos.applicationCoroutineScope,
- communalInteractor = kosmos.communalInteractor,
- communalMediaRepository = mediaRepository,
- communalSettingsInteractor = kosmos.communalSettingsInteractor,
- communalSmartspaceRepository = smartspaceRepository,
- )
}
@Test
- fun testListenForOngoingContentWhenCommunalIsEnabled() =
- testScope.runTest {
+ fun testListenForOngoingContent() =
+ kosmos.runTest {
+ underTest.start()
+
+ assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
+ assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
+
+ kosmos.setCommunalEnabled(true)
+
+ assertThat(fakeCommunalMediaRepository.isListening()).isTrue()
+ assertThat(fakeCommunalSmartspaceRepository.isListening()).isTrue()
+
+ kosmos.setCommunalEnabled(false)
+
+ assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
+ assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
+ }
+
+ @Test
+ fun testListenForOngoingContent_showUmoFalse() =
+ kosmos.runTest {
+ showUmoOnHub = false
underTest.start()
- runCurrent()
- assertThat(mediaRepository.isListening()).isFalse()
- assertThat(smartspaceRepository.isListening()).isFalse()
+ assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
+ assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
kosmos.setCommunalEnabled(true)
- runCurrent()
- assertThat(mediaRepository.isListening()).isTrue()
- assertThat(smartspaceRepository.isListening()).isTrue()
+ // Media listening does not start when UMO is disabled.
+ assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
+ assertThat(fakeCommunalSmartspaceRepository.isListening()).isTrue()
kosmos.setCommunalEnabled(false)
- runCurrent()
- assertThat(mediaRepository.isListening()).isFalse()
- assertThat(smartspaceRepository.isListening()).isFalse()
+ assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
+ assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index 917f3564a1bd..80ce43d61003 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -65,8 +65,7 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
-
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val underTest by lazy { kosmos.notificationsShadeOverlayContentViewModel }
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index c69ebab7a170..baf0aeb701d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -61,8 +61,7 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
usingMediaInComposeFragment = false // This is not for the compose fragment
}
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
-
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val underTest by lazy { kosmos.quickSettingsShadeOverlayContentViewModel }
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 559e363d8937..d3f592357b14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -73,9 +73,8 @@ class SceneInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val fakeSceneDataSource = kosmos.fakeSceneDataSource
-
- private val underTest = kosmos.sceneInteractor
+ private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
+ private val underTest by lazy { kosmos.sceneInteractor }
@Before
fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index 4a011c0844e5..ccc876c20623 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -50,11 +50,10 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val configurationRepository = kosmos.fakeConfigurationRepository
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val sceneInteractor = kosmos.sceneInteractor
+ private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
-
private val underTest by lazy { kosmos.shadeInteractorSceneContainerImpl }
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 37b4688f753d..a832f486ef32 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -15,7 +15,9 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -51,12 +53,11 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@EnableSceneContainer
class ShadeHeaderViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
- private val mobileIconsInteractor = kosmos.fakeMobileIconsInteractor
- private val sceneInteractor = kosmos.sceneInteractor
- private val deviceEntryInteractor = kosmos.deviceEntryInteractor
-
+ private val mobileIconsInteractor by lazy { kosmos.fakeMobileIconsInteractor }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private val underTest by lazy { kosmos.shadeHeaderViewModel }
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 3d8da6140ff7..70df82d95008 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -22,6 +22,7 @@ import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_
import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_VIA_SYSUI_CALLBACKS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -556,9 +557,9 @@ public class CommandQueueTest extends SysuiTestCase {
@Test
public void testImmersiveModeChanged() {
final int displayAreaId = 10;
- mCommandQueue.immersiveModeChanged(displayAreaId, true);
+ mCommandQueue.immersiveModeChanged(displayAreaId, true, TYPE_APPLICATION);
waitForIdleSync();
- verify(mCallbacks).immersiveModeChanged(displayAreaId, true);
+ verify(mCallbacks).immersiveModeChanged(displayAreaId, true, TYPE_APPLICATION);
}
@Test
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt
index 6e4dc1485c7b..0cbc30d399d0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt
@@ -34,6 +34,9 @@ constructor(
/** Font axes that can be modified on this clock */
val axes: List<ClockFontAxis> = listOf(),
+
+ /** List of font presets for this clock. Can be assigned directly. */
+ val axisPresets: List<List<ClockFontAxisSetting>> = listOf(),
)
/** Represents an Axis that can be modified */
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b273886e286e..4995858f95a4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1125,4 +1125,7 @@
<!-- Configuration to swipe to open glanceable hub -->
<bool name="config_swipeToOpenGlanceableHub">false</bool>
+
+ <!-- Whether or not to show the UMO on the glanceable hub when media is playing. -->
+ <bool name="config_showUmoOnHub">false</bool>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt
index 48a6d9de380c..7765d0017c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalOngoingContentStartable.kt
@@ -16,7 +16,9 @@
package com.android.systemui.communal
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
+import com.android.systemui.communal.dagger.CommunalModule.Companion.SHOW_UMO
import com.android.systemui.communal.data.repository.CommunalMediaRepository
import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -24,8 +26,8 @@ import com.android.systemui.communal.domain.interactor.CommunalSettingsInteracto
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class CommunalOngoingContentStartable
@@ -36,6 +38,7 @@ constructor(
private val communalMediaRepository: CommunalMediaRepository,
private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalSmartspaceRepository: CommunalSmartspaceRepository,
+ @Named(SHOW_UMO) private val showUmoOnHub: Boolean,
) : CoreStartable {
override fun start() {
@@ -46,10 +49,14 @@ constructor(
bgScope.launch {
communalInteractor.isCommunalEnabled.collect { enabled ->
if (enabled) {
- communalMediaRepository.startListening()
+ if (showUmoOnHub) {
+ communalMediaRepository.startListening()
+ }
communalSmartspaceRepository.startListening()
} else {
- communalMediaRepository.stopListening()
+ if (showUmoOnHub) {
+ communalMediaRepository.stopListening()
+ }
communalSmartspaceRepository.stopListening()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index ff741625a3cc..bb3be531aa8a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -105,6 +105,7 @@ interface CommunalModule {
const val LOGGABLE_PREFIXES = "loggable_prefixes"
const val LAUNCHER_PACKAGE = "launcher_package"
const val SWIPE_TO_HUB = "swipe_to_hub"
+ const val SHOW_UMO = "show_umo"
@Provides
@Communal
@@ -150,5 +151,11 @@ interface CommunalModule {
fun provideSwipeToHub(@Main resources: Resources): Boolean {
return resources.getBoolean(R.bool.config_swipeToOpenGlanceableHub)
}
+
+ @Provides
+ @Named(SHOW_UMO)
+ fun provideShowUmo(@Main resources: Resources): Boolean {
+ return resources.getBoolean(R.bool.config_showUmoOnHub)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java
index 0adc41313bae..8d4a24e0c2cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java
@@ -400,6 +400,9 @@ public class InternetDialogDelegateLegacy implements
mInternetDialogTitle.setText(internetContent.mInternetDialogTitleString);
mInternetDialogSubTitle.setText(internetContent.mInternetDialogSubTitle);
+ if (!internetContent.mIsWifiEnabled) {
+ setProgressBarVisible(false);
+ }
mAirplaneModeButton.setVisibility(
internetContent.mIsAirplaneModeEnabled ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 7dc2ae71b63e..e44701dba87c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -580,7 +580,8 @@ public class CommandQueue extends IStatusBar.Stub implements
/**
* @see IStatusBar#immersiveModeChanged
*/
- default void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {}
+ default void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode,
+ int windowType) {}
/**
* @see IStatusBar#moveFocusedTaskToDesktop(int)
@@ -876,11 +877,13 @@ public class CommandQueue extends IStatusBar.Stub implements
}
@Override
- public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {
+ public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode,
+ int windowType) {
synchronized (mLock) {
final SomeArgs args = SomeArgs.obtain();
args.argi1 = rootDisplayAreaId;
args.argi2 = isImmersiveMode ? 1 : 0;
+ args.argi3 = windowType;
mHandler.obtainMessage(MSG_IMMERSIVE_CHANGED, args).sendToTarget();
}
}
@@ -2030,8 +2033,10 @@ public class CommandQueue extends IStatusBar.Stub implements
args = (SomeArgs) msg.obj;
int rootDisplayAreaId = args.argi1;
boolean isImmersiveMode = args.argi2 != 0;
+ int windowType = args.argi3;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).immersiveModeChanged(rootDisplayAreaId, isImmersiveMode);
+ mCallbacks.get(i).immersiveModeChanged(rootDisplayAreaId, isImmersiveMode,
+ windowType);
}
break;
case MSG_ENTER_DESKTOP: {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index fed3f6e81130..97e62d79b374 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -23,6 +23,8 @@ import static android.app.StatusBarManager.DISABLE_HOME;
import static android.app.StatusBarManager.DISABLE_RECENT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
@@ -208,7 +210,8 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca
}
@Override
- public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {
+ public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode,
+ int windowType) {
mHandler.removeMessages(H.SHOW);
if (isImmersiveMode) {
if (DEBUG) Log.d(TAG, "immersiveModeChanged() sConfirmed=" + sConfirmed);
@@ -221,7 +224,9 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca
&& mCanSystemBarsBeShownByUser
&& !mNavBarEmpty
&& !UserManager.isDeviceInDemoMode(mDisplayContext)
- && (mLockTaskState != LOCK_TASK_MODE_LOCKED)) {
+ && (mLockTaskState != LOCK_TASK_MODE_LOCKED)
+ && windowType != TYPE_PRESENTATION
+ && windowType != TYPE_PRIVATE_PRESENTATION) {
final Message msg = mHandler.obtainMessage(
H.SHOW);
msg.arg1 = rootDisplayAreaId;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 6bfc9f07ffc4..4bd6053ea23c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -21,7 +21,6 @@ import android.app.INotificationManager
import android.app.NotificationChannel
import android.app.NotificationChannel.DEFAULT_CHANNEL_ID
import android.app.NotificationChannelGroup
-import android.app.NotificationManager.IMPORTANCE_NONE
import android.app.NotificationManager.Importance
import android.content.Context
import android.graphics.Color
@@ -40,7 +39,7 @@ import android.widget.TextView
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import javax.inject.Inject
private const val TAG = "ChannelDialogController"
@@ -59,9 +58,9 @@ private const val TAG = "ChannelDialogController"
*/
@SysUISingleton
class ChannelEditorDialogController @Inject constructor(
- @ShadeDisplayAware private val context: Context,
+ private val shadeDialogContextInteractor: ShadeDialogContextInteractor,
private val noMan: INotificationManager,
- private val dialogBuilder: ChannelEditorDialog.Builder
+ private val dialogBuilder: ChannelEditorDialog.Builder,
) {
private var prepared = false
@@ -272,7 +271,7 @@ class ChannelEditorDialogController @Inject constructor(
}
private fun initDialog() {
- dialogBuilder.setContext(context)
+ dialogBuilder.setContext(shadeDialogContextInteractor.context)
dialog = dialogBuilder.build()
dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacyTest.java
index 3d0a8f6cd236..ebbe023d0d24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacyTest.java
@@ -878,4 +878,18 @@ public class InternetDialogDelegateLegacyTest extends SysuiTestCase {
mMobileDataLayout.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
mConnectedWifi.setVisibility(connectedWifiVisible ? View.VISIBLE : View.GONE);
}
+
+ @Test
+ public void updateDialog_wifiIsDisabled_turnOffProgressBar() {
+ when(mInternetDetailsContentController.isWifiEnabled()).thenReturn(false);
+ mInternetDialogDelegateLegacy.mIsProgressBarVisible = true;
+
+ mInternetDialogDelegateLegacy.updateDialog(false);
+
+ mBgExecutor.runAllReady();
+ mInternetDialogDelegateLegacy.mDataInternetContent.observe(
+ mInternetDialogDelegateLegacy.mLifecycleOwner, i -> {
+ assertThat(mInternetDialogDelegateLegacy.mIsProgressBarVisible).isFalse();
+ });
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
index c5b19ab5862c..0b2fea53d811 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
@@ -31,13 +31,13 @@ import android.testing.TestableLooper
import android.view.View
import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.runner.RunWith
import org.junit.Test
import org.mockito.Answers
-import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito
@@ -66,11 +66,14 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() {
@Mock
private lateinit var dialog: ChannelEditorDialog
+ private val shadeDialogContextInteractor = FakeShadeDialogContextInteractor(mContext)
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
`when`(dialogBuilder.build()).thenReturn(dialog)
- controller = ChannelEditorDialogController(mContext, mockNoMan, dialogBuilder)
+ controller =
+ ChannelEditorDialogController(shadeDialogContextInteractor, mockNoMan, dialogBuilder)
channel1 = NotificationChannel(TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_DEFAULT)
channel2 = NotificationChannel(TEST_CHANNEL2, TEST_CHANNEL_NAME2, IMPORTANCE_NONE)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index e4806e62a9e5..e4806e62a9e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 9e914ad0a660..9e914ad0a660 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 804e7d635107..804e7d635107 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bd7a0ac55117..b75b7ddf8181 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2816,13 +2816,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyEmergencyNumberList()")) {
return;
}
- if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
- if (!mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY_CALLING)) {
- // TelephonyManager.getEmergencyNumberList() throws an exception if
- // FEATURE_TELEPHONY_CALLING is not defined.
- return;
- }
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CALLING)) {
+ // TelephonyManager.getEmergencyNumberList() throws an exception if
+ // FEATURE_TELEPHONY_CALLING is not defined.
+ return;
}
synchronized (mRecords) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8b701f0e2069..b0b34d0ab9c4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19471,7 +19471,7 @@ public class ActivityManagerService extends IActivityManager.Stub
/**
* @hide
*/
- @EnforcePermission("android.permission.INTERACT_ACROSS_USERS_FULL")
+ @EnforcePermission(INTERACT_ACROSS_USERS_FULL)
public IBinder refreshIntentCreatorToken(Intent intent) {
refreshIntentCreatorToken_enforcePermission();
IBinder binder = intent.getCreatorToken();
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 36035bdcddbc..78beb18263a7 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -832,7 +832,9 @@ class BroadcastQueueImpl extends BroadcastQueue {
// If this receiver is going to be skipped, skip it now itself and don't even enqueue
// it.
- final String skipReason = mSkipPolicy.shouldSkipMessage(r, receiver);
+ final String skipReason = Flags.avoidNoteOpAtEnqueue()
+ ? mSkipPolicy.shouldSkipAtEnqueueMessage(r, receiver)
+ : mSkipPolicy.shouldSkipMessage(r, receiver);
if (skipReason != null) {
setDeliveryState(null, null, r, i, receiver, BroadcastRecord.DELIVERY_SKIPPED,
"skipped by policy at enqueue: " + skipReason);
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index d2af84cf3d30..b0d5994cc60b 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -71,10 +71,20 @@ public class BroadcastSkipPolicy {
* {@code null} if it can proceed.
*/
public @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, @NonNull Object target) {
+ return shouldSkipMessage(r, target, false /* preflight */);
+ }
+
+ public @Nullable String shouldSkipAtEnqueueMessage(@NonNull BroadcastRecord r,
+ @NonNull Object target) {
+ return shouldSkipMessage(r, target, true /* preflight */);
+ }
+
+ private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, @NonNull Object target,
+ boolean preflight) {
if (target instanceof BroadcastFilter) {
- return shouldSkipMessage(r, (BroadcastFilter) target);
+ return shouldSkipMessage(r, (BroadcastFilter) target, preflight);
} else {
- return shouldSkipMessage(r, (ResolveInfo) target);
+ return shouldSkipMessage(r, (ResolveInfo) target, preflight);
}
}
@@ -86,7 +96,7 @@ public class BroadcastSkipPolicy {
* {@code null} if it can proceed.
*/
private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
- @NonNull ResolveInfo info) {
+ @NonNull ResolveInfo info, boolean preflight) {
final BroadcastOptions brOptions = r.options;
final ComponentName component = new ComponentName(
info.activityInfo.applicationInfo.packageName,
@@ -134,15 +144,23 @@ public class BroadcastSkipPolicy {
+ " requires " + info.activityInfo.permission;
}
} else if (info.activityInfo.permission != null) {
- final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
- if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
- r.callingUid, r.callerPackage, r.callerFeatureId,
- "Broadcast delivered to " + info.activityInfo.name)
- != AppOpsManager.MODE_ALLOWED) {
- return "Appop Denial: broadcasting "
- + broadcastDescription(r, component)
- + " requires appop " + AppOpsManager.permissionToOp(
- info.activityInfo.permission);
+ final String op = AppOpsManager.permissionToOp(info.activityInfo.permission);
+ if (op != null) {
+ final int mode;
+ if (preflight) {
+ mode = mService.getAppOpsManager().checkOpNoThrow(op,
+ r.callingUid, r.callerPackage, r.callerFeatureId);
+ } else {
+ mode = mService.getAppOpsManager().noteOpNoThrow(op,
+ r.callingUid, r.callerPackage, r.callerFeatureId,
+ "Broadcast delivered to " + info.activityInfo.name);
+ }
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ return "Appop Denial: broadcasting "
+ + broadcastDescription(r, component)
+ + " requires appop " + AppOpsManager.permissionToOp(
+ info.activityInfo.permission);
+ }
}
}
@@ -250,8 +268,8 @@ public class BroadcastSkipPolicy {
perm = PackageManager.PERMISSION_DENIED;
}
- int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
- if (appOp != AppOpsManager.OP_NONE) {
+ final String appOp = AppOpsManager.permissionToOp(excludedPermission);
+ if (appOp != null) {
// When there is an app op associated with the permission,
// skip when both the permission and the app op are
// granted.
@@ -259,7 +277,7 @@ public class BroadcastSkipPolicy {
mService.getAppOpsManager().checkOpNoThrow(appOp,
info.activityInfo.applicationInfo.uid,
info.activityInfo.packageName)
- == AppOpsManager.MODE_ALLOWED)) {
+ == AppOpsManager.MODE_ALLOWED)) {
return "Skipping delivery to " + info.activityInfo.packageName
+ " due to excluded permission " + excludedPermission;
}
@@ -292,9 +310,10 @@ public class BroadcastSkipPolicy {
createAttributionSourcesForResolveInfo(info);
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
- perm = hasPermissionForDataDelivery(
+ perm = hasPermission(
requiredPermission,
"Broadcast delivered to " + info.activityInfo.name,
+ preflight,
attributionSources)
? PackageManager.PERMISSION_GRANTED
: PackageManager.PERMISSION_DENIED;
@@ -308,10 +327,14 @@ public class BroadcastSkipPolicy {
}
}
}
- if (r.appOp != AppOpsManager.OP_NONE) {
- if (!noteOpForManifestReceiver(r.appOp, r, info, component)) {
+ if (r.appOp != AppOpsManager.OP_NONE && AppOpsManager.isValidOp(r.appOp)) {
+ final String op = AppOpsManager.opToPublicName(r.appOp);
+ final boolean appOpAllowed = preflight
+ ? checkOpForManifestReceiver(r.appOp, op, r, info, component)
+ : noteOpForManifestReceiver(r.appOp, op, r, info, component);
+ if (!appOpAllowed) {
return "Skipping delivery to " + info.activityInfo.packageName
- + " due to required appop " + r.appOp;
+ + " due to required appop " + AppOpsManager.opToName(r.appOp);
}
}
@@ -338,7 +361,7 @@ public class BroadcastSkipPolicy {
* {@code null} if it can proceed.
*/
private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
- @NonNull BroadcastFilter filter) {
+ @NonNull BroadcastFilter filter, boolean preflight) {
if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) {
return "Compat change filtered: broadcasting " + r.intent.toString()
+ " to uid " + filter.owningUid + " due to compat change "
@@ -372,18 +395,25 @@ public class BroadcastSkipPolicy {
+ " requires " + filter.requiredPermission
+ " due to registered receiver " + filter;
} else {
- final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
- if (opCode != AppOpsManager.OP_NONE
- && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
- r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
- != AppOpsManager.MODE_ALLOWED) {
- return "Appop Denial: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid="
- + r.callingPid + ", uid=" + r.callingUid + ")"
- + " requires appop " + AppOpsManager.permissionToOp(
- filter.requiredPermission)
- + " due to registered receiver " + filter;
+ final String op = AppOpsManager.permissionToOp(filter.requiredPermission);
+ if (op != null) {
+ final int mode;
+ if (preflight) {
+ mode = mService.getAppOpsManager().checkOpNoThrow(op,
+ r.callingUid, r.callerPackage, r.callerFeatureId);
+ } else {
+ mode = mService.getAppOpsManager().noteOpNoThrow(op, r.callingUid,
+ r.callerPackage, r.callerFeatureId,
+ "Broadcast sent to protected receiver");
+ }
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ return "Appop Denial: broadcasting "
+ + r.intent
+ + " from " + r.callerPackage + " (pid="
+ + r.callingPid + ", uid=" + r.callingUid + ")"
+ + " requires appop " + op
+ + " due to registered receiver " + filter;
+ }
}
}
}
@@ -433,9 +463,10 @@ public class BroadcastSkipPolicy {
.build();
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
- final int perm = hasPermissionForDataDelivery(
+ final int perm = hasPermission(
requiredPermission,
"Broadcast delivered to registered receiver " + filter.receiverId,
+ preflight,
attributionSource)
? PackageManager.PERMISSION_GRANTED
: PackageManager.PERMISSION_DENIED;
@@ -471,8 +502,8 @@ public class BroadcastSkipPolicy {
final int perm = checkComponentPermission(excludedPermission,
filter.receiverList.pid, filter.receiverList.uid, -1, true);
- int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
- if (appOp != AppOpsManager.OP_NONE) {
+ final String appOp = AppOpsManager.permissionToOp(excludedPermission);
+ if (appOp != null) {
// When there is an app op associated with the permission,
// skip when both the permission and the app op are
// granted.
@@ -480,14 +511,13 @@ public class BroadcastSkipPolicy {
mService.getAppOpsManager().checkOpNoThrow(appOp,
filter.receiverList.uid,
filter.packageName)
- == AppOpsManager.MODE_ALLOWED)) {
+ == AppOpsManager.MODE_ALLOWED)) {
return "Appop Denial: receiving "
- + r.intent.toString()
+ + r.intent
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
- + " excludes appop " + AppOpsManager.permissionToOp(
- excludedPermission)
+ + " excludes appop " + appOp
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")";
}
@@ -496,7 +526,7 @@ public class BroadcastSkipPolicy {
// skip when permission is granted.
if (perm == PackageManager.PERMISSION_GRANTED) {
return "Permission Denial: receiving "
- + r.intent.toString()
+ + r.intent
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
@@ -523,19 +553,27 @@ public class BroadcastSkipPolicy {
}
// If the broadcast also requires an app op check that as well.
- if (r.appOp != AppOpsManager.OP_NONE
- && mService.getAppOpsManager().noteOpNoThrow(r.appOp,
- filter.receiverList.uid, filter.packageName, filter.featureId,
- "Broadcast delivered to registered receiver " + filter.receiverId)
- != AppOpsManager.MODE_ALLOWED) {
- return "Appop Denial: receiving "
- + r.intent.toString()
- + " to " + filter.receiverList.app
- + " (pid=" + filter.receiverList.pid
- + ", uid=" + filter.receiverList.uid + ")"
- + " requires appop " + AppOpsManager.opToName(r.appOp)
- + " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")";
+ if (r.appOp != AppOpsManager.OP_NONE && AppOpsManager.isValidOp(r.appOp)) {
+ final String op = AppOpsManager.opToPublicName(r.appOp);
+ final int mode;
+ if (preflight) {
+ mode = mService.getAppOpsManager().checkOpNoThrow(op,
+ filter.receiverList.uid, filter.packageName, filter.featureId);
+ } else {
+ mode = mService.getAppOpsManager().noteOpNoThrow(op,
+ filter.receiverList.uid, filter.packageName, filter.featureId,
+ "Broadcast delivered to registered receiver " + filter.receiverId);
+ }
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ return "Appop Denial: receiving "
+ + r.intent
+ + " to " + filter.receiverList.app
+ + " (pid=" + filter.receiverList.pid
+ + ", uid=" + filter.receiverList.uid + ")"
+ + " requires appop " + AppOpsManager.opToName(r.appOp)
+ + " due to sender " + r.callerPackage
+ + " (uid " + r.callingUid + ")";
+ }
}
// Ensure that broadcasts are only sent to other apps if they are explicitly marked as
@@ -572,14 +610,14 @@ public class BroadcastSkipPolicy {
+ ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
}
- private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info,
- ComponentName component) {
+ private boolean noteOpForManifestReceiver(int opCode, String appOp, BroadcastRecord r,
+ ResolveInfo info, ComponentName component) {
if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {
- return noteOpForManifestReceiverInner(appOp, r, info, component, null);
+ return noteOpForManifestReceiverInner(opCode, appOp, r, info, component, null);
} else {
// Attribution tags provided, noteOp each tag
for (String tag : info.activityInfo.attributionTags) {
- if (!noteOpForManifestReceiverInner(appOp, r, info, component, tag)) {
+ if (!noteOpForManifestReceiverInner(opCode, appOp, r, info, component, tag)) {
return false;
}
}
@@ -587,8 +625,8 @@ public class BroadcastSkipPolicy {
}
}
- private boolean noteOpForManifestReceiverInner(int appOp, BroadcastRecord r, ResolveInfo info,
- ComponentName component, String tag) {
+ private boolean noteOpForManifestReceiverInner(int opCode, String appOp, BroadcastRecord r,
+ ResolveInfo info, ComponentName component, String tag) {
if (mService.getAppOpsManager().noteOpNoThrow(appOp,
info.activityInfo.applicationInfo.uid,
info.activityInfo.packageName,
@@ -598,7 +636,37 @@ public class BroadcastSkipPolicy {
Slog.w(TAG, "Appop Denial: receiving "
+ r.intent + " to "
+ component.flattenToShortString()
- + " requires appop " + AppOpsManager.opToName(appOp)
+ + " requires appop " + AppOpsManager.opToName(opCode)
+ + " due to sender " + r.callerPackage
+ + " (uid " + r.callingUid + ")");
+ return false;
+ }
+ return true;
+ }
+
+ private boolean checkOpForManifestReceiver(int opCode, String appOp, BroadcastRecord r,
+ ResolveInfo info, ComponentName component) {
+ if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {
+ return checkOpForManifestReceiverInner(opCode, appOp, r, info, component, null);
+ } else {
+ // Attribution tags provided, noteOp each tag
+ for (String tag : info.activityInfo.attributionTags) {
+ if (!checkOpForManifestReceiverInner(opCode, appOp, r, info, component, tag)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private boolean checkOpForManifestReceiverInner(int opCode, String appOp, BroadcastRecord r,
+ ResolveInfo info, ComponentName component, String tag) {
+ if (mService.getAppOpsManager().checkOpNoThrow(appOp, info.activityInfo.applicationInfo.uid,
+ info.activityInfo.packageName, tag) != AppOpsManager.MODE_ALLOWED) {
+ Slog.w(TAG, "Appop Denial: receiving "
+ + r.intent + " to "
+ + component.flattenToShortString()
+ + " requires appop " + AppOpsManager.opToName(opCode)
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")");
return false;
@@ -694,9 +762,10 @@ public class BroadcastSkipPolicy {
return mPermissionManager;
}
- private boolean hasPermissionForDataDelivery(
+ private boolean hasPermission(
@NonNull String permission,
@NonNull String message,
+ boolean preflight,
@NonNull AttributionSource... attributionSources) {
final PermissionManager permissionManager = getPermissionManager();
if (permissionManager == null) {
@@ -704,9 +773,14 @@ public class BroadcastSkipPolicy {
}
for (AttributionSource attributionSource : attributionSources) {
- final int permissionCheckResult =
- permissionManager.checkPermissionForDataDelivery(
- permission, attributionSource, message);
+ final int permissionCheckResult;
+ if (preflight) {
+ permissionCheckResult = permissionManager.checkPermissionForPreflight(
+ permission, attributionSource);
+ } else {
+ permissionCheckResult = permissionManager.checkPermissionForDataDelivery(
+ permission, attributionSource, message);
+ }
if (permissionCheckResult != PackageManager.PERMISSION_GRANTED) {
return false;
}
diff --git a/services/core/java/com/android/server/am/broadcasts_flags.aconfig b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
index 7f169db7dcec..68e21a35a531 100644
--- a/services/core/java/com/android/server/am/broadcasts_flags.aconfig
+++ b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
@@ -15,4 +15,15 @@ flag {
description: "Limit the scope of receiver priorities to within a process"
is_fixed_read_only: true
bug: "369487976"
+}
+
+flag {
+ name: "avoid_note_op_at_enqueue"
+ namespace: "backstage_power"
+ description: "Avoid triggering noteOp while enqueueing a broadcast"
+ is_fixed_read_only: true
+ bug: "268016162"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c644597287d4..0f1228f44b0d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11407,7 +11407,7 @@ public class AudioService extends IAudioService.Stub
/** see {@link AudioManager#getFocusDuckedUidsForTest()} */
@Override
- @EnforcePermission("android.permission.QUERY_AUDIO_STATE")
+ @EnforcePermission(QUERY_AUDIO_STATE)
public @NonNull List<Integer> getFocusDuckedUidsForTest() {
super.getFocusDuckedUidsForTest_enforcePermission();
return mPlaybackMonitor.getFocusDuckedUids();
@@ -11434,7 +11434,7 @@ public class AudioService extends IAudioService.Stub
* @see AudioManager#getFocusFadeOutDurationForTest()
* @return the fade out duration, in ms
*/
- @EnforcePermission("android.permission.QUERY_AUDIO_STATE")
+ @EnforcePermission(QUERY_AUDIO_STATE)
public long getFocusFadeOutDurationForTest() {
super.getFocusFadeOutDurationForTest_enforcePermission();
return mMediaFocusControl.getFocusFadeOutDurationForTest();
@@ -11447,7 +11447,7 @@ public class AudioService extends IAudioService.Stub
* @return the time gap after a fade out completion on focus loss, and fade in start, in ms
*/
@Override
- @EnforcePermission("android.permission.QUERY_AUDIO_STATE")
+ @EnforcePermission(QUERY_AUDIO_STATE)
public long getFocusUnmuteDelayAfterFadeOutForTest() {
super.getFocusUnmuteDelayAfterFadeOutForTest_enforcePermission();
return mMediaFocusControl.getFocusUnmuteDelayAfterFadeOutForTest();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 80c2d415d370..5a6d7a245f56 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2937,28 +2937,20 @@ public class UserManagerService extends IUserManager.Stub {
int flags = UserManager.SWITCHABILITY_STATUS_OK;
- t.traceBegin("TM.isInCall");
- final long identity = Binder.clearCallingIdentity();
- try {
- final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
- if (com.android.internal.telephony.flags
- .Flags.enforceTelephonyFeatureMappingForPublicApis()) {
- if (mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TELECOM)) {
- if (telecomManager != null && telecomManager.isInCall()) {
- flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
- }
- }
- } else {
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM)) {
+ t.traceBegin("TM.isInCall");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final TelecomManager telecomManager = mContext.getSystemService(
+ TelecomManager.class);
if (telecomManager != null && telecomManager.isInCall()) {
flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
+ t.traceEnd();
}
- t.traceEnd();
-
t.traceBegin("hasUserRestriction-DISALLOW_USER_SWITCH");
if (mLocalService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)) {
flags |= UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index fab19b6b8201..1afbb34c5f09 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -160,8 +160,10 @@ public interface StatusBarManagerInternal {
* @param displayId The changed display Id.
* @param rootDisplayAreaId The changed display area Id.
* @param isImmersiveMode {@code true} if the display area get into immersive mode.
+ * @param windowType The window type of the controlling window.
*/
- void immersiveModeChanged(int displayId, int rootDisplayAreaId, boolean isImmersiveMode);
+ void immersiveModeChanged(int displayId, int rootDisplayAreaId, boolean isImmersiveMode,
+ int windowType);
/**
* Show a rotation suggestion that a user may approve to rotate the screen.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index da9d01675984..798c794edaf5 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -732,7 +732,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void immersiveModeChanged(int displayId, int rootDisplayAreaId,
- boolean isImmersiveMode) {
+ boolean isImmersiveMode, int windowType) {
if (mBar == null) {
return;
}
@@ -746,7 +746,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
if (!CLIENT_TRANSIENT) {
// Only call from here when the client transient is not enabled.
try {
- mBar.immersiveModeChanged(rootDisplayAreaId, isImmersiveMode);
+ mBar.immersiveModeChanged(rootDisplayAreaId, isImmersiveMode, windowType);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 124097938ff8..57b82c38c5b8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2326,13 +2326,16 @@ final class ActivityRecord extends WindowToken {
if (isActivityTypeHome()) {
// The snapshot of home is only used once because it won't be updated while screen
// is on (see {@link TaskSnapshotController#screenTurningOff}).
- mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
final Transition transition = mTransitionController.getCollectingTransition();
if (transition != null && (transition.getFlags()
& WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {
+ mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
// Only use snapshot of home as starting window when unlocking directly.
return false;
}
+ // Add a reference before removing snapshot from cache.
+ snapshot.addReference(TaskSnapshot.REFERENCE_WRITE_TO_PARCEL);
+ mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
}
return createSnapshot(snapshot, typeParameter);
}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index aed5e140703c..f51e60c101e4 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -144,9 +144,9 @@ public class BackgroundActivityStartController {
.setPendingIntentCreatorBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
- private ActivityTaskManagerService mService;
+ private final ActivityTaskManagerService mService;
- private ActivityTaskSupervisor mSupervisor;
+ private final ActivityTaskSupervisor mSupervisor;
@GuardedBy("mStrictModeBalCallbacks")
private final SparseArray<ArrayMap<IBinder, IBackgroundActivityLaunchCallback>>
mStrictModeBalCallbacks = new SparseArray<>();
@@ -279,16 +279,24 @@ public class BackgroundActivityStartController {
mSupervisor = supervisor;
}
+ private ActivityTaskManagerService getService() {
+ return mService;
+ }
+
+ private ActivityTaskSupervisor getSupervisor() {
+ return mSupervisor;
+ }
+
private boolean isHomeApp(int uid, @Nullable String packageName) {
- if (mService.mHomeProcess != null) {
+ if (getService().mHomeProcess != null) {
// Fast check
- return uid == mService.mHomeProcess.mUid;
+ return uid == getService().mHomeProcess.mUid;
}
if (packageName == null) {
return false;
}
ComponentName activity =
- mService.getPackageManagerInternalLocked()
+ getService().getPackageManagerInternalLocked()
.getDefaultHomeActivity(UserHandle.getUserId(uid));
return activity != null && packageName.equals(activity.getPackageName());
}
@@ -342,7 +350,8 @@ public class BackgroundActivityStartController {
mAllowBalExemptionForSystemProcess = allowBalExemptionForSystemProcess;
mOriginatingPendingIntent = originatingPendingIntent;
mIntent = intent;
- mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
+ mRealCallingPackage = getService().getPackageNameIfUnique(realCallingUid,
+ realCallingPid);
mIsCallForResult = resultRecord != null;
mCheckedOptions = checkedOptions;
@BackgroundActivityStartMode int callerBackgroundActivityStartMode =
@@ -401,13 +410,13 @@ public class BackgroundActivityStartController {
checkedOptions, realCallingUid, mRealCallingPackage);
}
- mAppSwitchState = mService.getBalAppSwitchesState();
- mCallingUidProcState = mService.mActiveUids.getUidState(callingUid);
+ mAppSwitchState = getService().getBalAppSwitchesState();
+ mCallingUidProcState = getService().mActiveUids.getUidState(callingUid);
mIsCallingUidPersistentSystemProcess =
mCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
mCallingUidHasVisibleActivity =
- mService.mVisibleActivityProcessTracker.hasVisibleActivity(callingUid);
- mCallingUidHasNonAppVisibleWindow = mService.mActiveUids.hasNonAppVisibleWindow(
+ getService().mVisibleActivityProcessTracker.hasVisibleActivity(callingUid);
+ mCallingUidHasNonAppVisibleWindow = getService().mActiveUids.hasNonAppVisibleWindow(
callingUid);
if (realCallingUid == NO_PROCESS_UID) {
// no process provided
@@ -422,16 +431,17 @@ public class BackgroundActivityStartController {
mRealCallingUidHasNonAppVisibleWindow = mCallingUidHasNonAppVisibleWindow;
// In the PendingIntent case callerApp is not passed in, so resolve it ourselves.
mRealCallerApp = callerApp == null
- ? mService.getProcessController(realCallingPid, realCallingUid)
+ ? getService().getProcessController(realCallingPid, realCallingUid)
: callerApp;
mIsRealCallingUidPersistentSystemProcess = mIsCallingUidPersistentSystemProcess;
} else {
- mRealCallingUidProcState = mService.mActiveUids.getUidState(realCallingUid);
+ mRealCallingUidProcState = getService().mActiveUids.getUidState(realCallingUid);
mRealCallingUidHasVisibleActivity =
- mService.mVisibleActivityProcessTracker.hasVisibleActivity(realCallingUid);
+ getService().mVisibleActivityProcessTracker.hasVisibleActivity(
+ realCallingUid);
mRealCallingUidHasNonAppVisibleWindow =
- mService.mActiveUids.hasNonAppVisibleWindow(realCallingUid);
- mRealCallerApp = mService.getProcessController(realCallingPid, realCallingUid);
+ getService().mActiveUids.hasNonAppVisibleWindow(realCallingUid);
+ mRealCallerApp = getService().getProcessController(realCallingPid, realCallingUid);
mIsRealCallingUidPersistentSystemProcess =
mRealCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
}
@@ -481,7 +491,7 @@ public class BackgroundActivityStartController {
if (uid == 0) {
return "root[debugOnly]";
}
- String name = mService.getPackageManagerInternalLocked().getNameForUid(uid);
+ String name = getService().getPackageManagerInternalLocked().getNameForUid(uid);
if (name == null) {
name = "uid=" + uid;
}
@@ -783,7 +793,7 @@ public class BackgroundActivityStartController {
Process.getAppUidForSdkSandboxUid(state.mRealCallingUid);
// realCallingSdkSandboxUidToAppUid should probably just be used instead (or in addition
// to realCallingUid when calculating resultForRealCaller below.
- if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
+ if (getService().hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
state.setResultForRealCaller(new BalVerdict(BAL_ALLOW_SDK_SANDBOX,
/*background*/ false,
"uid in SDK sandbox has visible (non-toast) window"));
@@ -1000,30 +1010,28 @@ public class BackgroundActivityStartController {
* or {@link #BAL_BLOCK} if the launch should be blocked
*/
BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) {
+ boolean evaluateVisibleOnly = balAdditionalStartModes()
+ && state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
+ if (evaluateVisibleOnly) {
+ return evaluateChain(state, mCheckCallerVisible, mCheckCallerNonAppVisible,
+ mCheckCallerProcessAllowsForeground);
+ }
if (state.isPendingIntent()) {
// PendingIntents should mostly be allowed by the sender (real caller) or a permission
// the creator of the PendingIntent has. Visibility should be the exceptional case, so
// test it last (this does not change the result, just the bal code).
- BalVerdict result = BalVerdict.BLOCK;
- if (!(balAdditionalStartModes()
- && state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE)) {
- result = checkBackgroundActivityStartAllowedByCallerInBackground(state);
- }
- if (result == BalVerdict.BLOCK) {
- result = checkBackgroundActivityStartAllowedByCallerInForeground(state);
-
- }
- return result;
- } else {
- BalVerdict result = checkBackgroundActivityStartAllowedByCallerInForeground(state);
- if (result == BalVerdict.BLOCK && !(balAdditionalStartModes()
- && state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE)) {
- result = checkBackgroundActivityStartAllowedByCallerInBackground(state);
- }
- return result;
+ return evaluateChain(state, mCheckCallerIsAllowlistedUid,
+ mCheckCallerIsAllowlistedComponent, mCheckCallerHasBackgroundPermission,
+ mCheckCallerHasSawPermission, mCheckCallerHasBgStartAppOp,
+ mCheckCallerProcessAllowsBackground, mCheckCallerVisible,
+ mCheckCallerNonAppVisible, mCheckCallerProcessAllowsForeground);
}
+ return evaluateChain(state, mCheckCallerVisible, mCheckCallerNonAppVisible,
+ mCheckCallerProcessAllowsForeground, mCheckCallerIsAllowlistedUid,
+ mCheckCallerIsAllowlistedComponent, mCheckCallerHasBackgroundPermission,
+ mCheckCallerHasSawPermission, mCheckCallerHasBgStartAppOp,
+ mCheckCallerProcessAllowsBackground);
}
interface BalExemptionCheck {
@@ -1061,7 +1069,7 @@ public class BackgroundActivityStartController {
if (state.mCallingUidHasNonAppVisibleWindow) {
return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
/*background*/ false, "callingUid has non-app visible window "
- + mService.mActiveUids.getNonAppVisibleWindowDetails(state.mCallingUid));
+ + getService().mActiveUids.getNonAppVisibleWindowDetails(state.mCallingUid));
}
return BalVerdict.BLOCK;
};
@@ -1090,7 +1098,7 @@ public class BackgroundActivityStartController {
final int callingAppId = UserHandle.getAppId(state.mCallingUid);
// IME should always be allowed to start activity, like IME settings.
final WindowState imeWindow =
- mService.mRootWindowContainer.getCurrentInputMethodWindow();
+ getService().mRootWindowContainer.getCurrentInputMethodWindow();
if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ false,
@@ -1104,23 +1112,23 @@ public class BackgroundActivityStartController {
}
// don't abort if the caller has the same uid as the recents component
- if (mSupervisor.mRecentTasks.isCallerRecents(state.mCallingUid)) {
+ if (getSupervisor().mRecentTasks.isCallerRecents(state.mCallingUid)) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ true, "Recents Component");
}
// don't abort if the callingUid is the device owner
- if (mService.isDeviceOwner(state.mCallingUid)) {
+ if (getService().isDeviceOwner(state.mCallingUid)) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ true, "Device Owner");
}
// don't abort if the callingUid is a affiliated profile owner
- if (mService.isAffiliatedProfileOwner(state.mCallingUid)) {
+ if (getService().isAffiliatedProfileOwner(state.mCallingUid)) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ true, "Affiliated Profile Owner");
}
// don't abort if the callingUid has companion device
final int callingUserId = UserHandle.getUserId(state.mCallingUid);
- if (mService.isAssociatedCompanionApp(callingUserId, state.mCallingUid)) {
+ if (getService().isAssociatedCompanionApp(callingUserId, state.mCallingUid)) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ true, "Companion App");
}
@@ -1138,7 +1146,7 @@ public class BackgroundActivityStartController {
};
private final BalExemptionCheck mCheckCallerHasSawPermission = state -> {
// don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
- if (mService.hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid,
+ if (getService().hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid,
state.mCallingPackage)) {
return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
/*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
@@ -1148,7 +1156,7 @@ public class BackgroundActivityStartController {
private final BalExemptionCheck mCheckCallerHasBgStartAppOp = state -> {
// don't abort if the callingUid and callingPackage have the
// OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop
- if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow(
+ if (isSystemExemptFlagEnabled() && getService().getAppOpsManager().checkOpNoThrow(
AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
state.mCallingUid, state.mCallingPackage) == AppOpsManager.MODE_ALLOWED) {
return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
@@ -1170,34 +1178,18 @@ public class BackgroundActivityStartController {
* @return A code denoting which BAL rule allows an activity to be started,
* or {@link #BAL_BLOCK} if the launch should be blocked
*/
- BalVerdict checkBackgroundActivityStartAllowedByCallerInForeground(BalState state) {
- return evaluateChain(state, mCheckCallerVisible, mCheckCallerNonAppVisible,
- mCheckCallerProcessAllowsForeground);
- }
-
- /**
- * @return A code denoting which BAL rule allows an activity to be started,
- * or {@link #BAL_BLOCK} if the launch should be blocked
- */
- BalVerdict checkBackgroundActivityStartAllowedByCallerInBackground(BalState state) {
- return evaluateChain(state, mCheckCallerIsAllowlistedUid,
- mCheckCallerIsAllowlistedComponent, mCheckCallerHasBackgroundPermission,
- mCheckCallerHasSawPermission, mCheckCallerHasBgStartAppOp,
- mCheckCallerProcessAllowsBackground);
- }
-
- /**
- * @return A code denoting which BAL rule allows an activity to be started,
- * or {@link #BAL_BLOCK} if the launch should be blocked
- */
BalVerdict checkBackgroundActivityStartAllowedByRealCaller(BalState state) {
- BalVerdict result = checkBackgroundActivityStartAllowedByRealCallerInForeground(state);
- if (result == BalVerdict.BLOCK && !(balAdditionalStartModes()
+ boolean evaluateVisibleOnly = balAdditionalStartModes()
&& state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
- == MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE)) {
- result = checkBackgroundActivityStartAllowedByRealCallerInBackground(state);
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
+ if (evaluateVisibleOnly) {
+ return evaluateChain(state, mCheckRealCallerVisible, mCheckRealCallerNonAppVisible,
+ mCheckRealCallerProcessAllowsBalForeground);
}
- return result;
+ return evaluateChain(state, mCheckRealCallerVisible, mCheckRealCallerNonAppVisible,
+ mCheckRealCallerProcessAllowsBalForeground, mCheckRealCallerBalPermission,
+ mCheckRealCallerSawPermission, mCheckRealCallerAllowlistedUid,
+ mCheckRealCallerAllowlistedComponent, mCheckRealCallerProcessAllowsBalBackground);
}
private final BalExemptionCheck mCheckRealCallerVisible = state -> {
@@ -1218,21 +1210,20 @@ public class BackgroundActivityStartController {
if (state.mRealCallingUidHasNonAppVisibleWindow) {
return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
/*background*/ false, "realCallingUid has non-app visible window "
- + mService.mActiveUids.getNonAppVisibleWindowDetails(state.mRealCallingUid));
+ + getService().mActiveUids.getNonAppVisibleWindowDetails(
+ state.mRealCallingUid));
}
return BalVerdict.BLOCK;
};
- private final BalExemptionCheck mCheckRealCallerProcessAllowsBalForeground = state -> {
- // Don't abort if the realCallerApp or other processes of that uid are considered to be in
- // the foreground.
- return checkProcessAllowsBal(state.mRealCallerApp, state, BAL_CHECK_FOREGROUND);
- };
+ // Don't abort if the realCallerApp or other processes of that uid are considered to be in
+ // the foreground.
+ private final BalExemptionCheck mCheckRealCallerProcessAllowsBalForeground =
+ state -> checkProcessAllowsBal(state.mRealCallerApp, state, BAL_CHECK_FOREGROUND);
- private final BalExemptionCheck mCheckRealCallerProcessAllowsBalBackground = state -> {
- // don't abort if the callerApp or other processes of that uid are allowed in any way
- return checkProcessAllowsBal(state.mRealCallerApp, state, BAL_CHECK_BACKGROUND);
- };
+ // don't abort if the callerApp or other processes of that uid are allowed in any way
+ private final BalExemptionCheck mCheckRealCallerProcessAllowsBalBackground =
+ state -> checkProcessAllowsBal(state.mRealCallerApp, state, BAL_CHECK_BACKGROUND);
private final BalExemptionCheck mCheckRealCallerBalPermission = state -> {
boolean allowAlways = state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
@@ -1251,7 +1242,7 @@ public class BackgroundActivityStartController {
== MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
// don't abort if the realCallingUid has SYSTEM_ALERT_WINDOW permission
if (allowAlways
- && mService.hasSystemAlertWindowPermission(state.mRealCallingUid,
+ && getService().hasSystemAlertWindowPermission(state.mRealCallingUid,
state.mRealCallingPid, state.mRealCallingPackage)) {
return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
/*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
@@ -1276,7 +1267,7 @@ public class BackgroundActivityStartController {
private final BalExemptionCheck mCheckRealCallerAllowlistedComponent = state -> {
// don't abort if the realCallingUid is an associated companion app
- if (mService.isAssociatedCompanionApp(
+ if (getService().isAssociatedCompanionApp(
UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ false,
@@ -1285,25 +1276,6 @@ public class BackgroundActivityStartController {
return BalVerdict.BLOCK;
};
- /**
- * @return A code denoting which BAL rule allows an activity to be started,
- * or {@link #BAL_BLOCK} if the launch should be blocked
- */
- BalVerdict checkBackgroundActivityStartAllowedByRealCallerInForeground(BalState state) {
- return evaluateChain(state, mCheckRealCallerVisible, mCheckRealCallerNonAppVisible,
- mCheckRealCallerProcessAllowsBalForeground);
- }
-
- /**
- * @return A code denoting which BAL rule allows an activity to be started,
- * or {@link #BAL_BLOCK} if the launch should be blocked
- */
- BalVerdict checkBackgroundActivityStartAllowedByRealCallerInBackground(BalState state) {
- return evaluateChain(state, mCheckRealCallerBalPermission, mCheckRealCallerSawPermission,
- mCheckRealCallerAllowlistedUid, mCheckRealCallerAllowlistedComponent,
- mCheckRealCallerProcessAllowsBalBackground);
- }
-
@VisibleForTesting boolean hasBalPermission(int uid, int pid) {
return ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND,
pid, uid) == PERMISSION_GRANTED;
@@ -1329,7 +1301,7 @@ public class BackgroundActivityStartController {
} else {
// only if that one wasn't allowed, check the other ones
final ArraySet<WindowProcessController> uidProcesses =
- mService.mProcessMap.getProcesses(app.mUid);
+ getService().mProcessMap.getProcesses(app.mUid);
if (uidProcesses != null) {
for (int i = uidProcesses.size() - 1; i >= 0; i--) {
final WindowProcessController proc = uidProcesses.valueAt(i);
@@ -1500,7 +1472,7 @@ public class BackgroundActivityStartController {
if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
+ (enforceBlock ? " blocked " : " would block ")
- + getApplicationLabel(mService.mContext.getPackageManager(),
+ + getApplicationLabel(getService().mContext.getPackageManager(),
launchedFromPackageName);
showToast(toastText);
@@ -1522,7 +1494,7 @@ public class BackgroundActivityStartController {
}
@VisibleForTesting void showToast(String toastText) {
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ UiThread.getHandler().post(() -> Toast.makeText(getService().mContext,
toastText, Toast.LENGTH_LONG).show());
}
@@ -1599,7 +1571,7 @@ public class BackgroundActivityStartController {
return;
}
- String packageName = mService.mContext.getPackageManager().getNameForUid(callingUid);
+ String packageName = getService().mContext.getPackageManager().getNameForUid(callingUid);
BalState state = new BalState(callingUid, callingPid, packageName, INVALID_UID,
INVALID_PID, null, null, false, null, null, ActivityOptions.makeBasic());
@BalCode int balCode = checkBackgroundActivityStartAllowedByCaller(state).mCode;
@@ -1660,7 +1632,7 @@ public class BackgroundActivityStartController {
boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
.shouldRestrictActivitySwitch(callingUid) && bas.mTopActivityOptedIn;
- PackageManager pm = mService.mContext.getPackageManager();
+ PackageManager pm = getService().mContext.getPackageManager();
String callingPackage = pm.getNameForUid(callingUid);
final CharSequence callingLabel;
if (callingPackage == null) {
@@ -1821,7 +1793,7 @@ public class BackgroundActivityStartController {
return bas.optedIn(ar);
}
- PackageManager pm = mService.mContext.getPackageManager();
+ PackageManager pm = getService().mContext.getPackageManager();
ApplicationInfo applicationInfo;
final int sourceUserId = UserHandle.getUserId(sourceUid);
@@ -1878,7 +1850,7 @@ public class BackgroundActivityStartController {
if (sourceRecord == null) {
joiner.add(prefix + "Source Package: " + targetRecord.launchedFromPackage);
- String realCallingPackage = mService.mContext.getPackageManager().getNameForUid(
+ String realCallingPackage = getService().mContext.getPackageManager().getNameForUid(
realCallingUid);
joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
} else {
@@ -1913,7 +1885,7 @@ public class BackgroundActivityStartController {
joiner.add(prefix + "BalCode: " + balCodeToString(balCode));
joiner.add(prefix + "Allowed By Grace Period: " + allowedByGracePeriod);
joiner.add(prefix + "LastResumedActivity: "
- + recordToString.apply(mService.mLastResumedActivity));
+ + recordToString.apply(getService().mLastResumedActivity));
joiner.add(prefix + "System opted into enforcement: " + asmOptSystemIntoEnforcement());
if (mTopFinishedActivity != null) {
@@ -1986,7 +1958,7 @@ public class BackgroundActivityStartController {
}
private BalVerdict statsLog(BalVerdict finalVerdict, BalState state) {
- if (finalVerdict.blocks() && mService.isActivityStartsLoggingEnabled()) {
+ if (finalVerdict.blocks() && getService().isActivityStartsLoggingEnabled()) {
// log aborted activity start to TRON
mSupervisor
.getActivityMetricsLogger()
@@ -2222,7 +2194,7 @@ public class BackgroundActivityStartController {
return -1;
}
try {
- PackageManager pm = mService.mContext.getPackageManager();
+ PackageManager pm = getService().mContext.getPackageManager();
return pm.getTargetSdkVersion(packageName);
} catch (Exception e) {
return -1;
@@ -2243,8 +2215,8 @@ public class BackgroundActivityStartController {
this.mLaunchCount = entry == null || !ar.isUid(entry.mUid) ? 1 : entry.mLaunchCount + 1;
this.mDebugInfo = getDebugStringForActivityRecord(ar);
- mService.mH.postDelayed(() -> {
- synchronized (mService.mGlobalLock) {
+ getService().mH.postDelayed(() -> {
+ synchronized (getService().mGlobalLock) {
if (mTaskIdToFinishedActivity.get(taskId) == this) {
mTaskIdToFinishedActivity.remove(taskId);
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 4908df025dd1..ec5b503fbb9b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2564,7 +2564,7 @@ public class DisplayPolicy {
final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId;
// TODO(b/277290737): Move this to the client side, instead of using a proxy.
callStatusBarSafely(statusBar -> statusBar.immersiveModeChanged(getDisplayId(),
- rootDisplayAreaId, isImmersiveMode));
+ rootDisplayAreaId, isImmersiveMode, win.getWindowType()));
}
// Show transient bars for panic if needed.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 345aca8f6a5c..ce87ca5bf312 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2655,7 +2655,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// The client gave us a touchable region and so first
// we calculate the untouchable region, then punch that out of our
// expanded modal region.
- mTmpRegion.set(0, 0, frame.right, frame.bottom);
+ mTmpRegion.set(0, 0, frame.width(), frame.height());
mTmpRegion.op(mGivenTouchableRegion, Region.Op.DIFFERENCE);
region.op(mTmpRegion, Region.Op.DIFFERENCE);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index 5eb23a24908d..12866481b320 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -16,29 +16,43 @@
package com.android.server.am;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.BackgroundStartPrivileges;
+import android.app.BroadcastOptions;
+import android.app.SystemServiceRegistry;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
+import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.TestLooperManager;
import android.os.UserHandle;
+import android.permission.IPermissionManager;
+import android.permission.PermissionManager;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -47,7 +61,6 @@ import android.util.SparseArray;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.util.FrameworkStatsLog;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.AlarmManagerInternal;
@@ -55,6 +68,7 @@ import com.android.server.DropBoxManagerInternal;
import com.android.server.LocalServices;
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
+import com.android.server.firewall.IntentFirewall;
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.Rule;
@@ -63,8 +77,11 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
public abstract class BaseBroadcastQueueTest {
@@ -97,6 +114,8 @@ public abstract class BaseBroadcastQueueTest {
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.spyStatic(FrameworkStatsLog.class)
.spyStatic(ProcessList.class)
+ .spyStatic(SystemServiceRegistry.class)
+ .mockStatic(AppGlobals.class)
.build();
@@ -119,6 +138,16 @@ public abstract class BaseBroadcastQueueTest {
ProcessList mProcessList;
@Mock
PlatformCompat mPlatformCompat;
+ @Mock
+ IntentFirewall mIntentFirewall;
+ @Mock
+ IPackageManager mIPackageManager;
+ @Mock
+ AppOpsManager mAppOpsManager;
+ @Mock
+ IPermissionManager mIPermissionManager;
+ @Mock
+ PermissionManager mPermissionManager;
@Mock
AppStartInfoTracker mAppStartInfoTracker;
@@ -167,22 +196,22 @@ public abstract class BaseBroadcastQueueTest {
return getUidForPackage(invocation.getArgument(0));
}).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
+ final Context spyContext = spy(mContext);
+ doReturn(mPermissionManager).when(spyContext).getSystemService(PermissionManager.class);
final ActivityManagerService realAms = new ActivityManagerService(
- new TestInjector(mContext), mServiceThreadRule.getThread());
+ new TestInjector(spyContext), mServiceThreadRule.getThread());
realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
realAms.mOomAdjuster.mCachedAppOptimizer = mock(CachedAppOptimizer.class);
realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
- ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
+ doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
realAms.mPackageManagerInt = mPackageManagerInt;
realAms.mUsageStatsService = mUsageStatsManagerInt;
realAms.mProcessesReady = true;
mAms = spy(realAms);
- mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
- doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
- doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
+ mSkipPolicy = createBroadcastSkipPolicy();
doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
@@ -198,6 +227,14 @@ public abstract class BaseBroadcastQueueTest {
}
}
+ public BroadcastSkipPolicy createBroadcastSkipPolicy() {
+ final BroadcastSkipPolicy skipPolicy = spy(new BroadcastSkipPolicy(mAms));
+ doReturn(null).when(skipPolicy).shouldSkipAtEnqueueMessage(any(), any());
+ doReturn(null).when(skipPolicy).shouldSkipMessage(any(), any());
+ doReturn(false).when(skipPolicy).disallowBackgroundStart(any());
+ return skipPolicy;
+ }
+
static int getUidForPackage(@NonNull String packageName) {
switch (packageName) {
case PACKAGE_ANDROID: return android.os.Process.SYSTEM_UID;
@@ -240,6 +277,11 @@ public abstract class BaseBroadcastQueueTest {
public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
return null;
}
+
+ @Override
+ public IntentFirewall getIntentFirewall() {
+ return mIntentFirewall;
+ }
}
abstract String getTag();
@@ -281,24 +323,35 @@ public abstract class BaseBroadcastQueueTest {
ri.activityInfo.packageName = packageName;
ri.activityInfo.processName = processName;
ri.activityInfo.name = name;
+ ri.activityInfo.exported = true;
ri.activityInfo.applicationInfo = makeApplicationInfo(packageName, processName, userId);
return ri;
}
+ // TODO: Reuse BroadcastQueueTest.makeActiveProcessRecord()
+ @SuppressWarnings("GuardedBy")
+ ProcessRecord makeProcessRecord(ApplicationInfo info) {
+ final ProcessRecord r = spy(new ProcessRecord(mAms, info, info.processName, info.uid));
+ r.setPid(mNextPid.incrementAndGet());
+ ProcessRecord.updateProcessRecordNodes(r);
+ return r;
+ }
+
BroadcastFilter makeRegisteredReceiver(ProcessRecord app) {
return makeRegisteredReceiver(app, 0);
}
BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) {
final ReceiverList receiverList = mRegisteredReceivers.get(app.getPid());
- return makeRegisteredReceiver(receiverList, priority);
+ return makeRegisteredReceiver(receiverList, priority, null);
}
- static BroadcastFilter makeRegisteredReceiver(ReceiverList receiverList, int priority) {
+ static BroadcastFilter makeRegisteredReceiver(ReceiverList receiverList, int priority,
+ String requiredPermission) {
final IntentFilter filter = new IntentFilter();
filter.setPriority(priority);
final BroadcastFilter res = new BroadcastFilter(filter, receiverList,
- receiverList.app.info.packageName, null, null, null, receiverList.uid,
+ receiverList.app.info.packageName, null, null, requiredPermission, receiverList.uid,
receiverList.userId, false, false, true, receiverList.app.info,
mock(PlatformCompat.class));
receiverList.add(res);
@@ -313,4 +366,62 @@ public abstract class BaseBroadcastQueueTest {
ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) {
return test -> (test.uid == uid);
}
+
+ static final class BroadcastRecordBuilder {
+ private BroadcastQueue mQueue = mock(BroadcastQueue.class);
+ private Intent mIntent = mock(Intent.class);
+ private ProcessRecord mProcessRecord = mock(ProcessRecord.class);
+ private String mCallerPackage;
+ private String mCallerFeatureId;
+ private int mCallingPid;
+ private int mCallingUid;
+ private boolean mCallerInstantApp;
+ private String mResolvedType;
+ private String[] mRequiredPermissions;
+ private String[] mExcludedPermissions;
+ private String[] mExcludedPackages;
+ private int mAppOp;
+ private BroadcastOptions mOptions = BroadcastOptions.makeBasic();
+ private List mReceivers = Collections.emptyList();
+ private ProcessRecord mResultToApp;
+ private IIntentReceiver mResultTo;
+ private int mResultCode = Activity.RESULT_OK;
+ private String mResultData;
+ private Bundle mResultExtras;
+ private boolean mSerialized;
+ private boolean mSticky;
+ private boolean mInitialSticky;
+ private int mUserId = UserHandle.USER_SYSTEM;
+ private BackgroundStartPrivileges mBackgroundStartPrivileges =
+ BackgroundStartPrivileges.NONE;
+ private boolean mTimeoutExempt;
+ private BiFunction<Integer, Bundle, Bundle> mFilterExtrasForReceiver;
+ private int mCallerAppProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
+ private PlatformCompat mPlatformCompat = mock(PlatformCompat.class);
+
+ public BroadcastRecordBuilder setIntent(Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ public BroadcastRecordBuilder setRequiredPermissions(String[] requiredPermissions) {
+ mRequiredPermissions = requiredPermissions;
+ return this;
+ }
+
+ public BroadcastRecordBuilder setAppOp(int appOp) {
+ mAppOp = appOp;
+ return this;
+ }
+
+ public BroadcastRecord build() {
+ return new BroadcastRecord(mQueue, mIntent, mProcessRecord, mCallerPackage,
+ mCallerFeatureId, mCallingPid, mCallingUid, mCallerInstantApp, mResolvedType,
+ mRequiredPermissions, mExcludedPermissions, mExcludedPackages, mAppOp,
+ mOptions, mReceivers, mResultToApp, mResultTo, mResultCode, mResultData,
+ mResultExtras, mSerialized, mSticky, mInitialSticky, mUserId,
+ mBackgroundStartPrivileges, mTimeoutExempt, mFilterExtrasForReceiver,
+ mCallerAppProcState, mPlatformCompat);
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
index 409706b14c56..b32ce49d049d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
@@ -1803,8 +1803,10 @@ public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest {
assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
}
+ @SuppressWarnings("GuardedBy")
+ @DisableFlags(Flags.FLAG_AVOID_NOTE_OP_AT_ENQUEUE)
@Test
- public void testSkipPolicy_atEnqueueTime() throws Exception {
+ public void testSkipPolicy_atEnqueueTime_flagDisabled() throws Exception {
final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT);
final Object greenReceiver = makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN);
final Object redReceiver = makeManifestReceiver(PACKAGE_RED, CLASS_RED);
@@ -1839,6 +1841,44 @@ public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest {
verifyPendingRecords(redQueue, List.of(userPresent, timeTick));
}
+ @SuppressWarnings("GuardedBy")
+ @EnableFlags(Flags.FLAG_AVOID_NOTE_OP_AT_ENQUEUE)
+ @Test
+ public void testSkipPolicy_atEnqueueTime() throws Exception {
+ final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT);
+ final Object greenReceiver = makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN);
+ final Object redReceiver = makeManifestReceiver(PACKAGE_RED, CLASS_RED);
+
+ final BroadcastRecord userPresentRecord = makeBroadcastRecord(userPresent,
+ List.of(greenReceiver, redReceiver));
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick,
+ List.of(greenReceiver, redReceiver));
+
+ doAnswer(invocation -> {
+ final BroadcastRecord r = invocation.getArgument(0);
+ final Object o = invocation.getArgument(1);
+ if (userPresent.getAction().equals(r.intent.getAction())
+ && isReceiverEquals(o, greenReceiver)) {
+ return "receiver skipped by test";
+ }
+ return null;
+ }).when(mSkipPolicy).shouldSkipAtEnqueueMessage(any(BroadcastRecord.class), any());
+
+ mImpl.enqueueBroadcastLocked(userPresentRecord);
+ mImpl.enqueueBroadcastLocked(timeTickRecord);
+
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ // There should be only one broadcast for green process as the other would have
+ // been skipped.
+ verifyPendingRecords(greenQueue, List.of(timeTick));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ verifyPendingRecords(redQueue, List.of(userPresent, timeTick));
+ }
+
@DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testDeliveryDeferredForCached_flagDisabled() throws Exception {
@@ -2270,19 +2310,11 @@ public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest {
assertFalse(mImpl.isProcessFreezable(greenProcess));
}
- // TODO: Reuse BroadcastQueueTest.makeActiveProcessRecord()
- private ProcessRecord makeProcessRecord(ApplicationInfo info) {
- final ProcessRecord r = spy(new ProcessRecord(mAms, info, info.processName, info.uid));
- r.setPid(mNextPid.incrementAndGet());
- ProcessRecord.updateProcessRecordNodes(r);
- return r;
- }
-
BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) {
final IIntentReceiver receiver = mock(IIntentReceiver.class);
final ReceiverList receiverList = new ReceiverList(mAms, app, app.getPid(), app.info.uid,
UserHandle.getUserId(app.info.uid), receiver);
- return makeRegisteredReceiver(receiverList, priority);
+ return makeRegisteredReceiver(receiverList, priority, null /* requiredPermission */);
}
private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index ad35b25a0d74..3a9c99d57d71 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -2301,6 +2301,52 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
}
/**
+ * Verify that we skip broadcasts at enqueue if {@link BroadcastSkipPolicy} decides it
+ * should be skipped.
+ */
+ @EnableFlags(Flags.FLAG_AVOID_NOTE_OP_AT_ENQUEUE)
+ @Test
+ public void testSkipPolicy_atEnqueueTime() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final Object greenReceiver = makeRegisteredReceiver(receiverGreenApp);
+ final Object blueReceiver = makeRegisteredReceiver(receiverBlueApp);
+ final Object yellowReceiver = makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW);
+ final Object orangeReceiver = makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE);
+
+ doAnswer(invocation -> {
+ final BroadcastRecord r = invocation.getArgument(0);
+ final Object o = invocation.getArgument(1);
+ if (airplane.getAction().equals(r.intent.getAction())
+ && (isReceiverEquals(o, greenReceiver)
+ || isReceiverEquals(o, orangeReceiver))) {
+ return "test skipped receiver";
+ }
+ return null;
+ }).when(mSkipPolicy).shouldSkipAtEnqueueMessage(any(BroadcastRecord.class), any());
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver)));
+
+ waitForIdle();
+ // Verify that only blue and yellow receiver apps received the broadcast.
+ verifyScheduleRegisteredReceiver(never(), receiverGreenApp, USER_SYSTEM);
+ verify(mSkipPolicy, never()).shouldSkipMessage(any(BroadcastRecord.class),
+ eq(greenReceiver));
+ verifyScheduleRegisteredReceiver(receiverBlueApp, airplane);
+ final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW));
+ verifyScheduleReceiver(receiverYellowApp, airplane);
+ final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE,
+ getUidForPackage(PACKAGE_ORANGE));
+ assertNull(receiverOrangeApp);
+ verify(mSkipPolicy, never()).shouldSkipMessage(any(BroadcastRecord.class),
+ eq(orangeReceiver));
+ }
+
+ /**
* Verify broadcasts to runtime receivers in cached processes are deferred
* until that process leaves the cached state.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastSkipPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastSkipPolicyTest.java
new file mode 100644
index 000000000000..c8aad79edd12
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastSkipPolicyTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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.am;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.never;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class BroadcastSkipPolicyTest extends BaseBroadcastQueueTest {
+ private static final String TAG = "BroadcastSkipPolicyTest";
+
+ BroadcastSkipPolicy mBroadcastSkipPolicy;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mBroadcastSkipPolicy = new BroadcastSkipPolicy(mAms);
+
+ doReturn(true).when(mIntentFirewall).checkBroadcast(any(Intent.class),
+ anyInt(), anyInt(), nullable(String.class), anyInt());
+
+ doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
+ doReturn(true).when(mIPackageManager).isPackageAvailable(anyString(), anyInt());
+
+ doReturn(ActivityManager.APP_START_MODE_NORMAL).when(mAms).getAppStartModeLOSP(anyInt(),
+ anyString(), anyInt(), anyInt(), eq(true), eq(false), eq(false));
+
+ doReturn(mAppOpsManager).when(mAms).getAppOpsManager();
+ doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).checkOpNoThrow(anyString(),
+ anyInt(), anyString(), nullable(String.class));
+ doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).noteOpNoThrow(anyString(),
+ anyInt(), anyString(), nullable(String.class), anyString());
+
+ doReturn(mIPermissionManager).when(AppGlobals::getPermissionManager);
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mIPermissionManager).checkUidPermission(
+ anyInt(), anyString(), anyInt());
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
+ @Override
+ public BroadcastSkipPolicy createBroadcastSkipPolicy() {
+ return new BroadcastSkipPolicy(mAms);
+ }
+
+ @Test
+ public void testShouldSkipMessage_withManifestRcvr_withCompPerm_invokesNoteOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .build();
+ final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record,
+ makeManifestReceiverWithPermission(PACKAGE_GREEN, CLASS_GREEN,
+ Manifest.permission.PACKAGE_USAGE_STATS));
+ assertNull(msg);
+ verify(mAppOpsManager).noteOpNoThrow(
+ eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)),
+ eq(record.callingUid), eq(record.callerPackage), eq(record.callerFeatureId),
+ anyString());
+ verify(mAppOpsManager, never()).checkOpNoThrow(
+ anyString(), anyInt(), anyString(), nullable(String.class));
+ }
+
+ @Test
+ public void testShouldSkipMessage_withRegRcvr_withCompPerm_invokesNoteOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .build();
+ final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record,
+ makeRegisteredReceiver(receiverApp, 0 /* priority */,
+ Manifest.permission.PACKAGE_USAGE_STATS));
+ assertNull(msg);
+ verify(mAppOpsManager).noteOpNoThrow(
+ eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)),
+ eq(record.callingUid), eq(record.callerPackage), eq(record.callerFeatureId),
+ anyString());
+ verify(mAppOpsManager, never()).checkOpNoThrow(
+ anyString(), anyInt(), anyString(), nullable(String.class));
+ }
+
+ @Test
+ public void testShouldSkipAtEnqueueMessage_withManifestRcvr_withCompPerm_invokesCheckOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .build();
+ final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record,
+ makeManifestReceiverWithPermission(PACKAGE_GREEN, CLASS_GREEN,
+ Manifest.permission.PACKAGE_USAGE_STATS));
+ assertNull(msg);
+ verify(mAppOpsManager).checkOpNoThrow(
+ eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)),
+ eq(record.callingUid), eq(record.callerPackage), eq(record.callerFeatureId));
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ anyString(), anyInt(), anyString(), nullable(String.class), anyString());
+ }
+
+ @Test
+ public void testShouldSkipAtEnqueueMessage_withRegRcvr_withCompPerm_invokesCheckOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .build();
+ final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record,
+ makeRegisteredReceiver(receiverApp, 0 /* priority */,
+ Manifest.permission.PACKAGE_USAGE_STATS));
+ assertNull(msg);
+ verify(mAppOpsManager).checkOpNoThrow(
+ eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)),
+ eq(record.callingUid), eq(record.callerPackage), eq(record.callerFeatureId));
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ anyString(), anyInt(), anyString(), nullable(String.class), anyString());
+ }
+
+ @Test
+ public void testShouldSkipMessage_withManifestRcvr_withAppOp_invokesNoteOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .setAppOp(AppOpsManager.permissionToOpCode(Manifest.permission.PACKAGE_USAGE_STATS))
+ .build();
+ final ResolveInfo receiver = makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN);
+ final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record, receiver);
+ assertNull(msg);
+ verify(mAppOpsManager).noteOpNoThrow(
+ eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)),
+ eq(receiver.activityInfo.applicationInfo.uid),
+ eq(receiver.activityInfo.packageName), nullable(String.class), anyString());
+ verify(mAppOpsManager, never()).checkOpNoThrow(
+ anyString(), anyInt(), anyString(), nullable(String.class));
+ }
+
+ @Test
+ public void testShouldSkipMessage_withRegRcvr_withAppOp_invokesNoteOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .setAppOp(AppOpsManager.permissionToOpCode(Manifest.permission.PACKAGE_USAGE_STATS))
+ .build();
+ final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final BroadcastFilter filter = makeRegisteredReceiver(receiverApp, 0 /* priority */,
+ null /* requiredPermission */);
+ final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record, filter);
+ assertNull(msg);
+ verify(mAppOpsManager).noteOpNoThrow(
+ eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)),
+ eq(filter.receiverList.uid),
+ eq(filter.packageName), nullable(String.class), anyString());
+ verify(mAppOpsManager, never()).checkOpNoThrow(
+ anyString(), anyInt(), anyString(), nullable(String.class));
+ }
+
+ @Test
+ public void testShouldSkipAtEnqueueMessage_withManifestRcvr_withAppOp_invokesCheckOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .setAppOp(AppOpsManager.permissionToOpCode(Manifest.permission.PACKAGE_USAGE_STATS))
+ .build();
+ final ResolveInfo receiver = makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN);
+ final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record, receiver);
+ assertNull(msg);
+ verify(mAppOpsManager).checkOpNoThrow(
+ eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)),
+ eq(receiver.activityInfo.applicationInfo.uid),
+ eq(receiver.activityInfo.applicationInfo.packageName), nullable(String.class));
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ anyString(), anyInt(), anyString(), nullable(String.class), anyString());
+ }
+
+ @Test
+ public void testShouldSkipAtEnqueueMessage_withRegRcvr_withAppOp_invokesCheckOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .setAppOp(AppOpsManager.permissionToOpCode(Manifest.permission.PACKAGE_USAGE_STATS))
+ .build();
+ final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final BroadcastFilter filter = makeRegisteredReceiver(receiverApp, 0 /* priority */,
+ null /* requiredPermission */);
+ final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record, filter);
+ assertNull(msg);
+ verify(mAppOpsManager).checkOpNoThrow(
+ eq(AppOpsManager.permissionToOp(Manifest.permission.PACKAGE_USAGE_STATS)),
+ eq(filter.receiverList.uid),
+ eq(filter.packageName), nullable(String.class));
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ anyString(), anyInt(), anyString(), nullable(String.class), anyString());
+ }
+
+ @Test
+ public void testShouldSkipMessage_withManifestRcvr_withRequiredPerms_invokesNoteOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .setRequiredPermissions(new String[] {Manifest.permission.PACKAGE_USAGE_STATS})
+ .build();
+ final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record,
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN));
+ assertNull(msg);
+ verify(mPermissionManager).checkPermissionForDataDelivery(
+ eq(Manifest.permission.PACKAGE_USAGE_STATS), any(), anyString());
+ verify(mPermissionManager, never()).checkPermissionForPreflight(
+ eq(Manifest.permission.PACKAGE_USAGE_STATS), any());
+ }
+
+ @Test
+ public void testShouldSkipMessage_withRegRcvr_withRequiredPerms_invokesNoteOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .setRequiredPermissions(new String[] {Manifest.permission.PACKAGE_USAGE_STATS})
+ .build();
+ final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final String msg = mBroadcastSkipPolicy.shouldSkipMessage(record,
+ makeRegisteredReceiver(receiverApp, 0 /* priority */,
+ null /* requiredPermission */));
+ assertNull(msg);
+ verify(mPermissionManager).checkPermissionForDataDelivery(
+ eq(Manifest.permission.PACKAGE_USAGE_STATS), any(), anyString());
+ verify(mPermissionManager, never()).checkPermissionForPreflight(
+ eq(Manifest.permission.PACKAGE_USAGE_STATS), any());
+ }
+
+ @Test
+ public void testShouldSkipAtEnqueueMessage_withManifestRcvr_withRequiredPerms_invokesCheckOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .setRequiredPermissions(new String[] {Manifest.permission.PACKAGE_USAGE_STATS})
+ .build();
+ final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record,
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN));
+ assertNull(msg);
+ verify(mPermissionManager, never()).checkPermissionForDataDelivery(
+ eq(Manifest.permission.PACKAGE_USAGE_STATS), any(), anyString());
+ verify(mPermissionManager).checkPermissionForPreflight(
+ eq(Manifest.permission.PACKAGE_USAGE_STATS), any());
+ }
+
+ @Test
+ public void testShouldSkipAtEnqueueMessage_withRegRcvr_withRequiredPerms_invokesCheckOp() {
+ final BroadcastRecord record = new BroadcastRecordBuilder()
+ .setIntent(new Intent(Intent.ACTION_TIME_TICK))
+ .setRequiredPermissions(new String[] {Manifest.permission.PACKAGE_USAGE_STATS})
+ .build();
+ final ProcessRecord receiverApp = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final String msg = mBroadcastSkipPolicy.shouldSkipAtEnqueueMessage(record,
+ makeRegisteredReceiver(receiverApp, 0 /* priority */,
+ null /* requiredPermission */));
+ assertNull(msg);
+ verify(mPermissionManager, never()).checkPermissionForDataDelivery(
+ eq(Manifest.permission.PACKAGE_USAGE_STATS), any(), anyString());
+ verify(mPermissionManager).checkPermissionForPreflight(
+ eq(Manifest.permission.PACKAGE_USAGE_STATS), any());
+ }
+
+ private ResolveInfo makeManifestReceiverWithPermission(String packageName, String name,
+ String permission) {
+ final ResolveInfo resolveInfo = makeManifestReceiver(packageName, name);
+ resolveInfo.activityInfo.permission = permission;
+ return resolveInfo;
+ }
+
+ private BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority,
+ String requiredPermission) {
+ final IIntentReceiver receiver = mock(IIntentReceiver.class);
+ final ReceiverList receiverList = new ReceiverList(mAms, app, app.getPid(), app.info.uid,
+ UserHandle.getUserId(app.info.uid), receiver);
+ return makeRegisteredReceiver(receiverList, priority, requiredPermission);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/TransitionSubject.java b/services/tests/wmtests/src/com/android/server/TransitionSubject.java
new file mode 100644
index 000000000000..07026b98f226
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/TransitionSubject.java
@@ -0,0 +1,79 @@
+/*
+ * 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.wm;
+
+import android.annotation.Nullable;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TransitionSubject extends Subject {
+
+ @Nullable
+ private final Transition actual;
+
+ /**
+ * Internal constructor.
+ *
+ * @see TransitionSubject#assertThat(Transition)
+ */
+ private TransitionSubject(FailureMetadata metadata, @Nullable Transition actual) {
+ super(metadata, actual);
+ this.actual = actual;
+ }
+
+ /**
+ * In a fluent assertion chain, the argument to the "custom" overload of {@link
+ * StandardSubjectBuilder#about(CustomSubjectBuilder.Factory) about}, the method that specifies
+ * what kind of {@link Subject} to create.
+ */
+ public static Factory<TransitionSubject, Transition> transitions() {
+ return TransitionSubject::new;
+ }
+
+ /**
+ * Typical entry point for making assertions about Transitions.
+ *
+ * @see @Truth#assertThat(Object)
+ */
+ public static TransitionSubject assertThat(Transition transition) {
+ return Truth.assertAbout(transitions()).that(transition);
+ }
+
+ /**
+ * Converts to a {@link IterableSubject} containing {@link Transition#getFlags()} separated into
+ * a list of individual flags for assertions such as {@code flags().contains(TRANSIT_FLAG_XYZ)}.
+ *
+ * <p>If the subject is null, this will fail instead of returning a null subject.
+ */
+ public IterableSubject flags() {
+ isNotNull();
+
+ final List<Integer> sortedFlags = new ArrayList<>();
+ for (int i = 0; i < 32; i++) {
+ if ((actual.getFlags() & (1 << i)) != 0) {
+ sortedFlags.add((1 << i));
+ }
+ }
+ return com.google.common.truth.Truth.assertThat(sortedFlags);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index cfd501abbe8b..61ed0b53cdcf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -63,6 +63,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -80,6 +83,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFO
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
+import static com.android.server.wm.TransitionSubject.assertThat;
import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
@@ -147,6 +151,7 @@ import com.android.internal.logging.nano.MetricsProto;
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.WmDisplayCutout;
+import com.android.window.flags.Flags;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -2620,6 +2625,7 @@ public class DisplayContentTests extends WindowTestsBase {
final KeyguardController keyguard = mAtm.mKeyguardController;
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
final int displayId = mDisplayContent.getDisplayId();
+ final TestTransitionPlayer transitions = registerTestTransitionPlayer();
final BooleanSupplier keyguardShowing = () -> keyguard.isKeyguardShowing(displayId);
final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
@@ -2629,21 +2635,40 @@ public class DisplayContentTests extends WindowTestsBase {
keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
assertFalse(keyguardGoingAway.getAsBoolean());
assertFalse(appVisible.getAsBoolean());
+ transitions.flush();
// Start unlocking from AOD.
keyguard.keyguardGoingAway(displayId, 0x0 /* flags */);
assertTrue(keyguardGoingAway.getAsBoolean());
assertTrue(appVisible.getAsBoolean());
+ if (Flags.ensureKeyguardDoesTransitionStarting()) {
+ assertThat(transitions.mLastTransit).isNull();
+ } else {
+ assertThat(transitions.mLastTransit).flags()
+ .containsExactly(TRANSIT_FLAG_KEYGUARD_GOING_AWAY);
+ }
+ transitions.flush();
+
// Clear AOD. This does *not* clear the going-away status.
keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
assertTrue(keyguardGoingAway.getAsBoolean());
assertTrue(appVisible.getAsBoolean());
+ if (Flags.aodTransition()) {
+ assertThat(transitions.mLastTransit).flags()
+ .containsExactly(TRANSIT_FLAG_AOD_APPEARING);
+ } else {
+ assertThat(transitions.mLastTransit).isNull();
+ }
+ transitions.flush();
+
// Finish unlock
keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */);
assertFalse(keyguardGoingAway.getAsBoolean());
assertTrue(appVisible.getAsBoolean());
+
+ assertThat(transitions.mLastTransit).isNull();
}
@Test
@@ -2653,6 +2678,7 @@ public class DisplayContentTests extends WindowTestsBase {
final KeyguardController keyguard = mAtm.mKeyguardController;
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
final int displayId = mDisplayContent.getDisplayId();
+ final TestTransitionPlayer transitions = registerTestTransitionPlayer();
final BooleanSupplier keyguardShowing = () -> keyguard.isKeyguardShowing(displayId);
final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
@@ -2662,22 +2688,44 @@ public class DisplayContentTests extends WindowTestsBase {
keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
assertFalse(keyguardGoingAway.getAsBoolean());
assertFalse(appVisible.getAsBoolean());
+ transitions.flush();
// Start unlocking from AOD.
keyguard.keyguardGoingAway(displayId, 0x0 /* flags */);
assertTrue(keyguardGoingAway.getAsBoolean());
assertTrue(appVisible.getAsBoolean());
+ if (!Flags.ensureKeyguardDoesTransitionStarting()) {
+ assertThat(transitions.mLastTransit).flags()
+ .containsExactly(TRANSIT_FLAG_KEYGUARD_GOING_AWAY);
+ }
+ transitions.flush();
+
// Clear AOD. This does *not* clear the going-away status.
keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
assertTrue(keyguardGoingAway.getAsBoolean());
assertTrue(appVisible.getAsBoolean());
+ if (Flags.aodTransition()) {
+ assertThat(transitions.mLastTransit).flags()
+ .containsExactly(TRANSIT_FLAG_AOD_APPEARING);
+ } else {
+ assertThat(transitions.mLastTransit).isNull();
+ }
+ transitions.flush();
+
// Same API call a second time cancels the unlock, because AOD isn't changing.
keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
assertTrue(keyguardShowing.getAsBoolean());
assertFalse(keyguardGoingAway.getAsBoolean());
assertFalse(appVisible.getAsBoolean());
+
+ if (Flags.ensureKeyguardDoesTransitionStarting()) {
+ assertThat(transitions.mLastTransit).isNull();
+ } else {
+ assertThat(transitions.mLastTransit).flags()
+ .containsExactly(TRANSIT_FLAG_KEYGUARD_APPEARING);
+ }
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index c0642f5533eb..57ab13ffee89 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -2151,6 +2151,14 @@ public class WindowTestsBase extends SystemServiceTestsBase {
mLastRequest = null;
}
+ void flush() {
+ if (mLastTransit != null) {
+ start();
+ finish();
+ clear();
+ }
+ }
+
@Override
public void onTransitionReady(IBinder transitToken, TransitionInfo transitionInfo,
SurfaceControl.Transaction transaction, SurfaceControl.Transaction finishT)
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index ca4a643d7b20..ae7346eb6df4 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -1737,13 +1737,8 @@ public class EuiccManager {
private int getCardIdForDefaultEuicc() {
int cardId = TelephonyManager.UNINITIALIZED_CARD_ID;
- if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
- PackageManager pm = mContext.getPackageManager();
- if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
- TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
- cardId = tm.getCardIdForDefaultEuicc();
- }
- } else {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
cardId = tm.getCardIdForDefaultEuicc();
}
diff --git a/tools/processors/view_inspector/OWNERS b/tools/processors/view_inspector/OWNERS
index 0473f54e57ca..38d21e141f43 100644
--- a/tools/processors/view_inspector/OWNERS
+++ b/tools/processors/view_inspector/OWNERS
@@ -1,3 +1,2 @@
alanv@google.com
-ashleyrose@google.com
-aurimas@google.com \ No newline at end of file
+aurimas@google.com