diff options
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 |