diff options
87 files changed, 911 insertions, 4385 deletions
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 844e52c3ecf2..b0070c5faa36 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -207,7 +207,7 @@ BootAnimation::BootAnimation(sp<Callbacks> callbacks) : Thread(false), mLooper(new Looper(false)), mClockEnabled(true), mTimeIsAccurate(false), mTimeFormat12Hour(false), mTimeCheckThread(nullptr), mCallbacks(callbacks) { ATRACE_CALL(); - mSession = new SurfaceComposerClient(); + mSession = sp<SurfaceComposerClient>::make(); std::string powerCtl = android::base::GetProperty("sys.powerctl", ""); if (powerCtl.empty()) { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 00ec48b79541..d651010b641a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3031,6 +3031,7 @@ package android.provider { field public static final String DISABLED_PRINT_SERVICES = "disabled_print_services"; field @Deprecated public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages"; field public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners"; + field public static final String GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"; field public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations"; field public static final String NOTIFICATION_BADGING = "notification_badging"; field public static final String NOTIFICATION_BUBBLES = "notification_bubbles"; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 60e57b588be0..b97c9b5e83f2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10583,6 +10583,9 @@ public final class Settings { * * @hide */ + @TestApi + @Readable + @SuppressLint({"UnflaggedApi", "NoSettingsProvider"}) // @TestApi purely for CTS support. public static final String GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"; /** diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 3a3ea189b227..7013f7d705f8 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -52,13 +52,6 @@ flag { } flag { - name: "deprecate_fsv_sig" - namespace: "hardware_backed_security" - description: "Feature flag for deprecating .fsv_sig" - bug: "277916185" -} - -flag { name: "extend_vb_chain_to_updated_apk" namespace: "hardware_backed_security" description: "Use v4 signature and fs-verity to chain verification of allowlisted APKs to Verified Boot" diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index 505db30ca719..772fd968e507 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -225,7 +225,7 @@ public final class Adjustment implements Parcelable { public static final int TYPE_CONTENT_RECOMMENDATION = 4; /** - * Data type: String, a summarization of the text of the notification, or, if provided for + * Data type: CharSequence, a summarization of the text of the notification, or, if provided for * a group summary, a summarization of the text of all of the notificatrions in the group. * Send this key with a null value to remove an existing summarization. */ diff --git a/core/jni/android_media_ImageWriter.cpp b/core/jni/android_media_ImageWriter.cpp index 1357dd842ff1..8e58922bd9df 100644 --- a/core/jni/android_media_ImageWriter.cpp +++ b/core/jni/android_media_ImageWriter.cpp @@ -399,7 +399,7 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje } sp<JNIImageWriterContext> ctx(new JNIImageWriterContext(env, weakThiz, clazz)); - sp<Surface> producer = new Surface(bufferProducer, /*controlledByApp*/false); + sp<Surface> producer = sp<Surface>::make(bufferProducer, /*controlledByApp*/ false); ctx->setProducer(producer); /** * NATIVE_WINDOW_API_CPU isn't a good choice here, as it makes the bufferQueue not connectable diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 312c2067d396..783daec82b9e 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -139,7 +139,7 @@ jobject android_view_Surface_createFromIGraphicBufferProducer(JNIEnv* env, return NULL; } - sp<Surface> surface(new Surface(bufferProducer, true)); + sp<Surface> surface = sp<Surface>::make(bufferProducer, true); return android_view_Surface_createFromSurface(env, surface); } @@ -161,7 +161,7 @@ static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz, return 0; } - sp<Surface> surface(new Surface(producer, true)); + sp<Surface> surface = sp<Surface>::make(producer, true); if (surface == NULL) { jniThrowException(env, OutOfResourcesException, NULL); return 0; @@ -358,8 +358,8 @@ static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz, sp<Surface> sur; if (surfaceShim.graphicBufferProducer != nullptr) { // we have a new IGraphicBufferProducer, create a new Surface for it - sur = new Surface(surfaceShim.graphicBufferProducer, true, - surfaceShim.surfaceControlHandle); + sur = sp<Surface>::make(surfaceShim.graphicBufferProducer, true, + surfaceShim.surfaceControlHandle); // and keep a reference before passing to java sur->incStrong(&sRefBaseOwner); } diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp index 6ad109e80752..4f2ab09252c8 100644 --- a/core/jni/android_view_SurfaceSession.cpp +++ b/core/jni/android_view_SurfaceSession.cpp @@ -42,14 +42,14 @@ sp<SurfaceComposerClient> android_view_SurfaceSession_getClient( static jlong nativeCreate(JNIEnv* env, jclass clazz) { - SurfaceComposerClient* client = new SurfaceComposerClient(); - client->incStrong((void*)nativeCreate); - return reinterpret_cast<jlong>(client); + // Will be deleted via decStrong() in nativeDestroy. + auto client = sp<SurfaceComposerClient>::make(); + return reinterpret_cast<jlong>(client.release()); } static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) { SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr); - client->decStrong((void*)nativeCreate); + client->decStrong((void*)client); } static const JNINativeMethod gMethods[] = { diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index 21fe1f020b29..f71878ccff08 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -85,7 +85,7 @@ static void android_view_TextureView_createNativeWindow(JNIEnv* env, jobject tex jobject surface) { sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surface)); - sp<ANativeWindow> window = new Surface(producer, true); + sp<ANativeWindow> window = sp<Surface>::make(producer, true); window->incStrong((void*)android_view_TextureView_createNativeWindow); SET_LONG(textureView, gTextureViewClassInfo.nativeWindow, jlong(window.get())); diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp index 75330be2624d..1b3b14da49d5 100644 --- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp +++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp @@ -300,7 +300,7 @@ not_valid_surface: } sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(_env, native_window)); - window = new Surface(producer, true); + window = sp<Surface>::make(producer, true); if (window == NULL) goto not_valid_surface; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 56de48daf810..70539902f651 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -103,6 +103,10 @@ public class DesktopModeVisualIndicator { return null; } } + + private static boolean isDragToDesktopStartState(DragStartState startState) { + return startState == FROM_FULLSCREEN || startState == FROM_SPLIT; + } } private final VisualIndicatorViewContainer mVisualIndicatorViewContainer; @@ -125,7 +129,12 @@ public class DesktopModeVisualIndicator { @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider, SnapEventHandler snapEventHandler) { SurfaceControl.Builder builder = new SurfaceControl.Builder(); - taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder); + if (!DragStartState.isDragToDesktopStartState(dragStartState) + || !DesktopModeFlags.ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX.isTrue()) { + // In the DragToDesktop transition we attach the indicator to the transition root once + // that is available - for all other cases attach the indicator here. + taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder); + } mVisualIndicatorViewContainer = new VisualIndicatorViewContainer( DesktopModeFlags.ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX.isTrue() ? desktopExecutor : mainExecutor, @@ -159,6 +168,18 @@ public class DesktopModeVisualIndicator { mVisualIndicatorViewContainer.releaseVisualIndicator(); } + /** Reparent the visual indicator to {@code newParent}. */ + void reparentLeash(SurfaceControl.Transaction t, SurfaceControl newParent) { + mVisualIndicatorViewContainer.reparentLeash(t, newParent); + } + + /** Start the fade-in animation. */ + void fadeInIndicator() { + mVisualIndicatorViewContainer.fadeInIndicator( + mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType, + mTaskInfo.displayId); + } + /** * Based on the coordinates of the current drag event, determine which indicator type we should * display, including no visible indicator. 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 9cacb0c7d2b8..5d3cb86bf584 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 @@ -639,6 +639,7 @@ class DesktopTasksController( dragToDesktopTransitionHandler.startDragToDesktopTransition( taskInfo, dragToDesktopValueAnimator, + visualIndicator, ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index d396d8bff2b8..c8f7ea9f885f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -25,6 +25,7 @@ import android.os.SystemProperties import android.os.UserHandle import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CLOSE +import android.window.DesktopModeFlags import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.TransitionRequestInfo @@ -118,6 +119,7 @@ sealed class DragToDesktopTransitionHandler( fun startDragToDesktopTransition( taskInfo: RunningTaskInfo, dragToDesktopAnimator: MoveToDesktopAnimator, + visualIndicator: DesktopModeVisualIndicator?, ) { if (inProgress) { logV("Drag to desktop transition already in progress.") @@ -163,12 +165,14 @@ sealed class DragToDesktopTransitionHandler( dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken, otherSplitTask = otherTask, + visualIndicator = visualIndicator, ) } else { TransitionState.FromFullscreen( draggedTaskId = taskInfo.taskId, dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken, + visualIndicator = visualIndicator, ) } } @@ -457,6 +461,13 @@ sealed class DragToDesktopTransitionHandler( state.surfaceLayers = layers state.startTransitionFinishCb = finishCallback state.startTransitionFinishTransaction = finishTransaction + + val taskChange = state.draggedTaskChange ?: error("Expected non-null task change.") + val taskInfo = taskChange.taskInfo ?: error("Expected non-null task info.") + + if (DesktopModeFlags.ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX.isTrue) { + attachIndicatorToTransitionRoot(state, info, taskInfo, startTransaction) + } startTransaction.apply() if (state.cancelState == CancelState.NO_CANCEL) { @@ -485,8 +496,6 @@ sealed class DragToDesktopTransitionHandler( } else { SPLIT_POSITION_BOTTOM_OR_RIGHT } - val taskInfo = - state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.") val wct = WindowContainerTransaction() restoreWindowOrder(wct) state.startTransitionFinishTransaction?.apply() @@ -511,6 +520,21 @@ sealed class DragToDesktopTransitionHandler( return true } + private fun attachIndicatorToTransitionRoot( + state: TransitionState, + info: TransitionInfo, + taskInfo: RunningTaskInfo, + t: SurfaceControl.Transaction, + ) { + val transitionRoot = info.getRoot(info.findRootIndex(taskInfo.displayId)) + state.visualIndicator?.let { + // Attach the indicator to the transition root so that it's removed at the end of the + // transition regardless of whether we managed to release the indicator. + it.reparentLeash(t, transitionRoot.leash) + it.fadeInIndicator() + } + } + /** * Calculates start drag to desktop layers for transition [info]. The leash layer is calculated * based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is @@ -901,6 +925,7 @@ sealed class DragToDesktopTransitionHandler( abstract var surfaceLayers: DragToDesktopLayers? abstract var cancelState: CancelState abstract var startAborted: Boolean + abstract val visualIndicator: DesktopModeVisualIndicator? data class FromFullscreen( override val draggedTaskId: Int, @@ -915,6 +940,7 @@ sealed class DragToDesktopTransitionHandler( override var surfaceLayers: DragToDesktopLayers? = null, override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, + override val visualIndicator: DesktopModeVisualIndicator?, var otherRootChanges: MutableList<Change> = mutableListOf(), ) : TransitionState() @@ -931,6 +957,7 @@ sealed class DragToDesktopTransitionHandler( override var surfaceLayers: DragToDesktopLayers? = null, override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, + override val visualIndicator: DesktopModeVisualIndicator?, var splitRootChange: Change? = null, var otherSplitTask: Int, ) : TransitionState() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt index 919e8164b58e..23562388b3e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt @@ -130,6 +130,12 @@ constructor( } } + /** Reparent the indicator to {@code newParent}. */ + fun reparentLeash(t: SurfaceControl.Transaction, newParent: SurfaceControl) { + val leash = indicatorLeash ?: return + t.reparent(leash, newParent) + } + private fun showIndicator(taskSurface: SurfaceControl, leash: SurfaceControl) { mainExecutor.execute { indicatorLeash = leash @@ -166,7 +172,7 @@ constructor( displayController.getDisplayLayout(taskInfo.displayId) ?: error("Expected to find DisplayLayout for taskId${taskInfo.taskId}.") if (currentType == IndicatorType.NO_INDICATOR) { - fadeInIndicator(layout, newType, taskInfo.displayId, snapEventHandler) + fadeInIndicatorInternal(layout, newType, taskInfo.displayId, snapEventHandler) } else if (newType == IndicatorType.NO_INDICATOR) { fadeOutIndicator( layout, @@ -195,10 +201,22 @@ constructor( } /** + * Fade indicator in as provided type. + * + * Animator fades the indicator in while expanding the bounds outwards. + */ + fun fadeInIndicator(layout: DisplayLayout, type: IndicatorType, displayId: Int) { + if (isReleased) return + desktopExecutor.execute { + fadeInIndicatorInternal(layout, type, displayId, snapEventHandler) + } + } + + /** * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards. */ @VisibleForTesting - fun fadeInIndicator( + fun fadeInIndicatorInternal( layout: DisplayLayout, type: IndicatorType, displayId: Int, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index fed336b17f19..65fa9b4b5529 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -52,6 +52,7 @@ import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.TransitionUtil; +import com.android.wm.shell.shared.split.SplitScreenConstants; import com.android.wm.shell.transition.OneShotRemoteHandler; import com.android.wm.shell.transition.Transitions; @@ -362,7 +363,8 @@ class SplitScreenTransitions { WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, - int extraTransitType, boolean resizeAnim) { + int extraTransitType, boolean resizeAnim, + @SplitScreenConstants.PersistentSnapPosition int snapPosition) { if (mPendingEnter != null) { ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + " skip to start enter split transition since it already exist. "); @@ -373,16 +375,18 @@ class SplitScreenTransitions { .onSplitAnimationInvoked(true /*animationRunning*/)); } final IBinder transition = mTransitions.startTransition(transitType, wct, handler); - setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim); + setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim, + snapPosition); return transition; } /** Sets a transition to enter split. */ void setEnterTransition(@NonNull IBinder transition, @Nullable RemoteTransition remoteTransition, - int extraTransitType, boolean resizeAnim) { + int extraTransitType, boolean resizeAnim, + int snapPosition) { mPendingEnter = new EnterSession( - transition, remoteTransition, extraTransitType, resizeAnim); + transition, remoteTransition, extraTransitType, resizeAnim, snapPosition); ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Enter split screen"); @@ -675,13 +679,16 @@ class SplitScreenTransitions { /** Bundled information of enter transition. */ class EnterSession extends TransitSession { final boolean mResizeAnim; + /** The starting snap position we'll enter into with this transition. */ + final @SplitScreenConstants.PersistentSnapPosition int mEnteringPosition; EnterSession(IBinder transition, @Nullable RemoteTransition remoteTransition, - int extraTransitType, boolean resizeAnim) { + int extraTransitType, boolean resizeAnim, int snapPosition) { super(transition, null /* consumedCallback */, null /* finishedCallback */, remoteTransition, extraTransitType); this.mResizeAnim = resizeAnim; + this.mEnteringPosition = snapPosition; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index e1b57e229d36..7472b0ea56ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -653,7 +653,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, null, this, isSplitScreenVisible() ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN, - !mIsDropEntering); + !mIsDropEntering, SNAP_TO_2_50_50); // Due to drag already pip task entering split by this method so need to reset flag here. mIsDropEntering = false; @@ -787,7 +787,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index); mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, - extraTransitType, !mIsDropEntering); + extraTransitType, !mIsDropEntering, SNAP_TO_2_50_50); } /** @@ -833,7 +833,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index); mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, - extraTransitType, !mIsDropEntering); + extraTransitType, !mIsDropEntering, SNAP_TO_2_50_50); } /** @@ -1050,7 +1050,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mPausingTasks.clear(); } mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this, - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, snapPosition); setEnterInstanceId(instanceId); } @@ -1140,7 +1140,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this, - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, snapPosition); setEnterInstanceId(instanceId); } @@ -1645,6 +1645,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, grantFocusToStage(stageToFocus); } + private void grantFocusForSnapPosition(@PersistentSnapPosition int enteringPosition) { + switch (enteringPosition) { + case SNAP_TO_2_90_10 -> grantFocusToPosition(true /*leftOrTop*/); + case SNAP_TO_2_10_90 -> grantFocusToPosition(false /*leftOrTop*/); + default -> { /*no-op*/ } + } + } + private void clearRequestIfPresented() { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented"); if (mSideStage.mVisible && mSideStage.mHasChildren @@ -2911,7 +2919,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // split, prepare to enter split screen. prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering, SNAP_TO_2_50_50); } else if (isSplitScreenVisible() && isOpening) { // launching into an existing split stage; possibly launchAdjacent // If we're replacing a pip-able app, we need to let mixed handler take care of @@ -2920,7 +2928,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // updated layout will get applied in startAnimation pendingResize mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), - TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, true /*resizeAnim*/); + TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, true /*resizeAnim*/, + SNAP_TO_2_50_50); } } else if (inFullscreen && isSplitScreenVisible()) { // If the trigger task is in fullscreen and in split, exit split and place @@ -2998,14 +3007,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, out = new WindowContainerTransaction(); prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering, SNAP_TO_2_50_50); return out; } ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d " + "restoring to split", request.getDebugId()); out = new WindowContainerTransaction(); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), - TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */); + TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */, + SNAP_TO_2_50_50); } return out; } @@ -3192,7 +3202,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (keepSplitWithPip) { // Set an enter transition for when startAnimation gets called again mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null, - TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false); + TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false, + SNAP_TO_2_50_50); } else { int finalClosingTaskId = closingSplitTaskId; mRecentTasks.ifPresent(recentTasks -> @@ -3577,6 +3588,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }); mPausingTasks.clear(); + if (enableFlexibleTwoAppSplit()) { + grantFocusForSnapPosition(enterTransition.mEnteringPosition); + } }); if (info.getType() == TRANSIT_CHANGE && !isSplitActive() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt index dcc9e2415039..fe1dc29181b9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt @@ -20,6 +20,7 @@ import android.animation.AnimatorTestRule import android.app.ActivityManager.RunningTaskInfo import android.graphics.PointF import android.graphics.Rect +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -28,6 +29,7 @@ import android.view.SurfaceControl import androidx.test.filters.SmallTest import com.android.internal.policy.SystemBarUtils import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE +import com.android.window.flags.Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase @@ -45,6 +47,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.kotlin.any +import org.mockito.kotlin.never +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever /** @@ -345,6 +349,38 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { assertThat(visualIndicator.indicatorBounds).isEqualTo(dropTargetBounds) } + @Test + @DisableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun createIndicator_inTransitionFlagDisabled_isAttachedToDisplayArea() { + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) + + verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun createIndicator_fromFreeform_inTransitionFlagEnabled_isAttachedToDisplayArea() { + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM) + + verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun createIndicator_fromFullscreen_inTransitionFlagEnabled_notAttachedToDisplayArea() { + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) + + verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun createIndicator_fromSplit_inTransitionFlagEnabled_notAttachedToDisplayArea() { + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT) + + verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any()) + } + private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) { visualIndicator = DesktopModeVisualIndicator( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index de55db86d1e7..9588a5c73385 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -11,8 +11,11 @@ import android.graphics.PointF import android.graphics.Rect import android.os.IBinder import android.os.SystemProperties +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_OPEN import android.window.TransitionInfo @@ -23,6 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE import com.android.internal.jank.InteractionJankMonitor +import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder @@ -78,6 +82,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Mock private lateinit var homeTaskLeash: SurfaceControl @Mock private lateinit var desktopUserRepositories: DesktopUserRepositories @Mock private lateinit var bubbleController: BubbleController + @Mock private lateinit var visualIndicator: DesktopModeVisualIndicator private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() } @@ -740,11 +745,47 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { assertThat(fraction).isWithin(TOLERANCE).of(0f) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun startDrag_indicatorFlagEnabled_attachesIndicatorToTransitionRoot() { + val task = createTask() + val rootLeash = mock<SurfaceControl>() + val startTransaction = mock<SurfaceControl.Transaction>() + startDrag( + defaultHandler, + task, + startTransaction = startTransaction, + transitionRootLeash = rootLeash, + ) + + verify(visualIndicator).reparentLeash(startTransaction, rootLeash) + verify(visualIndicator).fadeInIndicator() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun startDrag_indicatorFlagDisabled_doesNotAttachIndicatorToTransitionRoot() { + val task = createTask() + val rootLeash = mock<SurfaceControl>() + val startTransaction = mock<SurfaceControl.Transaction>() + startDrag( + defaultHandler, + task, + startTransaction = startTransaction, + transitionRootLeash = rootLeash, + ) + + verify(visualIndicator, never()).reparentLeash(any(), any()) + verify(visualIndicator, never()).fadeInIndicator() + } + private fun startDrag( handler: DragToDesktopTransitionHandler, task: RunningTaskInfo = createTask(), + startTransaction: SurfaceControl.Transaction = mock(), finishTransaction: SurfaceControl.Transaction = mock(), homeChange: TransitionInfo.Change? = createHomeChange(), + transitionRootLeash: SurfaceControl? = null, ): IBinder { whenever(dragAnimator.position).thenReturn(PointF()) // Simulate transition is started and is ready to animate. @@ -756,8 +797,9 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, draggedTask = task, homeChange = homeChange, + rootLeash = transitionRootLeash, ), - startTransaction = mock(), + startTransaction = startTransaction, finishTransaction = finishTransaction, finishCallback = {}, ) @@ -778,7 +820,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) ) .thenReturn(token) - handler.startDragToDesktopTransition(task, dragAnimator) + handler.startDragToDesktopTransition(task, dragAnimator, visualIndicator) return token } @@ -845,6 +887,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { type: Int, draggedTask: RunningTaskInfo, homeChange: TransitionInfo.Change? = createHomeChange(), + rootLeash: SurfaceControl? = null, ) = TransitionInfo(type, /* flags= */ 0).apply { homeChange?.let { addChange(it) } @@ -861,6 +904,9 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { flags = flags or FLAG_IS_WALLPAPER } ) + if (rootLeash != null) { + addRootLeash(DEFAULT_DISPLAY, rootLeash, /* offsetLeft= */ 0, /* offsetTop= */ 0) + } } private fun createHomeChange() = diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt index 4c8cb3823f7e..c7518d5914b4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt @@ -25,6 +25,7 @@ import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Display +import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.SurfaceControlViewHost import android.view.View @@ -49,6 +50,8 @@ import org.mockito.Mockito.mock import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.verifyZeroInteractions @@ -121,7 +124,7 @@ class VisualIndicatorViewContainerTest : ShellTestCase() { DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, ) desktopExecutor.flushAll() - verify(spyViewContainer).fadeInIndicator(any(), any(), any(), any()) + verify(spyViewContainer).fadeInIndicatorInternal(any(), any(), any(), any()) } @Test @@ -265,6 +268,35 @@ class VisualIndicatorViewContainerTest : ShellTestCase() { ) } + @Test + fun fadeInIndicator_callsFadeIn() { + val spyViewContainer = setupSpyViewContainer() + + spyViewContainer.fadeInIndicator( + mock<DisplayLayout>(), + DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, + DEFAULT_DISPLAY, + ) + desktopExecutor.flushAll() + + verify(spyViewContainer).fadeInIndicatorInternal(any(), any(), any(), any()) + } + + @Test + fun fadeInIndicator_alreadyReleased_doesntCallFadeIn() { + val spyViewContainer = setupSpyViewContainer() + spyViewContainer.releaseVisualIndicator() + + spyViewContainer.fadeInIndicator( + mock<DisplayLayout>(), + DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, + DEFAULT_DISPLAY, + ) + desktopExecutor.flushAll() + + verify(spyViewContainer, never()).fadeInIndicatorInternal(any(), any(), any(), any()) + } + private fun setupSpyViewContainer(): VisualIndicatorViewContainer { val viewContainer = VisualIndicatorViewContainer( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index e5a6a6d258dd..70603fad37b9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; @@ -213,7 +214,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mSplitScreenTransitions.startEnterTransition( TRANSIT_OPEN, new WindowContainerTransaction(), new RemoteTransition(testRemote, "Test"), mStageCoordinator, - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); boolean accepted = mStageCoordinator.startAnimation(transition, info, @@ -239,7 +240,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mSplitScreenTransitions.startEnterTransition( TRANSIT_OPEN, new WindowContainerTransaction(), new RemoteTransition(testRemote, "Test"), mStageCoordinator, - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); boolean accepted = mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), @@ -262,7 +263,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mSplitScreenTransitions.startEnterTransition( TRANSIT_OPEN, new WindowContainerTransaction(), new RemoteTransition(testRemote, "Test"), mStageCoordinator, - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), @@ -524,7 +525,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder enterTransit = mSplitScreenTransitions.startEnterTransition( TRANSIT_OPEN, new WindowContainerTransaction(), new RemoteTransition(new TestRemoteTransition(), "Test"), - mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); mStageCoordinator.startAnimation(enterTransit, enterInfo, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index e9c4c31729e9..e246329446dc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -475,7 +475,7 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.startTask(mTaskId, SPLIT_POSITION_TOP_OR_LEFT, null /*options*/, null, SPLIT_INDEX_UNDEFINED); verify(mSplitScreenTransitions).startEnterTransition(anyInt(), - mWctCaptor.capture(), any(), any(), anyInt(), anyBoolean()); + mWctCaptor.capture(), any(), any(), anyInt(), anyBoolean(), anyInt()); int windowingMode = mWctCaptor.getValue().getChanges().get(mBinder).getWindowingMode(); assertEquals(windowingMode, WINDOWING_MODE_UNDEFINED); diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 275972495206..f6dc41ed128a 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -89,7 +89,7 @@ ASurfaceControl* ASurfaceControl_createFromWindow(ANativeWindow* window, const c CHECK_NOT_NULL(window); CHECK_NOT_NULL(debug_name); - sp<SurfaceComposerClient> client = new SurfaceComposerClient(); + sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make(); if (client->initCheck() != NO_ERROR) { return nullptr; } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt index ed9cd98a825a..f64f13d4a9b5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.common.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.model.StateChange import com.android.systemui.model.fakeSysUIStatePerDisplayRepository import com.android.systemui.model.sysUiStateFactory @@ -26,6 +27,7 @@ import com.android.systemui.model.sysuiStateInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.runner.RunWith @@ -49,6 +51,13 @@ class SysUIStatePerDisplayInteractorTest : SysuiTestCase() { add(1, state1) add(2, state2) } + runBlocking { + kosmos.displayRepository.apply { + addDisplay(0) + addDisplay(1) + addDisplay(2) + } + } } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt index 0197a1e61801..c72afc72fa16 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt @@ -16,26 +16,17 @@ package com.android.systemui.media.controls.domain.interactor -import android.R -import android.graphics.drawable.Icon import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope -import com.android.systemui.media.controls.MediaTestHelper import com.android.systemui.media.controls.data.repository.MediaFilterRepository import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor -import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor -import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor -import com.android.systemui.media.controls.shared.model.MediaCommonModel import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel -import com.android.systemui.media.controls.shared.model.SmartspaceMediaData -import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -52,16 +43,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() { private val mediaFilterRepository: MediaFilterRepository = with(kosmos) { mediaFilterRepository } - private val mediaRecommendationsInteractor: MediaRecommendationsInteractor = - kosmos.mediaRecommendationsInteractor - val icon = Icon.createWithResource(context, R.drawable.ic_media_play) - private val mediaRecommendation = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - packageName = PACKAGE_NAME, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor @@ -119,81 +100,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() { } @Test - fun addActiveRecommendation_inactiveMedia() = - testScope.runTest { - val hasActiveMediaOrRecommendation by - collectLastValue(underTest.hasActiveMediaOrRecommendation) - val hasAnyMediaOrRecommendation by - collectLastValue(underTest.hasAnyMediaOrRecommendation) - val currentMedia by collectLastValue(underTest.currentMedia) - - val userMedia = MediaData(active = false) - val recsLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) - val mediaLoadingModel = MediaDataLoadingModel.Loaded(userMedia.instanceId) - - mediaFilterRepository.setRecommendation(mediaRecommendation) - mediaFilterRepository.setRecommendationsLoadingState(recsLoadingModel) - - assertThat(hasActiveMediaOrRecommendation).isTrue() - assertThat(hasAnyMediaOrRecommendation).isTrue() - assertThat(currentMedia) - .containsExactly(MediaCommonModel.MediaRecommendations(recsLoadingModel)) - - mediaFilterRepository.addSelectedUserMediaEntry(userMedia) - mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel) - mediaFilterRepository.setOrderedMedia() - - assertThat(hasActiveMediaOrRecommendation).isTrue() - assertThat(hasAnyMediaOrRecommendation).isTrue() - assertThat(currentMedia) - .containsExactly( - MediaCommonModel.MediaRecommendations(recsLoadingModel), - MediaCommonModel.MediaControl(mediaLoadingModel, true), - ) - .inOrder() - } - - @Test - fun addActiveRecommendation_thenInactive() = - testScope.runTest { - val hasActiveMediaOrRecommendation by - collectLastValue(underTest.hasActiveMediaOrRecommendation) - val hasAnyMediaOrRecommendation by - collectLastValue(underTest.hasAnyMediaOrRecommendation) - - mediaFilterRepository.setRecommendation(mediaRecommendation) - - assertThat(hasActiveMediaOrRecommendation).isTrue() - assertThat(hasAnyMediaOrRecommendation).isTrue() - - mediaFilterRepository.setRecommendation(mediaRecommendation.copy(isActive = false)) - - assertThat(hasActiveMediaOrRecommendation).isFalse() - assertThat(hasAnyMediaOrRecommendation).isFalse() - } - - @Test - fun addActiveRecommendation_thenInvalid() = - testScope.runTest { - val hasActiveMediaOrRecommendation by - collectLastValue(underTest.hasActiveMediaOrRecommendation) - val hasAnyMediaOrRecommendation by - collectLastValue(underTest.hasAnyMediaOrRecommendation) - - mediaFilterRepository.setRecommendation(mediaRecommendation) - - assertThat(hasActiveMediaOrRecommendation).isTrue() - assertThat(hasAnyMediaOrRecommendation).isTrue() - - mediaFilterRepository.setRecommendation( - mediaRecommendation.copy(recommendations = listOf()) - ) - - assertThat(hasActiveMediaOrRecommendation).isFalse() - assertThat(hasAnyMediaOrRecommendation).isFalse() - } - - @Test fun hasAnyMedia_noMediaSet_returnsFalse() = testScope.runTest { assertThat(underTest.hasAnyMedia()).isFalse() } @@ -208,47 +114,4 @@ class MediaCarouselInteractorTest : SysuiTestCase() { @Test fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() = testScope.runTest { assertThat(underTest.hasActiveMediaOrRecommendation.value).isFalse() } - - @Test - fun loadMediaFromRec() = - testScope.runTest { - val currentMedia by collectLastValue(underTest.currentMedia) - val instanceId = InstanceId.fakeInstanceId(123) - val data = - MediaData( - active = true, - instanceId = instanceId, - packageName = PACKAGE_NAME, - notificationKey = KEY, - ) - val smartspaceLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) - val mediaLoadingModel = MediaDataLoadingModel.Loaded(instanceId) - - mediaFilterRepository.setRecommendation(mediaRecommendation) - mediaFilterRepository.setRecommendationsLoadingState(smartspaceLoadingModel) - mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME) - mediaFilterRepository.addSelectedUserMediaEntry(data) - mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel) - - assertThat(currentMedia) - .containsExactly(MediaCommonModel.MediaRecommendations(smartspaceLoadingModel)) - .inOrder() - - mediaFilterRepository.addSelectedUserMediaEntry(data.copy(isPlaying = true)) - mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel) - - assertThat(currentMedia) - .containsExactly( - MediaCommonModel.MediaControl(mediaLoadingModel, isMediaFromRec = true), - MediaCommonModel.MediaRecommendations(smartspaceLoadingModel), - ) - .inOrder() - } - - companion object { - private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" - private const val PACKAGE_NAME = "com.android.example" - private const val KEY = "key" - private const val SURFACE = 4 - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt deleted file mode 100644 index 2265c0149cc3..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.domain.interactor - -import android.R -import android.content.ComponentName -import android.content.Intent -import android.content.applicationContext -import android.graphics.drawable.Icon -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.Expandable -import com.android.systemui.broadcast.broadcastSender -import com.android.systemui.broadcast.mockBroadcastSender -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope -import com.android.systemui.media.controls.MediaTestHelper -import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl -import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor -import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor.Companion.EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME -import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor -import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter -import com.android.systemui.media.controls.shared.model.MediaRecModel -import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel -import com.android.systemui.media.controls.shared.model.SmartspaceMediaData -import com.android.systemui.plugins.activityStarter -import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.doNothing -import org.mockito.Mockito.spy -import org.mockito.Mockito.verify -import org.mockito.kotlin.eq - -@SmallTest -@RunWith(AndroidJUnit4::class) -class MediaRecommendationsInteractorTest : SysuiTestCase() { - - private val spyContext = spy(context) - private val kosmos = testKosmos().apply { applicationContext = spyContext } - private val testScope = kosmos.testScope - - private val mediaDataFilter: MediaDataFilterImpl = with(kosmos) { mediaDataFilter } - private val activityStarter = kosmos.activityStarter - private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play) - private val smartspaceMediaData: SmartspaceMediaData = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - packageName = PACKAGE_NAME, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) - - private val underTest: MediaRecommendationsInteractor = - with(kosmos) { - broadcastSender = mockBroadcastSender - kosmos.mediaRecommendationsInteractor - } - - @Test - fun addRecommendation_smartspaceMediaDataUpdate() = - testScope.runTest { - val recommendations by collectLastValue(underTest.recommendations) - - val model = - MediaRecommendationsModel( - key = KEY_MEDIA_SMARTSPACE, - packageName = PACKAGE_NAME, - areRecommendationsValid = true, - mediaRecs = - listOf( - MediaRecModel(icon = icon), - MediaRecModel(icon = icon), - MediaRecModel(icon = icon), - ), - ) - - mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData) - - assertThat(recommendations).isEqualTo(model) - } - - @Test - fun addInvalidRecommendation() = - testScope.runTest { - val recommendations by collectLastValue(underTest.recommendations) - val inValidData = smartspaceMediaData.copy(recommendations = listOf()) - - mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData) - assertThat(recommendations?.areRecommendationsValid).isTrue() - - mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, inValidData) - assertThat(recommendations?.areRecommendationsValid).isFalse() - assertThat(recommendations?.mediaRecs?.isEmpty()).isTrue() - } - - @Test - fun removeRecommendation_noTrampolineActivity() { - val intent = Intent() - - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData) - underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0) - - verify(kosmos.mockBroadcastSender).sendBroadcast(eq(intent)) - } - - @Test - fun removeRecommendation_usingTrampolineActivity() { - doNothing().whenever(spyContext).startActivity(any()) - val intent = Intent() - - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - intent.component = ComponentName(PACKAGE_NAME, EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) - - underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0) - - verify(spyContext).startActivity(eq(intent)) - } - - @Test - fun startSettings() { - underTest.startSettings() - - verify(activityStarter).startActivity(any(), eq(true)) - } - - @Test - fun startClickIntent() { - doNothing().whenever(spyContext).startActivity(any()) - val intent = Intent() - val expandable = mock<Expandable>() - - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData) - underTest.startClickIntent(expandable, intent) - - verify(spyContext).startActivity(eq(intent)) - } - - companion object { - private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" - private const val PACKAGE_NAME = "com.example.app" - private const val SURFACE = 4 - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt index 005424ba599e..faa62c2febc1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt @@ -23,7 +23,6 @@ import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel import com.android.systemui.media.controls.ui.viewmodel.mediaControlViewModel -import com.android.systemui.media.controls.ui.viewmodel.mediaRecommendationsViewModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Assert.fail @@ -56,25 +55,6 @@ class MediaDiffUtilTest : SysuiTestCase() { } @Test - fun newMediaRecommendationsAdded() { - val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true) - val oldList = listOf<MediaCommonViewModel>() - val newList = listOf(mediaRecs) - val mediaLoadedCallback = MediaViewModelCallback(oldList, newList) - val mediaLoadedListUpdateCallback = - MediaViewModelListUpdateCallback( - oldList, - newList, - { commonViewModel, _ -> assertThat(commonViewModel).isEqualTo(mediaRecs) }, - { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") }, - { fail("Unexpected to remove $it") }, - { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") }, - ) - - DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback) - } - - @Test fun updateMediaControl_contentChanged() { val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123), true) val oldList = listOf(mediaControl) @@ -94,25 +74,6 @@ class MediaDiffUtilTest : SysuiTestCase() { } @Test - fun updateMediaRecommendations_contentChanged() { - val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true) - val oldList = listOf(mediaRecs) - val newList = listOf(mediaRecs.copy(key = KEY_MEDIA_SMARTSPACE_2)) - val mediaLoadedCallback = MediaViewModelCallback(oldList, newList) - val mediaLoadedListUpdateCallback = - MediaViewModelListUpdateCallback( - oldList, - newList, - { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") }, - { commonViewModel, _ -> assertThat(commonViewModel).isNotEqualTo(mediaRecs) }, - { fail("Unexpected to remove $it") }, - { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") }, - ) - - DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback) - } - - @Test fun mediaControlMoved() { val mediaControl1 = createMediaControl(InstanceId.fakeInstanceId(123), true) val mediaControl2 = createMediaControl(InstanceId.fakeInstanceId(456), false) @@ -133,27 +94,6 @@ class MediaDiffUtilTest : SysuiTestCase() { } @Test - fun mediaRecommendationsMoved() { - val mediaControl1 = createMediaControl(InstanceId.fakeInstanceId(123), true) - val mediaControl2 = createMediaControl(InstanceId.fakeInstanceId(456), false) - val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true) - val oldList = listOf(mediaRecs, mediaControl1, mediaControl2) - val newList = listOf(mediaControl1, mediaControl2, mediaRecs) - val mediaLoadedCallback = MediaViewModelCallback(oldList, newList) - val mediaLoadedListUpdateCallback = - MediaViewModelListUpdateCallback( - oldList, - newList, - { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") }, - { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") }, - { fail("Unexpected to remove $it") }, - { commonViewModel, _, _ -> assertThat(commonViewModel).isEqualTo(mediaRecs) }, - ) - - DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback) - } - - @Test fun mediaControlRemoved() { val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123), true) val oldList = listOf(mediaControl) @@ -172,25 +112,6 @@ class MediaDiffUtilTest : SysuiTestCase() { DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback) } - @Test - fun mediaRecommendationsRemoved() { - val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE_2, false) - val oldList = listOf(mediaRecs) - val newList = listOf<MediaCommonViewModel>() - val mediaLoadedCallback = MediaViewModelCallback(oldList, newList) - val mediaLoadedListUpdateCallback = - MediaViewModelListUpdateCallback( - oldList, - newList, - { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") }, - { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") }, - { commonViewModel -> assertThat(commonViewModel).isEqualTo(mediaRecs) }, - { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") }, - ) - - DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback) - } - private fun createMediaControl( instanceId: InstanceId, immediatelyUpdateUi: Boolean, @@ -201,26 +122,7 @@ class MediaDiffUtilTest : SysuiTestCase() { controlViewModel = kosmos.mediaControlViewModel, onAdded = {}, onRemoved = {}, - onUpdated = {} - ) - } - - private fun createMediaRecommendations( - key: String, - loadingEnabled: Boolean, - ): MediaCommonViewModel.MediaRecommendations { - return MediaCommonViewModel.MediaRecommendations( - key = key, - loadingEnabled = loadingEnabled, - recsViewModel = kosmos.mediaRecommendationsViewModel, - onAdded = {}, - onRemoved = {}, - onUpdated = {} + onUpdated = {}, ) } - - companion object { - private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" - private const val KEY_MEDIA_SMARTSPACE_2 = "MEDIA_SMARTSPACE_ID_2" - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt index fb5bbf452cfa..e56b114dc847 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt @@ -19,23 +19,18 @@ package com.android.systemui.media.controls.ui.viewmodel import android.R import android.content.packageManager import android.content.pm.ApplicationInfo -import android.graphics.drawable.Icon import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope -import com.android.systemui.media.controls.MediaTestHelper import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor -import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter import com.android.systemui.media.controls.shared.mediaLogger import com.android.systemui.media.controls.shared.mockMediaLogger import com.android.systemui.media.controls.shared.model.MediaData -import com.android.systemui.media.controls.shared.model.SmartspaceMediaData -import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider import com.android.systemui.statusbar.notificationLockscreenUserManager import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any @@ -62,15 +57,7 @@ class MediaCarouselViewModelTest : SysuiTestCase() { private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager private val packageManager = kosmos.packageManager - private val icon = Icon.createWithResource(context, R.drawable.ic_media_play) private val drawable = context.getDrawable(R.drawable.ic_media_play) - private val smartspaceMediaData: SmartspaceMediaData = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - packageName = PACKAGE_NAME, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) private val underTest: MediaCarouselViewModel = kosmos.mediaCarouselViewModel @@ -121,53 +108,6 @@ class MediaCarouselViewModelTest : SysuiTestCase() { } @Test - fun loadMediaControlsAndRecommendations_mediaItemsAreUpdated() = - testScope.runTest { - val sortedMedia by collectLastValue(underTest.mediaItems) - val instanceId1 = InstanceId.fakeInstanceId(123) - val instanceId2 = InstanceId.fakeInstanceId(456) - - loadMediaControl(KEY, instanceId1) - loadMediaControl(KEY_2, instanceId2) - loadMediaRecommendations() - - val firstMediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl - val secondMediaControl = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl - val recsCard = sortedMedia?.get(2) as MediaCommonViewModel.MediaRecommendations - assertThat(firstMediaControl.instanceId).isEqualTo(instanceId2) - assertThat(secondMediaControl.instanceId).isEqualTo(instanceId1) - assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE) - } - - @Test - fun recommendationClicked_switchToPlayer() = - testScope.runTest { - val sortedMedia by collectLastValue(underTest.mediaItems) - kosmos.visualStabilityProvider.isReorderingAllowed = false - val instanceId = InstanceId.fakeInstanceId(123) - - loadMediaRecommendations() - kosmos.mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME) - - var recsCard = sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations - assertThat(sortedMedia).hasSize(1) - assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE) - - loadMediaControl(KEY, instanceId, false) - - recsCard = sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations - assertThat(sortedMedia).hasSize(1) - assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE) - - loadMediaControl(KEY, instanceId, true) - - val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl - assertThat(sortedMedia).hasSize(2) - assertThat(mediaControl.instanceId).isEqualTo(instanceId) - assertThat(mediaControl.isMediaFromRec).isTrue() - } - - @Test fun addMediaControlThenRemove_mediaEventsAreLogged() = testScope.runTest { val sortedMedia by collectLastValue(underTest.mediaItems) @@ -199,31 +139,6 @@ class MediaCarouselViewModelTest : SysuiTestCase() { verify(kosmos.mediaLogger).logMediaCardRemoved(eq(instanceId)) } - @Test - fun addMediaRecommendationThenRemove_mediaEventsAreLogged() = - testScope.runTest { - val sortedMedia by collectLastValue(underTest.mediaItems) - - loadMediaRecommendations() - - val mediaRecommendations = - sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations - assertThat(mediaRecommendations.key).isEqualTo(KEY_MEDIA_SMARTSPACE) - - // when media recommendation is added to carousel - mediaRecommendations.onAdded(mediaRecommendations) - - verify(kosmos.mediaLogger).logMediaRecommendationCardAdded(eq(KEY_MEDIA_SMARTSPACE)) - - mediaDataFilter.onSmartspaceMediaDataRemoved(KEY, true) - assertThat(sortedMedia).isEmpty() - - // when media recommendation is removed from carousel - mediaRecommendations.onRemoved(true) - - verify(kosmos.mediaLogger).logMediaRecommendationCardRemoved(eq(KEY_MEDIA_SMARTSPACE)) - } - private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) { whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true) whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true) @@ -239,15 +154,10 @@ class MediaCarouselViewModelTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(key, key, mediaData) } - private fun loadMediaRecommendations(key: String = KEY_MEDIA_SMARTSPACE) { - mediaDataFilter.onSmartspaceMediaDataLoaded(key, smartspaceMediaData) - } - companion object { private const val USER_ID = 0 private const val KEY = "key" private const val KEY_2 = "key2" private const val PACKAGE_NAME = "com.example.app" - private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt deleted file mode 100644 index 51b1911be5d5..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.ui.viewmodel - -import android.R -import android.content.packageManager -import android.content.pm.ApplicationInfo -import android.graphics.drawable.Icon -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope -import com.android.systemui.media.controls.MediaTestHelper -import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl -import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter -import com.android.systemui.media.controls.shared.model.SmartspaceMediaData -import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers -import org.mockito.Mockito - -@SmallTest -@RunWith(AndroidJUnit4::class) -class MediaRecommendationsViewModelTest : SysuiTestCase() { - - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - - private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter - private val packageManager = kosmos.packageManager - private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play) - private val drawable = context.getDrawable(R.drawable.ic_media_play) - private val smartspaceMediaData: SmartspaceMediaData = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - packageName = PACKAGE_NAME, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) - - private val underTest: MediaRecommendationsViewModel = kosmos.mediaRecommendationsViewModel - - @Test - fun loadRecommendations_recsCardViewModelIsLoaded() = - testScope.runTest { - whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable) - whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) - .thenReturn(drawable) - whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt())) - .thenReturn(ApplicationInfo()) - whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME) - val recsCardViewModel by collectLastValue(underTest.mediaRecsCard) - - context.setMockPackageManager(packageManager) - - mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData) - - assertThat(recsCardViewModel).isNotNull() - assertThat(recsCardViewModel?.mediaRecs?.size) - .isEqualTo(smartspaceMediaData.recommendations.size) - } - - companion object { - private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" - private const val PACKAGE_NAME = "com.example.app" - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt index 31f8590c0378..46430afecbb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt @@ -70,6 +70,7 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -83,6 +84,7 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @@ -155,7 +157,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { testScope = kosmos.testScope shadeViewStateProvider = TestShadeViewStateProvider() - Mockito.`when`( + whenever( kosmos.mockStatusBarContentInsetsProvider .getStatusBarContentInsetsForCurrentRotation() ) @@ -163,9 +165,9 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) - Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any())) + whenever(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any())) .thenReturn(iconManager) - Mockito.`when`(statusBarContentInsetsProviderStore.forDisplay(context.displayId)) + whenever(statusBarContentInsetsProviderStore.forDisplay(context.displayId)) .thenReturn(kosmos.mockStatusBarContentInsetsProvider) allowTestableLooperAsMainThread() looper.runWithLooper { @@ -174,7 +176,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { LayoutInflater.from(mContext).inflate(R.layout.keyguard_status_bar, null) as KeyguardStatusBarView ) - Mockito.`when`(keyguardStatusBarView.getDisplay()).thenReturn(mContext.display) + whenever(keyguardStatusBarView.display).thenReturn(mContext.display) } controller = createController() @@ -404,14 +406,14 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { fun updateViewState_alphaAndVisibilityGiven_viewUpdated() { // Verify the initial values so we know the method triggers changes. Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(1f) - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) val newAlpha = 0.5f val newVisibility = View.INVISIBLE controller.updateViewState(newAlpha, newVisibility) Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(newAlpha) - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility) + assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility) } @Test @@ -423,7 +425,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.updateViewState(1f, View.VISIBLE) // Since we're disabled, we stay invisible - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) } @Test @@ -444,15 +446,15 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { fun updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() { controller.onViewAttached() updateStateToKeyguard() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) - Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true) - Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true) + whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true) + whenever(keyguardBypassController.bypassEnabled).thenReturn(true) onFinishedGoingToSleep() controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) } @Test @@ -461,13 +463,13 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.onViewAttached() updateStateToKeyguard() - Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true) - Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(false) + whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true) + whenever(keyguardBypassController.bypassEnabled).thenReturn(false) onFinishedGoingToSleep() controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) } @Test @@ -476,13 +478,13 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.onViewAttached() updateStateToKeyguard() - Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false) - Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true) + whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false) + whenever(keyguardBypassController.bypassEnabled).thenReturn(true) onFinishedGoingToSleep() controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) } @Test @@ -495,7 +497,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) } @Test @@ -508,7 +510,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) } @Test @@ -520,7 +522,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) } @Test @@ -532,7 +534,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) } @Test @@ -544,7 +546,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) } @Test @@ -556,7 +558,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) } @Test @@ -568,7 +570,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.setDozing(true) - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) } @Test @@ -580,7 +582,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.setDozing(false) - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) } @Test @@ -595,7 +597,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.updateViewState() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE) Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f) } @@ -611,7 +613,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.updateViewState(0.789f, View.VISIBLE) - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE) Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f) } @@ -635,13 +637,13 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.init() controller.onViewAttached() updateStateToKeyguard() - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) controller.setDozing(true) // setDozing(true) should typically cause the view to hide. But since the flag is on, we // should ignore these set dozing calls and stay the same visibility. - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) } @Test @@ -679,7 +681,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { shadeViewStateProvider.setShouldHeadsUpBeVisible(true) controller.updateForHeadsUp(/* animate= */ false) - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) } @Test @@ -695,7 +697,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { shadeViewStateProvider.setShouldHeadsUpBeVisible(false) controller.updateForHeadsUp(/* animate= */ false) - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE) } @Test @@ -728,7 +730,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { val str = mContext.getString(com.android.internal.R.string.status_bar_volume) // GIVEN the setting is off - Mockito.`when`(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0)) + whenever(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0)) .thenReturn(0) // WHEN CollapsedStatusBarFragment builds the blocklist @@ -744,7 +746,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { val str = mContext.getString(com.android.internal.R.string.status_bar_volume) // GIVEN the setting is ON - Mockito.`when`( + whenever( secureSettings.getIntForUser( Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0, @@ -779,42 +781,52 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { controller.onViewAttached() updateStateToKeyguard() setDisableSystemInfo(true) - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) controller.animateKeyguardStatusBarIn() // Since we're disabled, we don't actually animate in and stay invisible - Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) + assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE) } @Test fun animateToGlanceableHub_affectsAlpha() = testScope.runTest { - controller.init() - val transitionAlphaAmount = .5f - ViewUtils.attachView(keyguardStatusBarView) - looper.processAllMessages() - updateStateToKeyguard() - kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal) - runCurrent() - controller.updateCommunalAlphaTransition(transitionAlphaAmount) - Truth.assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount) + try { + controller.init() + val transitionAlphaAmount = .5f + ViewUtils.attachView(keyguardStatusBarView) + + looper.processAllMessages() + updateStateToKeyguard() + kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal) + runCurrent() + controller.updateCommunalAlphaTransition(transitionAlphaAmount) + assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount) + } finally { + ViewUtils.detachView(keyguardStatusBarView) + } } @Test fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() = testScope.runTest { - controller.init() - val transitionAlphaAmount = .5f - ViewUtils.attachView(keyguardStatusBarView) - looper.processAllMessages() - updateStateToKeyguard() - kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal) - runCurrent() - controller.updateCommunalAlphaTransition(transitionAlphaAmount) - kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank) - runCurrent() - Truth.assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount) + try { + controller.init() + val transitionAlphaAmount = .5f + ViewUtils.attachView(keyguardStatusBarView) + + looper.processAllMessages() + updateStateToKeyguard() + kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal) + runCurrent() + controller.updateCommunalAlphaTransition(transitionAlphaAmount) + kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank) + runCurrent() + assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount) + } finally { + ViewUtils.detachView(keyguardStatusBarView) + } } /** diff --git a/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml b/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml deleted file mode 100644 index 495fbb893eac..000000000000 --- a/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 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. ---> - -<shape - xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <corners android:radius="24dp"/> - <gradient - android:angle="0" - android:startColor="#00000000" - android:endColor="#ff000000" - android:type="linear" /> -</shape> diff --git a/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml b/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml deleted file mode 100644 index de0a6201cb09..000000000000 --- a/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 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 - --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <!-- gradient from 25% in the center to 100% at edges --> - <gradient - android:type="radial" - android:gradientRadius="40%p" - android:startColor="#AE000000" - android:endColor="#00000000" /> -</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml deleted file mode 100644 index e63aa211f9f1..000000000000 --- a/packages/SystemUI/res/layout/media_recommendation_view.xml +++ /dev/null @@ -1,90 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 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 - --> -<!-- Layout for media recommendation item inside QSPanel carousel --> -<merge xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- Album cover --> - <ImageView - android:id="@+id/media_cover" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:translationZ="0dp" - android:scaleType="matrix" - android:adjustViewBounds="true" - android:clipToOutline="true" - android:layerType="hardware" - android:background="@drawable/bg_smartspace_media_item"/> - - <!-- App icon --> - <com.android.internal.widget.CachingIconView - android:id="@+id/media_rec_app_icon" - android:layout_width="@dimen/qs_media_rec_album_icon_size" - android:layout_height="@dimen/qs_media_rec_album_icon_size" - android:minWidth="@dimen/qs_media_rec_album_icon_size" - android:minHeight="@dimen/qs_media_rec_album_icon_size" - android:layout_marginStart="@dimen/qs_media_info_spacing" - android:layout_marginTop="@dimen/qs_media_info_spacing"/> - - <!-- Artist name --> - <TextView - android:id="@+id/media_title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/qs_media_info_spacing" - android:layout_marginEnd="@dimen/qs_media_info_spacing" - android:layout_marginBottom="@dimen/qs_media_rec_album_title_bottom_margin" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:singleLine="true" - android:textSize="12sp" - android:gravity="top" - android:layout_gravity="bottom" - android:importantForAccessibility="no"/> - - <!-- Album name --> - <TextView - android:id="@+id/media_subtitle" - android:layout_width="match_parent" - android:layout_height="@dimen/qs_media_rec_album_subtitle_height" - android:layout_marginEnd="@dimen/qs_media_info_spacing" - android:layout_marginStart="@dimen/qs_media_info_spacing" - android:layout_marginBottom="@dimen/qs_media_info_spacing" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:singleLine="true" - android:textSize="11sp" - android:gravity="center_vertical" - android:layout_gravity="bottom" - android:importantForAccessibility="no"/> - - <!-- Seek Bar --> - <SeekBar - android:id="@+id/media_progress_bar" - android:layout_width="match_parent" - android:layout_height="12dp" - android:layout_gravity="bottom" - android:maxHeight="@dimen/qs_media_enabled_seekbar_height" - android:thumb="@android:color/transparent" - android:splitTrack="false" - android:clickable="false" - android:progressTint="?android:attr/textColorPrimary" - android:progressBackgroundTint="?android:attr/textColorTertiary" - android:paddingTop="5dp" - android:paddingBottom="5dp" - android:paddingStart="0dp" - android:paddingEnd="0dp" - android:layout_marginEnd="@dimen/qs_media_info_spacing" - android:layout_marginStart="@dimen/qs_media_info_spacing" - android:layout_marginBottom="@dimen/qs_media_info_spacing"/> -</merge>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_recommendations.xml b/packages/SystemUI/res/layout/media_recommendations.xml deleted file mode 100644 index 65fc19c5b2a4..000000000000 --- a/packages/SystemUI/res/layout/media_recommendations.xml +++ /dev/null @@ -1,75 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 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 - --> - -<!-- Layout for media recommendations inside QSPanel carousel --> -<com.android.systemui.util.animation.TransitionLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/media_recommendations_updated" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:clipChildren="false" - android:clipToPadding="false" - android:forceHasOverlappingRendering="false" - android:background="@drawable/qs_media_background" - android:theme="@style/MediaPlayer"> - - <!-- This view just ensures the full media player is a certain height. --> - <View - android:id="@+id/sizing_view" - android:layout_width="match_parent" - android:layout_height="@dimen/qs_media_session_height_expanded" /> - - <TextView - android:id="@+id/media_rec_title" - style="@style/MediaPlayer.Recommendation.Header" - android:text="@string/controls_media_smartspace_rec_header"/> - - <FrameLayout - android:id="@+id/media_cover1_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" - > - - <include - layout="@layout/media_recommendation_view"/> - - </FrameLayout> - - - <FrameLayout - android:id="@+id/media_cover2_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" - > - - <include - layout="@layout/media_recommendation_view"/> - - </FrameLayout> - - <FrameLayout - android:id="@+id/media_cover3_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" - > - - <include - layout="@layout/media_recommendation_view"/> - - </FrameLayout> - - <include - layout="@layout/media_long_press_menu" /> - -</com.android.systemui.util.animation.TransitionLayout> diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml index 41bb37efa623..f4f0424ade98 100644 --- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml @@ -30,11 +30,6 @@ not appear immediately after user swipes to the side --> <dimen name="qs_tiles_page_horizontal_margin">20dp</dimen> - <!-- Size of Smartspace media recommendations cards in the QSPanel carousel --> - <dimen name="qs_media_rec_icon_top_margin">16dp</dimen> - <dimen name="qs_media_rec_album_size">112dp</dimen> - <dimen name="qs_media_rec_album_side_margin">16dp</dimen> - <dimen name="controls_panel_corner_radius">40dp</dimen> <dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 4995858f95a4..78e719f6289a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -98,10 +98,6 @@ TODO (b/293252410) - change this comment/resource when flag is enabled --> <integer name="small_land_lockscreen_quick_settings_max_rows">2</integer> - <!-- If the dp width of the available space is <= this value, potentially adjust the number - of media recommendation items--> - <integer name="default_qs_media_rec_width_dp">380</integer> - <!-- The number of columns that the top level tiles span in the QuickSettings --> <!-- The default tiles to display in QuickSettings --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7c370d3bc064..c8e31079fca0 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1342,19 +1342,6 @@ <dimen name="qs_media_session_collapsed_legacy_guideline">144dp</dimen> <dimen name="qs_media_session_collapsed_guideline">168dp</dimen> - <!-- Size of Smartspace media recommendations cards in the QSPanel carousel --> - <dimen name="qs_media_rec_default_width">380dp</dimen> - <dimen name="qs_media_rec_icon_top_margin">16dp</dimen> - <dimen name="qs_media_rec_album_icon_size">16dp</dimen> - <dimen name="qs_media_rec_album_size">88dp</dimen> - <dimen name="qs_media_rec_album_width">110dp</dimen> - <dimen name="qs_media_rec_album_height_expanded">108dp</dimen> - <dimen name="qs_media_rec_album_height_collapsed">77dp</dimen> - <dimen name="qs_media_rec_album_side_margin">16dp</dimen> - <dimen name="qs_media_rec_album_bottom_margin">8dp</dimen> - <dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen> - <dimen name="qs_media_rec_album_subtitle_height">12dp</dimen> - <!-- Chipbar --> <!-- (Used for media tap-to-transfer chip for sender device and active unlock) --> <dimen name="chipbar_outer_padding">16dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4431ddadc8de..7895ff7b90f6 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -895,57 +895,6 @@ <item name="android:textColor">@android:color/system_on_primary_dark</item> </style> - <style name="MediaPlayer.Recommendation"/> - - <style name="MediaPlayer.Recommendation.Header"> - <item name="android:layout_width">wrap_content</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:layout_marginTop">@dimen/qs_media_padding</item> - <item name="android:layout_marginStart">@dimen/qs_media_padding</item> - <item name="android:fontFamily">=@*android:string/config_headlineFontFamilyMedium</item> - <item name="android:singleLine">true</item> - <item name="android:textSize">14sp</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> - </style> - - <style name="MediaPlayer.Recommendation.AlbumContainer"> - <item name="android:layout_width">@dimen/qs_media_rec_album_size</item> - <item name="android:layout_height">@dimen/qs_media_rec_album_size</item> - <item name="android:background">@drawable/qs_media_light_source</item> - <item name="android:layout_marginTop">@dimen/qs_media_padding</item> - <item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item> - </style> - - <style name="MediaPlayer.Recommendation.AlbumContainer.Updated"> - <item name="android:layout_width">@dimen/qs_media_rec_album_width</item> - <item name="android:minWidth">@dimen/qs_media_rec_album_width</item> - <item name="android:minHeight">@dimen/qs_media_rec_album_height_collapsed</item> - <item name="android:background">@drawable/qs_media_light_source</item> - <item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item> - </style> - - <style name="MediaPlayer.Recommendation.Album"> - <item name="android:backgroundTint">@color/media_player_album_bg</item> - </style> - - <style name="MediaPlayer.Recommendation.Text"> - <item name="android:layout_width">@dimen/qs_media_rec_album_size</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:maxLines">1</item> - <item name="android:ellipsize">end</item> - <item name="android:textSize">14sp</item> - <item name="android:gravity">start</item> - </style> - - <style name="MediaPlayer.Recommendation.Text.Title"> - <item name="android:textColor">?android:attr/textColorPrimary</item> - </style> - - <style name="MediaPlayer.Recommendation.Text.Subtitle"> - <item name="android:textColor">?android:attr/textColorSecondary</item> - </style> - - <!-- Used to style charging animation AVD animation --> <style name="ChargingAnim" /> diff --git a/packages/SystemUI/res/xml/media_recommendations_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml deleted file mode 100644 index d3be3c7de5ad..000000000000 --- a/packages/SystemUI/res/xml/media_recommendations_collapsed.xml +++ /dev/null @@ -1,64 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 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 - --> -<ConstraintSet - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - > - - <Constraint - android:id="@+id/sizing_view" - android:layout_width="match_parent" - android:layout_height="@dimen/qs_media_session_height_collapsed" - /> - - <Constraint - android:id="@+id/media_rec_title" - style="@style/MediaPlayer.Recommendation.Header" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"/> - - <Constraint - android:id="@+id/media_cover1_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" - android:layout_height="@dimen/qs_media_rec_album_height_collapsed" - android:layout_marginEnd="@dimen/qs_media_info_spacing" - android:layout_marginStart="@dimen/qs_media_padding" - app:layout_constraintTop_toBottomOf="@+id/media_rec_title" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/> - - - <Constraint - android:id="@+id/media_cover2_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" - android:layout_height="@dimen/qs_media_rec_album_height_collapsed" - android:layout_marginEnd="@dimen/qs_media_info_spacing" - app:layout_constraintTop_toBottomOf="@+id/media_rec_title" - app:layout_constraintStart_toEndOf="@id/media_cover1_container" - app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/> - - <Constraint - android:id="@+id/media_cover3_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" - android:layout_height="@dimen/qs_media_rec_album_height_collapsed" - android:layout_marginEnd="@dimen/qs_media_padding" - app:layout_constraintTop_toBottomOf="@+id/media_rec_title" - app:layout_constraintStart_toEndOf="@id/media_cover2_container" - app:layout_constraintEnd_toEndOf="parent"/> - - -</ConstraintSet> diff --git a/packages/SystemUI/res/xml/media_recommendations_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_expanded.xml deleted file mode 100644 index 88c70552e9e8..000000000000 --- a/packages/SystemUI/res/xml/media_recommendations_expanded.xml +++ /dev/null @@ -1,71 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 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 - --> -<ConstraintSet - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - > - - <Constraint - android:id="@+id/sizing_view" - android:layout_width="match_parent" - android:layout_height="@dimen/qs_media_session_height_expanded" - /> - - <Constraint - android:id="@+id/media_rec_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/qs_media_padding" - android:layout_marginStart="@dimen/qs_media_padding" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:singleLine="true" - android:textSize="14sp" - android:textColor="@color/notification_primary_text_color"/> - - <Constraint - android:id="@+id/media_cover1_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" - android:layout_height="@dimen/qs_media_rec_album_height_expanded" - android:layout_marginEnd="@dimen/qs_media_info_spacing" - android:layout_marginStart="@dimen/qs_media_padding" - app:layout_constraintTop_toBottomOf="@+id/media_rec_title" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/> - - - <Constraint - android:id="@+id/media_cover2_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" - android:layout_height="@dimen/qs_media_rec_album_height_expanded" - android:layout_marginEnd="@dimen/qs_media_info_spacing" - app:layout_constraintTop_toBottomOf="@+id/media_rec_title" - app:layout_constraintStart_toEndOf="@id/media_cover1_container" - app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/> - - <Constraint - android:id="@+id/media_cover3_container" - style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated" - android:layout_height="@dimen/qs_media_rec_album_height_expanded" - android:layout_marginEnd="@dimen/qs_media_padding" - app:layout_constraintTop_toBottomOf="@+id/media_rec_title" - app:layout_constraintStart_toEndOf="@id/media_cover2_container" - app:layout_constraintEnd_toEndOf="parent"/> - - -</ConstraintSet> diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt index 097d50bb8f9d..9db7b50905f8 100644 --- a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt @@ -16,7 +16,9 @@ package com.android.systemui.common.domain.interactor +import android.util.Log import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.model.StateChange import com.android.systemui.model.SysUiState @@ -26,20 +28,36 @@ import javax.inject.Inject @SysUISingleton class SysUIStateDisplaysInteractor @Inject -constructor(private val sysUIStateRepository: PerDisplayRepository<SysUiState>) { +constructor( + private val sysUIStateRepository: PerDisplayRepository<SysUiState>, + private val displayRepository: DisplayRepository, +) { /** * Sets the flags on the given [targetDisplayId] based on the [stateChanges], while making sure * that those flags are not set in any other display. */ fun setFlagsExclusivelyToDisplay(targetDisplayId: Int, stateChanges: StateChange) { - sysUIStateRepository.forEachInstance { displayId, instance -> - if (displayId == targetDisplayId) { - stateChanges.applyTo(instance) - } else { - stateChanges.clearAllChangedFlagsIn(instance) - } + if (SysUiState.DEBUG) { + Log.d(TAG, "Setting flags $stateChanges only for display $targetDisplayId") } + displayRepository.displays.value + .mapNotNull { sysUIStateRepository[it.displayId] } + .apply { + // Let's first modify all states, without committing changes ... + forEach { displaySysUIState -> + if (displaySysUIState.displayId == targetDisplayId) { + stateChanges.applyTo(displaySysUIState) + } else { + stateChanges.clearFrom(displaySysUIState) + } + } + // ... And commit changes at the end + forEach { sysuiState -> sysuiState.commitUpdate() } + } } -} + private companion object { + const val TAG = "SysUIStateInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt index 04f245e91914..36d3eb51283a 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt @@ -91,18 +91,6 @@ interface PerDisplayRepository<T> { /** Debug name for this repository, mainly for tracing and logging. */ val debugName: String - - /** - * Invokes the specified action on each instance held by this repository. - * - * The action will receive the displayId and the instance associated with that display. - * If there is no instance for the display, the action is not called. - */ - fun forEachInstance(action: (Int, T) -> Unit) { - displayIds.forEach { displayId -> - get(displayId)?.let { instance -> action(displayId, instance) } - } - } } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt deleted file mode 100644 index 0cb36edfd382..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.domain.pipeline.interactor - -import android.content.Context -import android.content.Intent -import android.provider.Settings -import android.util.Log -import androidx.annotation.VisibleForTesting -import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.animation.Expandable -import com.android.systemui.broadcast.BroadcastSender -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.media.controls.data.repository.MediaFilterRepository -import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor -import com.android.systemui.media.controls.shared.model.MediaRecModel -import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel -import com.android.systemui.media.controls.shared.model.SmartspaceMediaData -import com.android.systemui.plugins.ActivityStarter -import java.net.URISyntaxException -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -/** Encapsulates business logic for media recommendation */ -@SysUISingleton -class MediaRecommendationsInteractor -@Inject -constructor( - @Application applicationScope: CoroutineScope, - @Application private val applicationContext: Context, - private val repository: MediaFilterRepository, - private val mediaDataProcessor: MediaDataProcessor, - private val broadcastSender: BroadcastSender, - private val activityStarter: ActivityStarter, -) { - - val recommendations: Flow<MediaRecommendationsModel> = - repository.smartspaceMediaData.map { toRecommendationsModel(it) }.distinctUntilChanged() - - /** Indicates whether the recommendations card is active. */ - val isActive: StateFlow<Boolean> = - repository.smartspaceMediaData - .map { it.isActive } - .distinctUntilChanged() - .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false) - - fun removeMediaRecommendations(key: String, dismissIntent: Intent?, delayMs: Long) { - mediaDataProcessor.dismissSmartspaceRecommendation(key, delayMs) - if (dismissIntent == null) { - Log.w(TAG, "Cannot create dismiss action click action: extras missing dismiss_intent.") - return - } - - val className = dismissIntent.component?.className - if (className == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) { - // Dismiss the card Smartspace data through Smartspace trampoline activity. - applicationContext.startActivity(dismissIntent) - } else { - broadcastSender.sendBroadcast(dismissIntent) - } - } - - fun startSettings() { - activityStarter.startActivity(SETTINGS_INTENT, /* dismissShade= */ true) - } - - fun startClickIntent(expandable: Expandable, intent: Intent) { - if (shouldActivityOpenInForeground(intent)) { - // Request to unlock the device if the activity needs to be opened in foreground. - activityStarter.postStartActivityDismissingKeyguard( - intent, - 0 /* delay */, - expandable.activityTransitionController( - InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER - ), - ) - } else { - // Otherwise, open the activity in background directly. - applicationContext.startActivity(intent) - } - } - - /** Returns if the action will open the activity in foreground. */ - private fun shouldActivityOpenInForeground(intent: Intent): Boolean { - val intentString = intent.extras?.getString(EXTRAS_SMARTSPACE_INTENT) ?: return false - try { - val wrapperIntent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME) - return wrapperIntent.getBooleanExtra(KEY_SMARTSPACE_OPEN_IN_FOREGROUND, false) - } catch (e: URISyntaxException) { - Log.wtf(TAG, "Failed to create intent from URI: $intentString") - e.printStackTrace() - } - return false - } - - private fun toRecommendationsModel(data: SmartspaceMediaData): MediaRecommendationsModel { - val mediaRecs = ArrayList<MediaRecModel>() - data.recommendations.forEach { - with(it) { mediaRecs.add(MediaRecModel(intent, title, subtitle, icon, extras)) } - } - return with(data) { - MediaRecommendationsModel( - key = targetId, - uid = getUid(applicationContext), - packageName = packageName, - instanceId = instanceId, - appName = getAppName(applicationContext), - dismissIntent = dismissIntent, - areRecommendationsValid = isValid(), - mediaRecs = mediaRecs, - ) - } - } - - fun switchToMediaControl(packageName: String) { - repository.setMediaFromRecPackageName(packageName) - } - - companion object { - - private const val TAG = "MediaRecommendationsInteractor" - - // TODO (b/237284176) : move AGSA reference out. - private const val EXTRAS_SMARTSPACE_INTENT = - "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT" - @VisibleForTesting - const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = - "com.google.android.apps.gsa.staticplugins.opa.smartspace." + - "ExportedSmartspaceTrampolineActivity" - - private const val KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND" - - private val SETTINGS_INTENT = Intent(Settings.ACTION_MEDIA_CONTROLS_SETTINGS) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt deleted file mode 100644 index 4877d18de7ab..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.ui.binder - -import android.app.WallpaperColors -import android.content.Context -import android.content.res.ColorStateList -import android.content.res.Configuration -import android.graphics.Bitmap -import android.graphics.Color -import android.graphics.Matrix -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.Drawable -import android.graphics.drawable.GradientDrawable -import android.graphics.drawable.Icon -import android.graphics.drawable.LayerDrawable -import android.os.Trace -import android.util.TypedValue -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.content.res.AppCompatResources -import androidx.constraintlayout.widget.ConstraintSet -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.animation.Expandable -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS -import com.android.systemui.media.controls.ui.animation.surfaceFromScheme -import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme -import com.android.systemui.media.controls.ui.animation.textSecondaryFromScheme -import com.android.systemui.media.controls.ui.controller.MediaViewController -import com.android.systemui.media.controls.ui.util.MediaArtworkHelper -import com.android.systemui.media.controls.ui.view.RecommendationViewHolder -import com.android.systemui.media.controls.ui.viewmodel.MediaRecViewModel -import com.android.systemui.media.controls.ui.viewmodel.MediaRecommendationsViewModel -import com.android.systemui.media.controls.ui.viewmodel.MediaRecsCardViewModel -import com.android.systemui.monet.ColorScheme -import com.android.systemui.monet.Style -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.res.R -import com.android.systemui.util.animation.TransitionLayout -import kotlin.math.min -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.collectLatest -import com.android.app.tracing.coroutines.launchTraced as launch -import kotlinx.coroutines.withContext - -private const val TAG = "MediaRecommendationsViewBinder" -private const val MEDIA_REC_SCRIM_START_ALPHA = 0.15f -private const val MEDIA_REC_SCRIM_END_ALPHA = 1.0f - -object MediaRecommendationsViewBinder { - - /** Binds recommendations view holder to the given view-model */ - fun bind( - viewHolder: RecommendationViewHolder, - viewModel: MediaRecommendationsViewModel, - mediaViewController: MediaViewController, - falsingManager: FalsingManager, - backgroundDispatcher: CoroutineDispatcher, - mainDispatcher: CoroutineDispatcher, - ) { - mediaViewController.recsConfigurationChangeListener = this::updateRecommendationsVisibility - val cardView = viewHolder.recommendations - cardView.repeatWhenAttached { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.mediaRecsCard.collectLatest { viewModel -> - viewModel?.let { - bindRecsCard( - viewHolder, - it, - mediaViewController, - falsingManager, - backgroundDispatcher, - mainDispatcher, - ) - } - } - } - } - } - } - } - - suspend fun bindRecsCard( - viewHolder: RecommendationViewHolder, - viewModel: MediaRecsCardViewModel, - viewController: MediaViewController, - falsingManager: FalsingManager, - backgroundDispatcher: CoroutineDispatcher, - mainDispatcher: CoroutineDispatcher, - ) { - // Set up media control location and its listener. - viewModel.onLocationChanged(viewController.currentEndLocation) - viewController.locationChangeListener = viewModel.onLocationChanged - - // Bind main card. - viewHolder.recommendations.contentDescription = - viewModel.contentDescription.invoke(viewController.isGutsVisible) - - viewHolder.recommendations.setOnClickListener { - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener - viewModel.onClicked(Expandable.fromView(it)) - } - - viewHolder.recommendations.setOnLongClickListener { - if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) - return@setOnLongClickListener true - if (!viewController.isGutsVisible) { - openGuts(viewHolder, viewModel, viewController) - } else { - closeGuts(viewHolder, viewModel, viewController) - } - return@setOnLongClickListener true - } - - // Bind colors - val appIcon = viewModel.mediaRecs.first().appIcon - fetchAndUpdateColors(viewHolder, appIcon, backgroundDispatcher, mainDispatcher) - // Bind all recommendations. - bindRecommendationsList( - viewHolder, - viewModel.mediaRecs, - falsingManager, - backgroundDispatcher, - mainDispatcher, - ) - updateRecommendationsVisibility(viewController, viewHolder.recommendations) - - // Set visibility of recommendations. - val expandedSet: ConstraintSet = viewController.expandedLayout - val collapsedSet: ConstraintSet = viewController.collapsedLayout - viewHolder.mediaTitles.forEach { - setVisibleAndAlpha(expandedSet, it.id, viewModel.areTitlesVisible) - setVisibleAndAlpha(collapsedSet, it.id, viewModel.areTitlesVisible) - } - viewHolder.mediaSubtitles.forEach { - setVisibleAndAlpha(expandedSet, it.id, viewModel.areSubtitlesVisible) - setVisibleAndAlpha(collapsedSet, it.id, viewModel.areSubtitlesVisible) - } - - bindRecommendationsGuts(viewHolder, viewModel, viewController, falsingManager) - - viewController.refreshState() - } - - private fun bindRecommendationsGuts( - viewHolder: RecommendationViewHolder, - viewModel: MediaRecsCardViewModel, - viewController: MediaViewController, - falsingManager: FalsingManager, - ) { - val gutsViewHolder = viewHolder.gutsViewHolder - val gutsViewModel = viewModel.gutsMenu - - gutsViewHolder.gutsText.text = gutsViewModel.gutsText - gutsViewHolder.dismissText.visibility = View.VISIBLE - gutsViewHolder.dismiss.isEnabled = true - gutsViewHolder.dismiss.setOnClickListener { - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener - closeGuts(viewHolder, viewModel, viewController) - gutsViewModel.onDismissClicked() - } - - gutsViewHolder.cancelText.background = gutsViewModel.cancelTextBackground - gutsViewHolder.cancel.setOnClickListener { - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - closeGuts(viewHolder, viewModel, viewController) - } - } - - gutsViewHolder.settings.setOnClickListener { - if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - gutsViewModel.onSettingsClicked.invoke() - } - } - - gutsViewHolder.setDismissible(gutsViewModel.isDismissEnabled) - } - - private suspend fun bindRecommendationsList( - viewHolder: RecommendationViewHolder, - mediaRecs: List<MediaRecViewModel>, - falsingManager: FalsingManager, - backgroundDispatcher: CoroutineDispatcher, - mainDispatcher: CoroutineDispatcher, - ) { - mediaRecs.forEachIndexed { index, mediaRecViewModel -> - if (index >= NUM_REQUIRED_RECOMMENDATIONS) return@forEachIndexed - - val appIconView = viewHolder.mediaAppIcons[index] - appIconView.clearColorFilter() - appIconView.setImageDrawable(mediaRecViewModel.appIcon) - - val mediaCoverContainer = viewHolder.mediaCoverContainers[index] - mediaCoverContainer.setOnClickListener { - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener - mediaRecViewModel.onClicked(Expandable.fromView(it), index) - } - mediaCoverContainer.setOnLongClickListener { - if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) - return@setOnLongClickListener true - (it.parent as View).performLongClick() - return@setOnLongClickListener true - } - - val mediaCover = viewHolder.mediaCoverItems[index] - bindRecommendationArtwork( - mediaCover.context, - viewHolder, - mediaRecViewModel, - index, - backgroundDispatcher, - mainDispatcher, - ) - mediaCover.contentDescription = mediaRecViewModel.contentDescription - - val title = viewHolder.mediaTitles[index] - title.text = mediaRecViewModel.title - - val subtitle = viewHolder.mediaSubtitles[index] - subtitle.text = mediaRecViewModel.subtitle - - val progressBar = viewHolder.mediaProgressBars[index] - progressBar.progress = mediaRecViewModel.progress - if (mediaRecViewModel.progress == 0) { - progressBar.visibility = View.GONE - } - } - } - - private fun openGuts( - viewHolder: RecommendationViewHolder, - viewModel: MediaRecsCardViewModel, - mediaViewController: MediaViewController, - ) { - viewHolder.marquee(true, MediaViewController.GUTS_ANIMATION_DURATION) - mediaViewController.openGuts() - viewHolder.recommendations.contentDescription = viewModel.contentDescription.invoke(true) - viewModel.onLongClicked.invoke() - } - - private fun closeGuts( - viewHolder: RecommendationViewHolder, - mediaRecsCardViewModel: MediaRecsCardViewModel, - mediaViewController: MediaViewController, - ) { - viewHolder.marquee(false, MediaViewController.GUTS_ANIMATION_DURATION) - mediaViewController.closeGuts(false) - viewHolder.recommendations.contentDescription = - mediaRecsCardViewModel.contentDescription.invoke(false) - } - - private fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) { - set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else ConstraintSet.GONE) - set.setAlpha(resId, if (visible) 1.0f else 0.0f) - } - - fun updateRecommendationsVisibility( - mediaViewController: MediaViewController, - cardView: TransitionLayout, - ) { - val fittedRecsNum = getNumberOfFittedRecommendations(cardView.context) - val expandedSet = mediaViewController.expandedLayout - val collapsedSet = mediaViewController.collapsedLayout - val mediaCoverContainers = getMediaCoverContainers(cardView) - // Hide media cover that cannot fit in the recommendation card. - mediaCoverContainers.forEachIndexed { index, container -> - setVisibleAndAlpha(expandedSet, container.id, index < fittedRecsNum) - setVisibleAndAlpha(collapsedSet, container.id, index < fittedRecsNum) - } - } - - private fun getMediaCoverContainers(cardView: TransitionLayout): List<ViewGroup> { - return listOf<ViewGroup>( - cardView.requireViewById(R.id.media_cover1_container), - cardView.requireViewById(R.id.media_cover2_container), - cardView.requireViewById(R.id.media_cover3_container), - ) - } - - private fun getNumberOfFittedRecommendations(context: Context): Int { - val res = context.resources - val config = res.configuration - val defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp) - val recCoverWidth = - (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) + - res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2) - - // On landscape, media controls should take half of the screen width. - val displayAvailableDpWidth = - if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { - config.screenWidthDp / 2 - } else { - config.screenWidthDp - } - val fittedNum = - if (displayAvailableDpWidth > defaultDpWidth) { - val recCoverDefaultWidth = - res.getDimensionPixelSize(R.dimen.qs_media_rec_default_width) - recCoverDefaultWidth / recCoverWidth - } else { - val displayAvailableWidth = - TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - displayAvailableDpWidth.toFloat(), - res.displayMetrics, - ) - .toInt() - displayAvailableWidth / recCoverWidth - } - return min(fittedNum.toDouble(), NUM_REQUIRED_RECOMMENDATIONS.toDouble()).toInt() - } - - private suspend fun bindRecommendationArtwork( - context: Context, - viewHolder: RecommendationViewHolder, - viewModel: MediaRecViewModel, - index: Int, - backgroundDispatcher: CoroutineDispatcher, - mainDispatcher: CoroutineDispatcher, - ) { - val traceCookie = viewHolder.hashCode() - val traceName = "MediaRecommendationsViewBinder#bindRecommendationArtwork" - Trace.beginAsyncSection(traceName, traceCookie) - - // Capture width & height from views in foreground for artwork scaling in background - val width = context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) - val height = - context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_height_expanded) - - withContext(backgroundDispatcher) { - val artwork = - getRecCoverBackground( - context, - viewModel.albumIcon, - width, - height, - backgroundDispatcher, - ) - withContext(mainDispatcher) { - val mediaCover = viewHolder.mediaCoverItems[index] - val coverMatrix = Matrix(mediaCover.imageMatrix) - coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height) - mediaCover.imageMatrix = coverMatrix - mediaCover.setImageDrawable(artwork) - } - } - } - - /** Returns the recommendation album cover of [width]x[height] size. */ - private suspend fun getRecCoverBackground( - context: Context, - icon: Icon?, - width: Int, - height: Int, - backgroundDispatcher: CoroutineDispatcher, - ): Drawable = - withContext(backgroundDispatcher) { - return@withContext MediaArtworkHelper.getWallpaperColor( - context, - backgroundDispatcher, - icon, - TAG, - ) - ?.let { wallpaperColors -> - addGradientToRecommendationAlbum( - context, - icon!!, - ColorScheme(wallpaperColors, true, Style.CONTENT), - width, - height, - ) - } ?: ColorDrawable(Color.TRANSPARENT) - } - - private fun addGradientToRecommendationAlbum( - context: Context, - artworkIcon: Icon, - mutableColorScheme: ColorScheme, - width: Int, - height: Int, - ): LayerDrawable { - // First try scaling rec card using bitmap drawable. - // If returns null, set drawable bounds. - val albumArt = - getScaledRecommendationCover(context, artworkIcon, width, height) - ?: MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height) - val gradient = - AppCompatResources.getDrawable(context, R.drawable.qs_media_rec_scrim)?.mutate() - as GradientDrawable - return MediaArtworkHelper.setUpGradientColorOnDrawable( - albumArt, - gradient, - mutableColorScheme, - MEDIA_REC_SCRIM_START_ALPHA, - MEDIA_REC_SCRIM_END_ALPHA, - ) - } - - /** Returns a [Drawable] of a given [artworkIcon] scaled to [width]x[height] size, . */ - private fun getScaledRecommendationCover( - context: Context, - artworkIcon: Icon, - width: Int, - height: Int, - ): Drawable? { - check(width > 0) { "Width must be a positive number but was $width" } - check(height > 0) { "Height must be a positive number but was $height" } - - return if ( - artworkIcon.type == Icon.TYPE_BITMAP || artworkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP - ) { - artworkIcon.bitmap?.let { - val bitmap = Bitmap.createScaledBitmap(it, width, height, false) - BitmapDrawable(context.resources, bitmap) - } - } else { - null - } - } - - private suspend fun fetchAndUpdateColors( - viewHolder: RecommendationViewHolder, - appIcon: Drawable, - backgroundDispatcher: CoroutineDispatcher, - mainDispatcher: CoroutineDispatcher, - ) = - withContext(backgroundDispatcher) { - val colorScheme = - ColorScheme(WallpaperColors.fromDrawable(appIcon), /* darkTheme= */ true) - withContext(mainDispatcher) { - val backgroundColor = surfaceFromScheme(colorScheme) - val textPrimaryColor = textPrimaryFromScheme(colorScheme) - val textSecondaryColor = textSecondaryFromScheme(colorScheme) - - viewHolder.cardTitle.setTextColor(textPrimaryColor) - viewHolder.recommendations.setBackgroundTintList( - ColorStateList.valueOf(backgroundColor) - ) - - viewHolder.mediaTitles.forEach { it.setTextColor(textPrimaryColor) } - viewHolder.mediaSubtitles.forEach { it.setTextColor(textSecondaryColor) } - viewHolder.mediaProgressBars.forEach { - it.progressTintList = ColorStateList.valueOf(textPrimaryColor) - } - - viewHolder.gutsViewHolder.setColors(colorScheme) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index 7b1ae57ed421..ac6343c6bb64 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -61,14 +61,12 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder -import com.android.systemui.media.controls.ui.binder.MediaRecommendationsViewBinder import com.android.systemui.media.controls.ui.util.MediaViewModelCallback import com.android.systemui.media.controls.ui.util.MediaViewModelListUpdateCallback import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.controls.ui.view.MediaScrollView import com.android.systemui.media.controls.ui.view.MediaViewHolder -import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.MediaCarouselViewModel import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel import com.android.systemui.media.controls.util.MediaUiEventLogger @@ -478,41 +476,10 @@ constructor( MediaPlayerData.isSwipedAway = false } - override fun onSmartspaceMediaDataLoaded( - key: String, - data: SmartspaceMediaData, - shouldPrioritize: Boolean, - ) { - debugLogger.logRecommendationLoaded(key, data.isActive) - // Log the case where the hidden media carousel with the existed inactive resume - // media is shown by the Smartspace signal. - if (data.isActive) { - addSmartspaceMediaRecommendations(key, data, shouldPrioritize) - } else { - // Handle update to inactive as a removal - onSmartspaceMediaDataRemoved(data.targetId, immediately = true) - } - MediaPlayerData.isSwipedAway = false - } - override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { debugLogger.logMediaRemoved(key, userInitiated) removePlayer(key, userInitiated = userInitiated) } - - override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { - debugLogger.logRecommendationRemoved(key, immediately) - if (immediately || isReorderingAllowed) { - removePlayer(key) - if (!immediately) { - // Although it wasn't requested, we were able to process the removal - // immediately since reordering is allowed. So, notify hosts to update - updateHostVisibility() - } - } else { - keysNeedRemoval.add(key) - } - } } ) } @@ -655,22 +622,6 @@ constructor( mediaContent.addView(viewHolder.player, position) controllerById[commonViewModel.instanceId.toString()] = viewController } - is MediaCommonViewModel.MediaRecommendations -> { - val viewHolder = - RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent) - viewController.attachRecommendations(viewHolder) - viewController.recommendationViewHolder?.recommendations?.layoutParams = lp - MediaRecommendationsViewBinder.bind( - viewHolder, - commonViewModel.recsViewModel, - viewController, - falsingManager, - backgroundDispatcher, - mainDispatcher, - ) - mediaContent.addView(viewHolder.recommendations, position) - controllerById[commonViewModel.key] = viewController - } } viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded) updateViewControllerToState(viewController, noAnimation = true) @@ -695,21 +646,10 @@ constructor( } private fun onRemoved(commonViewModel: MediaCommonViewModel) { - val id = - when (commonViewModel) { - is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString() - is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key - } + val id = (commonViewModel as MediaCommonViewModel.MediaControl).instanceId.toString() controllerById.remove(id)?.let { - when (commonViewModel) { - is MediaCommonViewModel.MediaControl -> { - mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player) - mediaContent.removeView(it.mediaViewHolder!!.player) - } - is MediaCommonViewModel.MediaRecommendations -> { - mediaContent.removeView(it.recommendationViewHolder!!.recommendations) - } - } + mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player) + mediaContent.removeView(it.mediaViewHolder!!.player) it.onDestroy() mediaCarouselScrollHandler.onPlayersChanged() updatePageIndicator() @@ -718,21 +658,10 @@ constructor( } private fun onMoved(commonViewModel: MediaCommonViewModel, from: Int, to: Int) { - val id = - when (commonViewModel) { - is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString() - is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key - } + val id = (commonViewModel as MediaCommonViewModel.MediaControl).instanceId.toString() controllerById[id]?.let { mediaContent.removeViewAt(from) - when (commonViewModel) { - is MediaCommonViewModel.MediaControl -> { - mediaContent.addView(it.mediaViewHolder!!.player, to) - } - is MediaCommonViewModel.MediaRecommendations -> { - mediaContent.addView(it.recommendationViewHolder!!.recommendations, to) - } - } + mediaContent.addView(it.mediaViewHolder!!.player, to) } updatePageIndicator() mediaCarouselScrollHandler.onPlayersChanged() @@ -746,11 +675,9 @@ constructor( val viewIds = viewModels .map { mediaCommonViewModel -> - when (mediaCommonViewModel) { - is MediaCommonViewModel.MediaControl -> - mediaCommonViewModel.instanceId.toString() - is MediaCommonViewModel.MediaRecommendations -> mediaCommonViewModel.key - } + (mediaCommonViewModel as MediaCommonViewModel.MediaControl) + .instanceId + .toString() } .toHashSet() controllerById @@ -758,7 +685,6 @@ constructor( .forEach { mediaCarouselScrollHandler.onPrePlayerRemoved(it.value.mediaViewHolder?.player) mediaContent.removeView(it.value.mediaViewHolder?.player) - mediaContent.removeView(it.value.recommendationViewHolder?.recommendations) it.value.onDestroy() mediaCarouselScrollHandler.onPlayersChanged() updatePageIndicator() @@ -808,9 +734,6 @@ constructor( mediaContent.removeAllViews() for (mediaPlayer in MediaPlayerData.players()) { mediaPlayer.mediaViewHolder?.let { mediaContent.addView(it.player) } - ?: mediaPlayer.recommendationViewHolder?.let { - mediaContent.addView(it.recommendations) - } } mediaCarouselScrollHandler.onPlayersChanged() mediaControlChipInteractor.updateMediaControlChipModelLegacy( @@ -980,67 +903,6 @@ constructor( return MediaViewHolder.create(LayoutInflater.from(context), mediaContent) } - private fun addSmartspaceMediaRecommendations( - key: String, - data: SmartspaceMediaData, - shouldPrioritize: Boolean, - ) = - traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") { - if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel") - MediaPlayerData.getMediaPlayer(key)?.let { - Log.w(TAG, "Skip adding smartspace target in carousel") - return - } - - val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey() - existingSmartspaceMediaKey?.let { - val removedPlayer = - removePlayer(existingSmartspaceMediaKey, dismissMediaData = false) - removedPlayer?.run { - debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey) - onDestroy() - } - } - - val newRecs = mediaControlPanelFactory.get() - newRecs.attachRecommendation( - RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent) - ) - newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions - val lp = - LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) - newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp) - newRecs.bindRecommendation(data) - val curVisibleMediaKey = - MediaPlayerData.visiblePlayerKeys() - .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) - MediaPlayerData.addMediaRecommendation( - key, - data, - newRecs, - shouldPrioritize, - systemClock, - debugLogger, - ) - updateViewControllerToState(newRecs.mediaViewController, noAnimation = true) - reorderAllPlayers(curVisibleMediaKey) - updatePageIndicator() - mediaFrame.requiresRemeasuring = true - // Check postcondition: mediaContent should have the same number of children as there - // are elements in mediaPlayers. - if (MediaPlayerData.players().size != mediaContent.childCount) { - Log.e( - TAG, - "Size of players list and number of views in carousel are out of sync. " + - "Players size is ${MediaPlayerData.players().size}. " + - "View count is ${mediaContent.childCount}.", - ) - } - } - fun removePlayer( key: String, dismissMediaData: Boolean = true, @@ -1057,7 +919,6 @@ constructor( return removed?.apply { mediaCarouselScrollHandler.onPrePlayerRemoved(removed.mediaViewHolder?.player) mediaContent.removeView(removed.mediaViewHolder?.player) - mediaContent.removeView(removed.recommendationViewHolder?.recommendations) removed.onDestroy() mediaCarouselScrollHandler.onPlayersChanged() mediaControlChipInteractor.updateMediaControlChipModelLegacy( @@ -1095,31 +956,18 @@ constructor( val mediaDataList = MediaPlayerData.mediaData() // Do not loop through the original list of media data because the re-addition of media data // is being executed in background thread. - mediaDataList.forEach { (key, data, isSsMediaRec) -> - if (isSsMediaRec) { - val smartspaceMediaData = MediaPlayerData.smartspaceMediaData + mediaDataList.forEach { (key, data, _) -> + val isSsReactivated = MediaPlayerData.isSsReactivated(key) + if (recreateMedia) { removePlayer(key, dismissMediaData = false, dismissRecommendation = false) - smartspaceMediaData?.let { - addSmartspaceMediaRecommendations( - it.targetId, - it, - MediaPlayerData.shouldPrioritizeSs, - ) - } - onUiExecutionEnd.run() - } else { - val isSsReactivated = MediaPlayerData.isSsReactivated(key) - if (recreateMedia) { - removePlayer(key, dismissMediaData = false, dismissRecommendation = false) - } - addOrUpdatePlayer( - key = key, - oldKey = null, - data = data, - isSsReactivated = isSsReactivated, - onUiExecutionEnd = onUiExecutionEnd, - ) } + addOrUpdatePlayer( + key = key, + oldKey = null, + data = data, + isSsReactivated = isSsReactivated, + onUiExecutionEnd = onUiExecutionEnd, + ) } } @@ -1129,12 +977,8 @@ constructor( if (recreateMedia) { mediaContent.removeAllViews() commonViewModels.forEachIndexed { index, viewModel -> - when (viewModel) { - is MediaCommonViewModel.MediaControl -> - controllerById[viewModel.instanceId.toString()]?.onDestroy() - is MediaCommonViewModel.MediaRecommendations -> - controllerById[viewModel.key]?.onDestroy() - } + val mediaControlViewModel = (viewModel as MediaCommonViewModel.MediaControl) + controllerById[mediaControlViewModel.instanceId.toString()]?.onDestroy() onAdded(viewModel, index, configChanged = true) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt index 5d62c022efba..365389107648 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt @@ -64,28 +64,6 @@ constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) { { "removing player $str1, by user $bool1" }, ) - fun logRecommendationLoaded(key: String, isActive: Boolean) = - buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - bool1 = isActive - }, - { "add recommendation $str1, active $bool1" }, - ) - - fun logRecommendationRemoved(key: String, immediately: Boolean) = - buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - bool1 = immediately - }, - { "removing recommendation $str1, immediate=$bool1" }, - ) - fun logCarouselHidden() = buffer.log(TAG, LogLevel.DEBUG, {}, { "hiding carousel" }) fun logCarouselVisible() = buffer.log(TAG, LogLevel.DEBUG, {}, { "showing carousel" }) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index a6bf5f43698b..006eb203a669 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -22,7 +22,6 @@ import static com.android.settingslib.flags.Flags.legacyLeAudioSharing; import static com.android.systemui.Flags.communalHub; import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation; import static com.android.systemui.media.controls.domain.pipeline.MediaActionsKt.getNotificationActions; -import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS; import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA; import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY; import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA; @@ -35,24 +34,17 @@ import android.app.ActivityOptions; import android.app.BroadcastOptions; import android.app.PendingIntent; import android.app.WallpaperColors; -import android.app.smartspace.SmartspaceAction; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BlendMode; import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Animatable; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; @@ -69,14 +61,12 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.util.Pair; -import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.SeekBar; import android.widget.TextView; import androidx.annotation.NonNull; @@ -105,7 +95,6 @@ import com.android.systemui.media.controls.shared.model.MediaAction; import com.android.systemui.media.controls.shared.model.MediaButton; import com.android.systemui.media.controls.shared.model.MediaData; import com.android.systemui.media.controls.shared.model.MediaDeviceData; -import com.android.systemui.media.controls.shared.model.SmartspaceMediaData; import com.android.systemui.media.controls.ui.animation.AnimationBindHandler; import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition; import com.android.systemui.media.controls.ui.animation.MediaColorSchemesKt; @@ -113,7 +102,6 @@ import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler import com.android.systemui.media.controls.ui.binder.SeekBarObserver; import com.android.systemui.media.controls.ui.view.GutsViewHolder; import com.android.systemui.media.controls.ui.view.MediaViewHolder; -import com.android.systemui.media.controls.ui.view.RecommendationViewHolder; import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel; import com.android.systemui.media.controls.util.MediaDataUtils; import com.android.systemui.media.controls.util.MediaUiEventLogger; @@ -143,14 +131,12 @@ import com.android.systemui.util.ColorUtilKt; import com.android.systemui.util.animation.TransitionLayout; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.GlobalSettings; -import com.android.systemui.util.time.SystemClock; import dagger.Lazy; import kotlin.Triple; import kotlin.Unit; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -165,17 +151,6 @@ public class MediaControlPanel { protected static final String TAG = "MediaControlPanel"; private static final float DISABLED_ALPHA = 0.38f; - private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google" - + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity"; - private static final String EXTRAS_SMARTSPACE_INTENT = - "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT"; - private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name"; - private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND"; - - private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f; - private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f; - private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f; - private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS); // Buttons to show in small player when using semantic actions @@ -215,17 +190,14 @@ public class MediaControlPanel { private Context mContext; private MediaViewHolder mMediaViewHolder; - private RecommendationViewHolder mRecommendationViewHolder; private String mKey; private MediaData mMediaData; - private SmartspaceMediaData mRecommendationData; private MediaViewController mMediaViewController; private MediaSession.Token mToken; private MediaController mController; private Lazy<MediaDataManager> mMediaDataManagerLazy; // Uid for the media app. protected int mUid = Process.INVALID_UID; - private int mSmartspaceMediaItemsCount; private MediaCarouselController mMediaCarouselController; private final MediaOutputDialogManager mMediaOutputDialogManager; private final FalsingManager mFalsingManager; @@ -241,7 +213,6 @@ public class MediaControlPanel { private final NotificationLockscreenUserManager mLockscreenUserManager; // Used for logging. - private SystemClock mSystemClock; private MediaUiEventLogger mLogger; private InstanceId mInstanceId; private String mPackageName; @@ -310,7 +281,6 @@ public class MediaControlPanel { MediaOutputDialogManager mediaOutputDialogManager, MediaCarouselController mediaCarouselController, FalsingManager falsingManager, - SystemClock systemClock, MediaUiEventLogger logger, KeyguardStateController keyguardStateController, ActivityIntentHelper activityIntentHelper, @@ -330,7 +300,6 @@ public class MediaControlPanel { mMediaOutputDialogManager = mediaOutputDialogManager; mMediaCarouselController = mediaCarouselController; mFalsingManager = falsingManager; - mSystemClock = systemClock; mLogger = logger; mKeyguardStateController = keyguardStateController; mActivityIntentHelper = activityIntentHelper; @@ -373,16 +342,6 @@ public class MediaControlPanel { } /** - * Get the recommendation view holder used to display Smartspace media recs. - * - * @return the recommendation view holder - */ - @Nullable - public RecommendationViewHolder getRecommendationViewHolder() { - return mRecommendationViewHolder; - } - - /** * Get the view controller used to display media controls * * @return the media view controller @@ -465,7 +424,7 @@ public class MediaControlPanel { mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar()); mSeekBarViewModel.setScrubbingChangeListener(mScrubbingChangeListener); mSeekBarViewModel.setEnabledChangeListener(mEnabledChangeListener); - mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER); + mMediaViewController.attach(player); vh.getPlayer().setOnLongClickListener(v -> { if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true; @@ -522,26 +481,6 @@ public class MediaControlPanel { return result; } - /** Attaches the recommendations to the recommendation view holder. */ - public void attachRecommendation(RecommendationViewHolder vh) { - mRecommendationViewHolder = vh; - TransitionLayout recommendations = vh.getRecommendations(); - - mMediaViewController.attach(recommendations, MediaViewController.TYPE.RECOMMENDATION); - mMediaViewController.configurationChangeListener = this::updateRecommendationsVisibility; - - mRecommendationViewHolder.getRecommendations().setOnLongClickListener(v -> { - if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true; - if (!mMediaViewController.isGutsVisible()) { - openGuts(); - return true; - } else { - closeGuts(); - return true; - } - }); - } - /** Bind this player view based on the data given. */ public void bindPlayer(@NonNull MediaData data, String key) { SceneContainerFlag.assertInLegacyMode(); @@ -868,24 +807,6 @@ public class MediaControlPanel { mMediaViewHolder.getPlayer().setContentDescription(contentDescription); } - private void bindRecommendationContentDescription(SmartspaceMediaData data) { - if (mRecommendationViewHolder == null) { - return; - } - - CharSequence contentDescription; - if (mMediaViewController.isGutsVisible()) { - contentDescription = - mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText(); - } else if (data != null) { - contentDescription = mContext.getString(R.string.controls_media_smartspace_rec_header); - } else { - contentDescription = null; - } - - mRecommendationViewHolder.getRecommendations().setContentDescription(contentDescription); - } - private void bindArtworkAndColors(MediaData data, String key, boolean updateBackground) { final int traceCookie = data.hashCode(); final String traceName = "MediaControlPanel#bindArtworkAndColors<" + key + ">"; @@ -993,62 +914,6 @@ public class MediaControlPanel { }); } - private void bindRecommendationArtwork( - SmartspaceAction recommendation, - String packageName, - int itemIndex - ) { - final int traceCookie = recommendation.hashCode(); - final String traceName = - "MediaControlPanel#bindRecommendationArtwork<" + packageName + ">"; - Trace.beginAsyncSection(traceName, traceCookie); - - // Capture width & height from views in foreground for artwork scaling in background - int width = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_rec_album_width); - int height = mContext.getResources().getDimensionPixelSize( - R.dimen.qs_media_rec_album_height_expanded); - - mBackgroundExecutor.execute(() -> { - // Album art - ColorScheme mutableColorScheme = null; - Drawable artwork; - Icon artworkIcon = recommendation.getIcon(); - WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon); - if (wallpaperColors != null) { - mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT); - artwork = addGradientToRecommendationAlbum(artworkIcon, mutableColorScheme, width, - height); - } else { - artwork = new ColorDrawable(Color.TRANSPARENT); - } - - mMainExecutor.execute(() -> { - // Bind the artwork drawable to media cover. - ImageView mediaCover = - mRecommendationViewHolder.getMediaCoverItems().get(itemIndex); - // Rescale media cover - Matrix coverMatrix = new Matrix(mediaCover.getImageMatrix()); - coverMatrix.postScale(REC_MEDIA_COVER_SCALE_FACTOR, REC_MEDIA_COVER_SCALE_FACTOR, - 0.5f * width, 0.5f * height); - mediaCover.setImageMatrix(coverMatrix); - mediaCover.setImageDrawable(artwork); - - // Set up the app icon. - ImageView appIconView = mRecommendationViewHolder.getMediaAppIcons().get(itemIndex); - appIconView.clearColorFilter(); - try { - Drawable icon = mContext.getPackageManager() - .getApplicationIcon(packageName); - appIconView.setImageDrawable(icon); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Cannot find icon for package " + packageName, e); - appIconView.setImageResource(R.drawable.ic_music_note); - } - Trace.endAsyncSection(traceName, traceCookie); - }); - }); - } - // This method should be called from a background thread. WallpaperColors.fromBitmap takes a // good amount of time. We do that work on the background executor to avoid stalling animations // on the UI Thread. @@ -1088,21 +953,6 @@ public class MediaControlPanel { MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY, MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY); } - @VisibleForTesting - protected LayerDrawable addGradientToRecommendationAlbum(Icon artworkIcon, - ColorScheme mutableColorScheme, int width, int height) { - // First try scaling rec card using bitmap drawable. - // If returns null, set drawable bounds. - Drawable albumArt = getScaledRecommendationCover(artworkIcon, width, height); - if (albumArt == null) { - albumArt = getScaledBackground(artworkIcon, width, height); - } - GradientDrawable gradient = (GradientDrawable) mContext.getDrawable( - R.drawable.qs_media_rec_scrim).mutate(); - return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme, - MEDIA_REC_SCRIM_START_ALPHA, MEDIA_REC_SCRIM_END_ALPHA); - } - private LayerDrawable setupGradientColorOnDrawable(Drawable albumArt, GradientDrawable gradient, ColorScheme mutableColorScheme, float startAlpha, float endAlpha) { int startColor; @@ -1465,258 +1315,6 @@ public class MediaControlPanel { return controller; } - /** Bind this recommendation view based on the given data. */ - public void bindRecommendation(@NonNull SmartspaceMediaData data) { - if (mRecommendationViewHolder == null) { - return; - } - - if (!data.isValid()) { - Log.e(TAG, "Received an invalid recommendation list; returning"); - return; - } - - if (Trace.isEnabled()) { - Trace.traceBegin(Trace.TRACE_TAG_APP, - "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">"); - } - - mRecommendationData = data; - mPackageName = data.getPackageName(); - mInstanceId = data.getInstanceId(); - - // Set up recommendation card's header. - ApplicationInfo applicationInfo; - try { - applicationInfo = mContext.getPackageManager() - .getApplicationInfo(data.getPackageName(), 0 /* flags */); - mUid = applicationInfo.uid; - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Fail to get media recommendation's app info", e); - Trace.endSection(); - return; - } - - CharSequence appName = data.getAppName(mContext); - if (appName == null) { - Log.w(TAG, "Fail to get media recommendation's app name"); - Trace.endSection(); - return; - } - - PackageManager packageManager = mContext.getPackageManager(); - // Set up media source app's logo. - Drawable icon = packageManager.getApplicationIcon(applicationInfo); - fetchAndUpdateRecommendationColors(icon); - - // Set up media rec card's tap action if applicable. - TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations(); - setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(), - /* interactedSubcardRank */ -1); - bindRecommendationContentDescription(data); - - List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems(); - List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers(); - List<SmartspaceAction> recommendations = data.getValidRecommendations(); - - boolean hasTitle = false; - boolean hasSubtitle = false; - int fittedRecsNum = getNumberOfFittedRecommendations(); - for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) { - SmartspaceAction recommendation = recommendations.get(itemIndex); - - // Set up media item cover. - ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex); - bindRecommendationArtwork(recommendation, data.getPackageName(), itemIndex); - - // Set up the media item's click listener if applicable. - ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex); - setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, itemIndex); - // Bubble up the long-click event to the card. - mediaCoverContainer.setOnLongClickListener(v -> { - if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true; - View parent = (View) v.getParent(); - if (parent != null) { - parent.performLongClick(); - } - return true; - }); - - // Set up the accessibility label for the media item. - String artistName = recommendation.getExtras() - .getString(KEY_SMARTSPACE_ARTIST_NAME, ""); - if (artistName.isEmpty()) { - mediaCoverImageView.setContentDescription( - mContext.getString( - R.string.controls_media_smartspace_rec_item_no_artist_description, - recommendation.getTitle(), appName)); - } else { - mediaCoverImageView.setContentDescription( - mContext.getString( - R.string.controls_media_smartspace_rec_item_description, - recommendation.getTitle(), artistName, appName)); - } - - // Set up title - CharSequence title = recommendation.getTitle(); - hasTitle |= !TextUtils.isEmpty(title); - TextView titleView = mRecommendationViewHolder.getMediaTitles().get(itemIndex); - titleView.setText(title); - - // Set up subtitle - // It would look awkward to show a subtitle if we don't have a title. - boolean shouldShowSubtitleText = !TextUtils.isEmpty(title); - CharSequence subtitle = shouldShowSubtitleText ? recommendation.getSubtitle() : ""; - hasSubtitle |= !TextUtils.isEmpty(subtitle); - TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); - subtitleView.setText(subtitle); - - // Set up progress bar - SeekBar mediaProgressBar = - mRecommendationViewHolder.getMediaProgressBars().get(itemIndex); - TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); - // show progress bar if the recommended album is played. - Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras()); - if (progress == null || progress <= 0.0) { - mediaProgressBar.setVisibility(View.GONE); - mediaSubtitle.setVisibility(View.VISIBLE); - } else { - mediaProgressBar.setProgress((int) (progress * 100)); - mediaProgressBar.setVisibility(View.VISIBLE); - mediaSubtitle.setVisibility(View.GONE); - } - } - mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS; - - // If there's no subtitles and/or titles for any of the albums, hide those views. - ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); - ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); - final boolean titlesVisible = hasTitle; - final boolean subtitlesVisible = hasSubtitle; - mRecommendationViewHolder.getMediaTitles().forEach((titleView) -> { - setVisibleAndAlpha(expandedSet, titleView.getId(), titlesVisible); - setVisibleAndAlpha(collapsedSet, titleView.getId(), titlesVisible); - }); - mRecommendationViewHolder.getMediaSubtitles().forEach((subtitleView) -> { - setVisibleAndAlpha(expandedSet, subtitleView.getId(), subtitlesVisible); - setVisibleAndAlpha(collapsedSet, subtitleView.getId(), subtitlesVisible); - }); - - // Media covers visibility. - setMediaCoversVisibility(fittedRecsNum); - - // Guts - Runnable onDismissClickedRunnable = () -> { - closeGuts(); - mMediaDataManagerLazy.get().dismissSmartspaceRecommendation( - data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L); - - Intent dismissIntent = data.getDismissIntent(); - if (dismissIntent == null) { - Log.w(TAG, "Cannot create dismiss action click action: " - + "extras missing dismiss_intent."); - return; - } - - if (dismissIntent.getComponent() != null - && dismissIntent.getComponent().getClassName() - .equals(EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)) { - // Dismiss the card Smartspace data through Smartspace trampoline activity. - mContext.startActivity(dismissIntent); - } else { - mBroadcastSender.sendBroadcast(dismissIntent); - } - }; - bindGutsMenuCommon( - /* isDismissible= */ true, - appName.toString(), - mRecommendationViewHolder.getGutsViewHolder(), - onDismissClickedRunnable); - - mController = null; - if (mMetadataAnimationHandler == null || !mMetadataAnimationHandler.isRunning()) { - mMediaViewController.refreshState(); - } - Trace.endSection(); - } - - private Unit updateRecommendationsVisibility() { - int fittedRecsNum = getNumberOfFittedRecommendations(); - setMediaCoversVisibility(fittedRecsNum); - return Unit.INSTANCE; - } - - private void setMediaCoversVisibility(int fittedRecsNum) { - ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); - ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); - List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers(); - // Hide media cover that cannot fit in the recommendation card. - for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) { - setVisibleAndAlpha(expandedSet, mediaCoverContainers.get(itemIndex).getId(), - itemIndex < fittedRecsNum); - setVisibleAndAlpha(collapsedSet, mediaCoverContainers.get(itemIndex).getId(), - itemIndex < fittedRecsNum); - } - } - - @VisibleForTesting - protected int getNumberOfFittedRecommendations() { - Resources res = mContext.getResources(); - Configuration config = res.getConfiguration(); - int defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp); - int recCoverWidth = res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) - + res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2; - - // On landscape, media controls should take half of the screen width. - int displayAvailableDpWidth = config.screenWidthDp; - if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { - displayAvailableDpWidth = displayAvailableDpWidth / 2; - } - int fittedNum; - if (displayAvailableDpWidth > defaultDpWidth) { - int recCoverDefaultWidth = res.getDimensionPixelSize( - R.dimen.qs_media_rec_default_width); - fittedNum = recCoverDefaultWidth / recCoverWidth; - } else { - int displayAvailableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - displayAvailableDpWidth, res.getDisplayMetrics()); - fittedNum = displayAvailableWidth / recCoverWidth; - } - return Math.min(fittedNum, NUM_REQUIRED_RECOMMENDATIONS); - } - - private void fetchAndUpdateRecommendationColors(Drawable appIcon) { - mBackgroundExecutor.execute(() -> { - ColorScheme colorScheme = new ColorScheme( - WallpaperColors.fromDrawable(appIcon), /* darkTheme= */ true); - mMainExecutor.execute(() -> setRecommendationColors(colorScheme)); - }); - } - - private void setRecommendationColors(ColorScheme colorScheme) { - if (mRecommendationViewHolder == null) { - return; - } - - int backgroundColor = MediaColorSchemesKt.surfaceFromScheme(colorScheme); - int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme); - int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme); - - mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor); - - mRecommendationViewHolder.getRecommendations() - .setBackgroundTintList(ColorStateList.valueOf(backgroundColor)); - mRecommendationViewHolder.getMediaTitles().forEach( - (title) -> title.setTextColor(textPrimaryColor)); - mRecommendationViewHolder.getMediaSubtitles().forEach( - (subtitle) -> subtitle.setTextColor(textSecondaryColor)); - mRecommendationViewHolder.getMediaProgressBars().forEach( - (progressBar) -> progressBar.setProgressTintList( - ColorStateList.valueOf(textPrimaryColor))); - - mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme); - } - private void bindGutsMenuCommon( boolean isDismissible, String appName, @@ -1772,14 +1370,10 @@ public class MediaControlPanel { public void closeGuts(boolean immediate) { if (mMediaViewHolder != null) { mMediaViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION); - } else if (mRecommendationViewHolder != null) { - mRecommendationViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION); } mMediaViewController.closeGuts(immediate); if (mMediaViewHolder != null) { bindPlayerContentDescription(mMediaData); - } else if (mRecommendationViewHolder != null) { - bindRecommendationContentDescription(mRecommendationData); } } @@ -1790,14 +1384,10 @@ public class MediaControlPanel { private void openGuts() { if (mMediaViewHolder != null) { mMediaViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION); - } else if (mRecommendationViewHolder != null) { - mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION); } mMediaViewController.openGuts(); if (mMediaViewHolder != null) { bindPlayerContentDescription(mMediaData); - } else if (mRecommendationViewHolder != null) { - bindRecommendationContentDescription(mRecommendationData); } mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId); } @@ -1822,29 +1412,6 @@ public class MediaControlPanel { } /** - * Scale artwork to fill the background of media covers in recommendation card. - */ - @UiThread - private Drawable getScaledRecommendationCover(Icon artworkIcon, int width, int height) { - if (width == 0 || height == 0) { - return null; - } - if (artworkIcon != null) { - Bitmap bitmap; - if (artworkIcon.getType() == Icon.TYPE_BITMAP - || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) { - Bitmap artworkBitmap = artworkIcon.getBitmap(); - if (artworkBitmap != null) { - bitmap = Bitmap.createScaledBitmap(artworkIcon.getBitmap(), width, - height, false); - return new BitmapDrawable(mContext.getResources(), bitmap); - } - } - } - return null; - } - - /** * Get the current media controller * * @return the controller @@ -1896,64 +1463,5 @@ public class MediaControlPanel { set.setVisibility(actionId, visible ? ConstraintSet.VISIBLE : notVisibleValue); set.setAlpha(actionId, visible ? 1.0f : 0.0f); } - - private void setSmartspaceRecItemOnClickListener( - @NonNull View view, - @NonNull SmartspaceAction action, - int interactedSubcardRank) { - if (view == null || action == null || action.getIntent() == null - || action.getIntent().getExtras() == null) { - Log.e(TAG, "No tap action can be set up"); - return; - } - - view.setOnClickListener(v -> { - if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return; - - if (interactedSubcardRank == -1) { - mLogger.logRecommendationCardTap(mPackageName, mInstanceId); - } else { - mLogger.logRecommendationItemTap(mPackageName, mInstanceId, interactedSubcardRank); - } - - if (shouldSmartspaceRecItemOpenInForeground(action)) { - // Request to unlock the device if the activity needs to be opened in foreground. - mActivityStarter.postStartActivityDismissingKeyguard( - action.getIntent(), - 0 /* delay */, - buildLaunchAnimatorController( - mRecommendationViewHolder.getRecommendations())); - } else { - // Otherwise, open the activity in background directly. - view.getContext().startActivity(action.getIntent()); - } - - // Automatically scroll to the active player once the media is loaded. - mMediaCarouselController.setShouldScrollToKey(true); - }); - } - - /** Returns if the Smartspace action will open the activity in foreground. */ - private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) { - if (action == null || action.getIntent() == null - || action.getIntent().getExtras() == null) { - return false; - } - - String intentString = action.getIntent().getExtras().getString(EXTRAS_SMARTSPACE_INTENT); - if (intentString == null) { - return false; - } - - try { - Intent wrapperIntent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME); - return wrapperIntent.getBooleanExtra(KEY_SMARTSPACE_OPEN_IN_FOREGROUND, false); - } catch (URISyntaxException e) { - Log.wtf(TAG, "Failed to create intent from URI: " + intentString); - e.printStackTrace(); - } - - return false; - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index b687dce20b06..dba190022c8b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -38,7 +38,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder -import com.android.systemui.media.controls.ui.binder.MediaRecommendationsViewBinder import com.android.systemui.media.controls.ui.binder.SeekBarObserver import com.android.systemui.media.controls.ui.controller.MediaCarouselController.Companion.calculateAlpha import com.android.systemui.media.controls.ui.view.GutsViewHolder @@ -48,7 +47,6 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.hea import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.labelLargeTF import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.labelMediumTF import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.titleMediumTF -import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel import com.android.systemui.res.R @@ -90,15 +88,6 @@ constructor( private val globalSettings: GlobalSettings, ) { - /** - * Indicating that the media view controller is for a notification-based player, session-based - * player, or recommendation - */ - enum class TYPE { - PLAYER, - RECOMMENDATION, - } - companion object { @JvmField val GUTS_ANIMATION_DURATION = 234L } @@ -115,7 +104,6 @@ constructor( private var animationDuration: Long = 0 private var animateNextStateChange: Boolean = false private val measurement = MeasurementOutput(0, 0) - private var type: TYPE = TYPE.PLAYER /** A map containing all viewStates for all locations of this mediaState */ private val viewStates: MutableMap<CacheKey, TransitionViewState?> = mutableMapOf() @@ -203,7 +191,6 @@ constructor( private var isNextButtonAvailable = false /** View holders for controller */ - var recommendationViewHolder: RecommendationViewHolder? = null var mediaViewHolder: MediaViewHolder? = null private lateinit var seekBarObserver: SeekBarObserver @@ -417,13 +404,9 @@ constructor( /** Set the height of UMO background constraints. */ private fun setBackgroundHeights(height: Int) { - val backgroundIds = - if (type == TYPE.PLAYER) { - MediaViewHolder.backgroundIds - } else { - setOf(RecommendationViewHolder.backgroundId) - } - backgroundIds.forEach { id -> expandedLayout.getConstraint(id).layout.mHeight = height } + MediaViewHolder.backgroundIds.forEach { id -> + expandedLayout.getConstraint(id).layout.mHeight = height + } } /** @@ -431,11 +414,7 @@ constructor( * [TransitionViewState]. */ private fun setGutsViewState(viewState: TransitionViewState) { - val controlsIds = - when (type) { - TYPE.PLAYER -> MediaViewHolder.controlsIds - TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds - } + val controlsIds = MediaViewHolder.controlsIds val gutsIds = GutsViewHolder.ids controlsIds.forEach { id -> viewState.widgetStates.get(id)?.let { state -> @@ -467,7 +446,6 @@ constructor( squishedViewState.widgetStates.get(id)?.let { state -> state.height = squishedHeight } } - // media player calculateWidgetGroupAlphaForSquishiness( MediaViewHolder.expandedBottomActionIds, squishedViewState.measureHeight.toFloat(), @@ -480,20 +458,6 @@ constructor( squishedViewState, squishFraction, ) - // recommendation card - val titlesTop = - calculateWidgetGroupAlphaForSquishiness( - RecommendationViewHolder.mediaTitlesAndSubtitlesIds, - squishedViewState.measureHeight.toFloat(), - squishedViewState, - squishFraction, - ) - calculateWidgetGroupAlphaForSquishiness( - RecommendationViewHolder.mediaContainersIds, - titlesTop, - squishedViewState, - squishFraction, - ) return squishedViewState } @@ -661,10 +625,10 @@ constructor( * Attach a view to this controller. This may perform measurements if it's not available yet and * should therefore be done carefully. */ - fun attach(transitionLayout: TransitionLayout, type: TYPE) = + fun attach(transitionLayout: TransitionLayout) = traceSection("MediaViewController#attach") { - loadLayoutForType(type) - logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation) + loadLayoutConstraints() + logger.logMediaLocation("attach", currentStartLocation, currentEndLocation) this.transitionLayout = transitionLayout layoutController.attach(transitionLayout) if (currentEndLocation == MediaHierarchyManager.LOCATION_UNKNOWN) { @@ -691,7 +655,7 @@ constructor( seekBarViewModel.setEnabledChangeListener(enabledChangeListener) val mediaCard = mediaViewHolder.player - attach(mediaViewHolder.player, TYPE.PLAYER) + attach(mediaViewHolder.player) val turbulenceNoiseView = mediaViewHolder.turbulenceNoiseView turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView) @@ -813,15 +777,6 @@ constructor( } } - fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) { - if (!SceneContainerFlag.isEnabled) return - this.recommendationViewHolder = recommendationViewHolder - - attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION) - recsConfigurationChangeListener = - MediaRecommendationsViewBinder::updateRecommendationsVisibility - } - fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) { if (!SceneContainerFlag.isEnabled) return seekBarViewModel.logSeek = onSeek @@ -1026,20 +981,10 @@ constructor( return result } - private fun loadLayoutForType(type: TYPE) { - this.type = type - - // These XML resources contain ConstraintSets that will apply to this player type's layout - when (type) { - TYPE.PLAYER -> { - collapsedLayout.load(context, R.xml.media_session_collapsed) - expandedLayout.load(context, R.xml.media_session_expanded) - } - TYPE.RECOMMENDATION -> { - collapsedLayout.load(context, R.xml.media_recommendations_collapsed) - expandedLayout.load(context, R.xml.media_recommendations_expanded) - } - } + private fun loadLayoutConstraints() { + // These XML resources contain ConstraintSets that will apply to this player's layout + collapsedLayout.load(context, R.xml.media_session_collapsed) + expandedLayout.load(context, R.xml.media_session_expanded) readjustUIUpdateConstraints() refreshState() } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt index f28edd638b10..2fc44ad3cce6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt @@ -42,8 +42,7 @@ class MediaViewModelCallback( ) { oldItem.instanceId == newItem.instanceId } else { - oldItem is MediaCommonViewModel.MediaRecommendations && - newItem is MediaCommonViewModel.MediaRecommendations + false } } @@ -56,11 +55,6 @@ class MediaViewModelCallback( ) { oldItem.immediatelyUpdateUi == newItem.immediatelyUpdateUi && oldItem.updateTime == newItem.updateTime - } else if ( - oldItem is MediaCommonViewModel.MediaRecommendations && - newItem is MediaCommonViewModel.MediaRecommendations - ) { - oldItem.key == newItem.key && oldItem.loadingEnabled == newItem.loadingEnabled } else { false } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt deleted file mode 100644 index 2d028d0213ff..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.ui.view - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.SeekBar -import android.widget.TextView -import com.android.internal.widget.CachingIconView -import com.android.systemui.media.controls.ui.drawable.IlluminationDrawable -import com.android.systemui.res.R -import com.android.systemui.util.animation.TransitionLayout - -private const val TAG = "RecommendationViewHolder" - -/** ViewHolder for a Smartspace media recommendation. */ -class RecommendationViewHolder private constructor(itemView: View) { - - val recommendations = itemView as TransitionLayout - - // Recommendation screen - val cardTitle: TextView = itemView.requireViewById(R.id.media_rec_title) - - val mediaCoverContainers = - listOf<ViewGroup>( - itemView.requireViewById(R.id.media_cover1_container), - itemView.requireViewById(R.id.media_cover2_container), - itemView.requireViewById(R.id.media_cover3_container) - ) - val mediaAppIcons: List<CachingIconView> = - mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) } - val mediaTitles: List<TextView> = - mediaCoverContainers.map { it.requireViewById(R.id.media_title) } - val mediaSubtitles: List<TextView> = - mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) } - val mediaProgressBars: List<SeekBar> = - mediaCoverContainers.map { - it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply { - // Media playback is in the direction of tape, not time, so it stays LTR - layoutDirection = View.LAYOUT_DIRECTION_LTR - } - } - - val mediaCoverItems: List<ImageView> = - mediaCoverContainers.map { it.requireViewById(R.id.media_cover) } - val gutsViewHolder = GutsViewHolder(itemView) - - init { - (recommendations.background as IlluminationDrawable).let { background -> - mediaCoverContainers.forEach { background.registerLightSource(it) } - background.registerLightSource(gutsViewHolder.cancel) - background.registerLightSource(gutsViewHolder.dismiss) - background.registerLightSource(gutsViewHolder.settings) - } - } - - fun marquee(start: Boolean, delay: Long) { - gutsViewHolder.marquee(start, delay, TAG) - } - - companion object { - /** - * Creates a RecommendationViewHolder. - * - * @param inflater LayoutInflater to use to inflate the layout. - * @param parent Parent of inflated view. - */ - @JvmStatic - fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder { - val itemView = - inflater.inflate(R.layout.media_recommendations, parent, false /* attachToRoot */) - // Because this media view (a TransitionLayout) is used to measure and layout the views - // in various states before being attached to its parent, we can't depend on the default - // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction. - itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE - return RecommendationViewHolder(itemView) - } - - // Res Ids for the control components on the recommendation view. - val controlsIds = - setOf( - R.id.media_rec_title, - R.id.media_cover, - R.id.media_cover1_container, - R.id.media_cover2_container, - R.id.media_cover3_container, - R.id.media_title, - R.id.media_subtitle, - ) - - val mediaTitlesAndSubtitlesIds = - setOf( - R.id.media_title, - R.id.media_subtitle, - ) - - val mediaContainersIds = - setOf( - R.id.media_cover1_container, - R.id.media_cover2_container, - R.id.media_cover3_container - ) - - val backgroundId = R.id.sizing_view - } -} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt index e5f1766fbb28..dfaee4434bcf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt @@ -49,7 +49,6 @@ constructor( private val visualStabilityProvider: VisualStabilityProvider, private val interactor: MediaCarouselInteractor, private val controlInteractorFactory: MediaControlInteractorFactory, - private val recommendationsViewModel: MediaRecommendationsViewModel, private val logger: MediaUiEventLogger, private val mediaLogger: MediaLogger, ) { @@ -69,7 +68,7 @@ constructor( when (commonModel) { is MediaCommonModel.MediaControl -> add(toViewModel(commonModel)) is MediaCommonModel.MediaRecommendations -> - add(toViewModel(commonModel)) + return@forEach // TODO(b/382680767): remove } } } @@ -95,8 +94,6 @@ constructor( private val mediaControlByInstanceId = mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>() - private var mediaRecs: MediaCommonViewModel.MediaRecommendations? = null - private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf() private var allowReorder = false @@ -149,37 +146,6 @@ constructor( ) } - private fun toViewModel( - commonModel: MediaCommonModel.MediaRecommendations - ): MediaCommonViewModel.MediaRecommendations { - return mediaRecs?.copy( - key = commonModel.recsLoadingModel.key, - loadingEnabled = interactor.isRecommendationActive(), - ) - ?: MediaCommonViewModel.MediaRecommendations( - key = commonModel.recsLoadingModel.key, - loadingEnabled = interactor.isRecommendationActive(), - recsViewModel = recommendationsViewModel, - onAdded = { commonViewModel -> - mediaLogger.logMediaRecommendationCardAdded( - commonModel.recsLoadingModel.key - ) - onMediaRecommendationAddedOrUpdated( - commonViewModel as MediaCommonViewModel.MediaRecommendations - ) - }, - onRemoved = { immediatelyRemove -> - onMediaRecommendationRemoved(commonModel, immediatelyRemove) - }, - onUpdated = { commonViewModel -> - onMediaRecommendationAddedOrUpdated( - commonViewModel as MediaCommonViewModel.MediaRecommendations - ) - }, - ) - .also { mediaRecs = it } - } - private fun onMediaControlAddedOrUpdated( commonViewModel: MediaCommonViewModel, commonModel: MediaCommonModel.MediaControl, @@ -197,32 +163,6 @@ constructor( } } - private fun onMediaRecommendationAddedOrUpdated( - commonViewModel: MediaCommonViewModel.MediaRecommendations - ) { - if (!interactor.isRecommendationActive()) { - commonViewModel.onRemoved(true) - } - } - - private fun onMediaRecommendationRemoved( - commonModel: MediaCommonModel.MediaRecommendations, - immediatelyRemove: Boolean, - ) { - mediaLogger.logMediaRecommendationCardRemoved(commonModel.recsLoadingModel.key) - if (immediatelyRemove || isReorderingAllowed()) { - interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L) - mediaRecs = null - if (!immediatelyRemove) { - // Although it wasn't requested, we were able to process the removal - // immediately since reordering is allowed. So, notify hosts to update - updateHostVisibility() - } - } else { - modelsPendingRemoval.add(commonModel) - } - } - private fun isReorderingAllowed(): Boolean { return visualStabilityProvider.isReorderingAllowed } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt index 52cb173b39cb..d493d57051f7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt @@ -35,13 +35,4 @@ sealed class MediaCommonViewModel { val isMediaFromRec: Boolean = false, val updateTime: Long = 0, ) : MediaCommonViewModel() - - data class MediaRecommendations( - val key: String, - val loadingEnabled: Boolean, - val recsViewModel: MediaRecommendationsViewModel, - override val onAdded: (MediaCommonViewModel) -> Unit, - override val onRemoved: (Boolean) -> Unit, - override val onUpdated: (MediaCommonViewModel) -> Unit, - ) : MediaCommonViewModel() } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt deleted file mode 100644 index 77add4035067..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.ui.viewmodel - -import android.graphics.drawable.Drawable -import android.graphics.drawable.Icon -import com.android.systemui.animation.Expandable - -/** Models UI state for media recommendation item */ -data class MediaRecViewModel( - val contentDescription: CharSequence, - val title: CharSequence = "", - val subtitle: CharSequence = "", - /** track progress [0 - 100] for the recommendation album. */ - val progress: Int = 0, - val albumIcon: Icon? = null, - val appIcon: Drawable, - val onClicked: ((Expandable, Int) -> Unit), -) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt deleted file mode 100644 index 90313ddc736e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.ui.viewmodel - -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable -import android.os.Process -import android.util.Log -import com.android.internal.logging.InstanceId -import com.android.systemui.animation.Expandable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor -import com.android.systemui.media.controls.shared.model.MediaRecModel -import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel -import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager -import com.android.systemui.media.controls.ui.controller.MediaLocation -import com.android.systemui.media.controls.ui.controller.MediaViewController.Companion.GUTS_ANIMATION_DURATION -import com.android.systemui.media.controls.util.MediaDataUtils -import com.android.systemui.media.controls.util.MediaUiEventLogger -import com.android.systemui.res.R -import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map - -/** Models UI state and handles user input for media recommendations */ -@SysUISingleton -class MediaRecommendationsViewModel -@Inject -constructor( - @Application private val applicationContext: Context, - @Background private val backgroundDispatcher: CoroutineDispatcher, - private val interactor: MediaRecommendationsInteractor, - private val logger: MediaUiEventLogger, -) { - - val mediaRecsCard: Flow<MediaRecsCardViewModel?> = - interactor.recommendations - .map { recsCard -> toRecsViewModel(recsCard) } - .distinctUntilChanged() - .flowOn(backgroundDispatcher) - - @MediaLocation private var location = MediaHierarchyManager.LOCATION_UNKNOWN - - /** - * Called whenever the recommendation has been expired or removed by the user. This method - * removes the recommendation card entirely from the carousel. - */ - private fun onMediaRecommendationsDismissed( - key: String, - uid: Int, - packageName: String, - dismissIntent: Intent?, - instanceId: InstanceId?, - ) { - logger.logLongPressDismiss(uid, packageName, instanceId) - interactor.removeMediaRecommendations(key, dismissIntent, GUTS_DISMISS_DELAY_MS_DURATION) - } - - private fun onClicked( - expandable: Expandable, - intent: Intent?, - packageName: String, - instanceId: InstanceId?, - index: Int, - ) { - if (intent == null || intent.extras == null) { - Log.e(TAG, "No tap action can be set up") - return - } - - if (index == -1) { - logger.logRecommendationCardTap(packageName, instanceId) - } else { - logger.logRecommendationItemTap(packageName, instanceId, index) - } - - // set the package name of the player added by recommendation once the media is loaded. - interactor.switchToMediaControl(packageName) - - interactor.startClickIntent(expandable, intent) - } - - private suspend fun toRecsViewModel(model: MediaRecommendationsModel): MediaRecsCardViewModel? { - if (!model.areRecommendationsValid) { - Log.e(TAG, "Received an invalid recommendation list") - return null - } - if (model.appName == null || model.uid == Process.INVALID_UID) { - Log.w(TAG, "Fail to get media recommendation's app info") - return null - } - - val appIcon = getIconFromApp(model.packageName) ?: return null - - var areTitlesVisible = false - var areSubtitlesVisible = false - val mediaRecs = - model.mediaRecs.map { mediaRecModel -> - areTitlesVisible = areTitlesVisible || !mediaRecModel.title.isNullOrEmpty() - areSubtitlesVisible = areSubtitlesVisible || !mediaRecModel.subtitle.isNullOrEmpty() - val progress = MediaDataUtils.getDescriptionProgress(mediaRecModel.extras) ?: 0.0 - MediaRecViewModel( - contentDescription = - setUpMediaRecContentDescription(mediaRecModel, model.appName), - title = mediaRecModel.title ?: "", - subtitle = mediaRecModel.subtitle ?: "", - progress = (progress * 100).toInt(), - albumIcon = mediaRecModel.icon, - appIcon = appIcon, - onClicked = { expandable, index -> - onClicked( - expandable, - mediaRecModel.intent, - model.packageName, - model.instanceId, - index, - ) - }, - ) - } - // Subtitles should only be visible if titles are visible. - areSubtitlesVisible = areTitlesVisible && areSubtitlesVisible - - return MediaRecsCardViewModel( - contentDescription = { gutsVisible -> - if (gutsVisible) { - applicationContext.getString( - R.string.controls_media_close_session, - model.appName, - ) - } else { - applicationContext.getString(R.string.controls_media_smartspace_rec_header) - } - }, - onClicked = { expandable -> - onClicked( - expandable, - model.dismissIntent, - model.packageName, - model.instanceId, - index = -1, - ) - }, - onLongClicked = { - logger.logLongPressOpen(model.uid, model.packageName, model.instanceId) - }, - mediaRecs = mediaRecs, - areTitlesVisible = areTitlesVisible, - areSubtitlesVisible = areSubtitlesVisible, - gutsMenu = toGutsViewModel(model), - onLocationChanged = { location = it }, - ) - } - - private fun toGutsViewModel(model: MediaRecommendationsModel): GutsViewModel { - return GutsViewModel( - gutsText = - applicationContext.getString(R.string.controls_media_close_session, model.appName), - onDismissClicked = { - onMediaRecommendationsDismissed( - model.key, - model.uid, - model.packageName, - model.dismissIntent, - model.instanceId, - ) - }, - cancelTextBackground = - applicationContext.getDrawable(R.drawable.qs_media_outline_button), - onSettingsClicked = { - logger.logLongPressSettings(model.uid, model.packageName, model.instanceId) - interactor.startSettings() - }, - ) - } - - private fun setUpMediaRecContentDescription( - mediaRec: MediaRecModel, - appName: CharSequence?, - ): CharSequence { - // Set up the accessibility label for the media item. - val artistName = mediaRec.extras?.getString(KEY_SMARTSPACE_ARTIST_NAME, "") - return if (artistName.isNullOrEmpty()) { - applicationContext.getString( - R.string.controls_media_smartspace_rec_item_no_artist_description, - mediaRec.title, - appName, - ) - } else { - applicationContext.getString( - R.string.controls_media_smartspace_rec_item_description, - mediaRec.title, - artistName, - appName, - ) - } - } - - private fun getIconFromApp(packageName: String): Drawable? { - return try { - applicationContext.packageManager.getApplicationIcon(packageName) - } catch (e: PackageManager.NameNotFoundException) { - Log.w(TAG, "Cannot find icon for package $packageName", e) - null - } - } - - companion object { - private const val TAG = "MediaRecommendationsViewModel" - private const val KEY_SMARTSPACE_ARTIST_NAME = "artist_name" - /** - * Delay duration is based on [GUTS_ANIMATION_DURATION], it should have 100 ms increase in - * order to let the animation end. - */ - private const val GUTS_DISMISS_DELAY_MS_DURATION = 334L - } -} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt deleted file mode 100644 index f1f7dc2195d5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.ui.viewmodel - -import com.android.systemui.animation.Expandable - -/** Models UI state for media recommendations card. */ -data class MediaRecsCardViewModel( - val contentDescription: (Boolean) -> CharSequence, - val onClicked: (Expandable) -> Unit, - val onLongClicked: () -> Unit, - val mediaRecs: List<MediaRecViewModel>, - val areTitlesVisible: Boolean, - val areSubtitlesVisible: Boolean, - val gutsMenu: GutsViewModel, - val onLocationChanged: (Int) -> Unit, -) diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt index f29b15730986..aaed606f8fb2 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt @@ -16,6 +16,8 @@ package com.android.systemui.model +import com.android.systemui.shared.system.QuickStepContract.getSystemUiStateString + /** * Represents a set of state changes. A bit can either be set to `true` or `false`. * @@ -43,13 +45,16 @@ class StateChange { fun hasChanges() = flagsToSet != 0L || flagsToClear != 0L - /** Applies all changed flags to [sysUiState]. */ + /** + * Applies all changed flags to [sysUiState]. + * + * Note this doesn't call [SysUiState.commitUpdate]. + */ fun applyTo(sysUiState: SysUiState) { iterateBits(flagsToSet or flagsToClear) { bit -> val isBitSetInNewState = flagsToSet and bit != 0L sysUiState.setFlag(bit, isBitSetInNewState) } - sysUiState.commitUpdate() } fun applyTo(sysUiState: Long): Long { @@ -69,14 +74,25 @@ class StateChange { } } - /** Clears all the flags changed in a [sysUiState] */ - fun clearAllChangedFlagsIn(sysUiState: SysUiState) { + /** + * Clears all the flags changed in a [sysUiState]. + * + * Note this doesn't call [SysUiState.commitUpdate]. + */ + fun clearFrom(sysUiState: SysUiState) { iterateBits(flagsToSet or flagsToClear) { bit -> sysUiState.setFlag(bit, false) } - sysUiState.commitUpdate() } fun clear() { flagsToSet = 0 flagsToClear = 0 } + + override fun toString(): String { + return """StateChange(flagsToSet=${getSystemUiStateString(flagsToSet)}, flagsToClear=${ + getSystemUiStateString( + flagsToClear + ) + })""" + } } diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt index 53105b2c0f6a..663355941613 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt @@ -74,6 +74,9 @@ interface SysUiState : Dumpable { */ fun destroy() + /** The display ID this instances is associated with */ + val displayId: Int + companion object { const val DEBUG: Boolean = false } @@ -84,7 +87,7 @@ private const val TAG = "SysUIState" class SysUiStateImpl @AssistedInject constructor( - @Assisted private val displayId: Int, + @Assisted override val displayId: Int, private val sceneContainerPlugin: SceneContainerPlugin?, private val dumpManager: DumpManager, private val stateDispatcher: SysUIStateDispatcher, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt index 85658bb71c8b..50012abc69d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt @@ -26,12 +26,14 @@ import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.foundation.Image +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -39,6 +41,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicText import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.key @@ -49,8 +52,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorProducer +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role @@ -60,7 +71,7 @@ import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.semantics.toggleableState -import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.modifiers.size @@ -73,6 +84,9 @@ import com.android.systemui.common.ui.compose.load import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconWidth +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TILE_INITIAL_DELAY_MILLIS +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TILE_MARQUEE_ITERATIONS +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileLabelBlurWidth import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState import com.android.systemui.qs.ui.compose.borderOnFocus @@ -104,30 +118,31 @@ fun LargeTileContent( val focusBorderColor = MaterialTheme.colorScheme.secondary Box( modifier = - Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) { - Modifier.borderOnFocus(color = focusBorderColor, iconShape.topEnd) - .clip(iconShape) - .verticalSquish(squishiness) - .drawBehind { drawRect(animatedBackgroundColor) } - .combinedClickable( - onClick = toggleClick!!, - onLongClick = onLongClick, - onLongClickLabel = longPressLabel, - hapticFeedbackEnabled = !Flags.msdlFeedback(), - ) - .thenIf(accessibilityUiState != null) { - Modifier.semantics { - accessibilityUiState as AccessibilityUiState - contentDescription = accessibilityUiState.contentDescription - stateDescription = accessibilityUiState.stateDescription - accessibilityUiState.toggleableState?.let { - toggleableState = it + Modifier.size(CommonTileDefaults.ToggleTargetSize) + .clip(iconShape) + .verticalSquish(squishiness) + .drawBehind { drawRect(animatedBackgroundColor) } + .thenIf(toggleClick != null) { + Modifier.borderOnFocus(color = focusBorderColor, iconShape.topEnd) + .combinedClickable( + onClick = toggleClick!!, + onLongClick = onLongClick, + onLongClickLabel = longPressLabel, + hapticFeedbackEnabled = !Flags.msdlFeedback(), + ) + .thenIf(accessibilityUiState != null) { + Modifier.semantics { + accessibilityUiState as AccessibilityUiState + contentDescription = accessibilityUiState.contentDescription + stateDescription = accessibilityUiState.stateDescription + accessibilityUiState.toggleableState?.let { + toggleableState = it + } + role = Role.Switch } - role = Role.Switch - } - .sysuiResTag(TEST_TAG_TOGGLE) - } - } + .sysuiResTag(TEST_TAG_TOGGLE) + } + } ) { SmallTileContent( iconProvider = iconProvider, @@ -167,18 +182,15 @@ fun LargeTileLabels( val animatedSecondaryLabelColor by animateColorAsState(colors.secondaryLabel, label = "QSTileSecondaryLabelColor") Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { - BasicText( - label, + TileLabel( + text = label, style = MaterialTheme.typography.labelLarge, color = { animatedLabelColor }, - maxLines = 1, - overflow = TextOverflow.Ellipsis, ) if (!TextUtils.isEmpty(secondaryLabel)) { - BasicText( + TileLabel( secondaryLabel ?: "", color = { animatedSecondaryLabelColor }, - maxLines = 1, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.thenIf( @@ -194,9 +206,9 @@ fun LargeTileLabels( @Composable fun SmallTileContent( - modifier: Modifier = Modifier, iconProvider: Context.() -> Icon, color: Color, + modifier: Modifier = Modifier, size: () -> Dp = { CommonTileDefaults.IconSize }, animateToEnd: Boolean = false, ) { @@ -212,31 +224,39 @@ fun SmallTileContent( } } if (loadedDrawable is Animatable) { + // Skip initial animation, icons should animate only as the state change + // and not when first composed + var shouldSkipInitialAnimation by remember { mutableStateOf(true) } + LaunchedEffect(Unit) { shouldSkipInitialAnimation = animateToEnd } + val painter = when (icon) { is Icon.Resource -> { val image = AnimatedImageVector.animatedVectorResource(id = icon.res) key(icon) { - if (animateToEnd) { - rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true) - } else { - var atEnd by remember(icon.res) { mutableStateOf(false) } - LaunchedEffect(key1 = icon.res) { atEnd = true } - rememberAnimatedVectorPainter( - animatedImageVector = image, - atEnd = atEnd, - ) - } + var atEnd by remember(icon) { mutableStateOf(shouldSkipInitialAnimation) } + LaunchedEffect(key1 = icon.res) { atEnd = true } + + rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) } } is Icon.Loaded -> { - LaunchedEffect(loadedDrawable) { + val painter = rememberDrawablePainter(loadedDrawable) + + // rememberDrawablePainter automatically starts the animation. Using + // SideEffect here to immediately stop it if needed + DisposableEffect(painter) { if (loadedDrawable is AnimatedVectorDrawable) { loadedDrawable.forceAnimationOnUI() } + if (shouldSkipInitialAnimation) { + loadedDrawable.stop() + } + onDispose {} } - rememberDrawablePainter(loadedDrawable) + + painter } } @@ -251,6 +271,45 @@ fun SmallTileContent( } } +@Composable +private fun TileLabel( + text: String, + color: ColorProducer, + style: TextStyle, + modifier: Modifier = Modifier, +) { + BasicText( + text = text, + color = color, + style = style, + maxLines = 1, + modifier = + modifier + .fillMaxWidth() + .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } + .drawWithContent { + drawContent() + // Draw a blur over the end of the text + val edgeWidthPx = TileLabelBlurWidth.toPx() + drawRect( + topLeft = Offset(size.width - edgeWidthPx, 0f), + size = Size(edgeWidthPx, size.height), + brush = + Brush.horizontalGradient( + colors = listOf(Color.Transparent, Color.Black), + startX = size.width, + endX = size.width - edgeWidthPx, + ), + blendMode = BlendMode.DstIn, + ) + } + .basicMarquee( + iterations = TILE_MARQUEE_ITERATIONS, + initialDelayMillis = TILE_INITIAL_DELAY_MILLIS, + ), + ) +} + object CommonTileDefaults { val IconSize = 32.dp val LargeTileIconSize = 28.dp @@ -258,9 +317,13 @@ object CommonTileDefaults { val SideIconHeight = 20.dp val ToggleTargetSize = 56.dp val TileHeight = 72.dp - val TilePadding = 8.dp + val TileStartPadding = 8.dp + val TileEndPadding = 16.dp val TileArrangementPadding = 6.dp val InactiveCornerRadius = 50.dp + val TileLabelBlurWidth = 32.dp + const val TILE_MARQUEE_ITERATIONS = 1 + const val TILE_INITIAL_DELAY_MILLIS = 2000 @Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index 6e6c0b61d85b..69b967a68c3c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -713,7 +713,7 @@ private fun AvailableTileGridCell( // Displays the tile as an icon tile with the label underneath Column( horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top), + verticalArrangement = spacedBy(CommonTileDefaults.TileStartPadding, Alignment.Top), modifier = modifier .graphicsLayer { this.alpha = alpha } @@ -813,7 +813,7 @@ fun EditTile( placeable.place(startPadding.roundToInt(), 0) } } - .tilePadding(), + .largeTilePadding(), ) { // Icon Box(Modifier.size(ToggleTargetSize)) { @@ -847,7 +847,7 @@ private fun toAvailableTiles( private fun MeasureScope.iconHorizontalCenter(containerSize: Int): Float { return (containerSize - ToggleTargetSize.roundToPx()) / 2f - - CommonTileDefaults.TilePadding.toPx() + CommonTileDefaults.TileStartPadding.toPx() } private fun Modifier.tileBackground(color: Color): Modifier { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index d73dc870756b..a56fabcc7dc3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -84,7 +84,9 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.flags.QsDetailedView import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileEndPadding import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileStartPadding import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileUiState @@ -270,7 +272,7 @@ fun TileContainer( iconOnly = iconOnly, ) .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) - .tilePadding(), + .thenIf(!iconOnly) { Modifier.largeTilePadding() }, // Icon tiles are center aligned content = content, ) } @@ -284,7 +286,7 @@ fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) { .clip(TileDefaults.animateTileShapeAsState(state = uiState.state).value) .background(colors.background) .height(TileHeight) - .tilePadding() + .largeTilePadding() ) { LargeTileContent( label = uiState.label, @@ -311,8 +313,8 @@ fun tileHorizontalArrangement(): Arrangement.Horizontal { return spacedBy(space = CommonTileDefaults.TileArrangementPadding, alignment = Alignment.Start) } -fun Modifier.tilePadding(): Modifier { - return padding(CommonTileDefaults.TilePadding) +fun Modifier.largeTilePadding(): Modifier { + return padding(start = TileStartPadding, end = TileEndPadding) } @Composable @@ -356,10 +358,10 @@ private object TileDefaults { val ActiveIconCornerRadius = 16.dp val ActiveTileCornerRadius = 24.dp - /** An active tile without dual target uses the active color as background */ + /** An active icon tile uses the active color as background */ @Composable @ReadOnlyComposable - fun activeTileColors(): TileColors = + fun activeIconTileColors(): TileColors = TileColors( background = MaterialTheme.colorScheme.primary, iconBackground = MaterialTheme.colorScheme.primary, @@ -418,10 +420,10 @@ private object TileDefaults { fun getColorForState(uiState: TileUiState, iconOnly: Boolean): TileColors { return when (uiState.state) { STATE_ACTIVE -> { - if (uiState.handlesSecondaryClick && !iconOnly) { + if (!iconOnly) { activeDualTargetTileColors() } else { - activeTileColors() + activeIconTileColors() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index bfd512fa6a2d..ef0660fbcd1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -566,12 +566,11 @@ public final class KeyboardShortcutListSearch { Arrays.asList( Pair.create(KeyEvent.KEYCODE_TAB, KeyEvent.META_META_ON))), /* Back: go back to previous state (back button) */ - /* Meta + Escape, Meta + backspace, Meta + left arrow */ + /* Meta + Escape, Meta + left arrow */ new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_go_back), Arrays.asList( Pair.create(KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_META_ON), - Pair.create(KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON), Pair.create(KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_META_ON))), /* Take a full screenshot: Meta + S */ new ShortcutKeyGroupMultiMappingInfo( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt index 2001a3ea7ca0..dad08e014c0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt @@ -422,25 +422,6 @@ class MediaCarouselControllerTest(flags: FlagsParameterization) : SysuiTestCase( assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec) } - @DisableSceneContainer - @Test - fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() { - verify(mediaDataManager).addListener(capture(listener)) - - testPlayerOrdering() - - // If smartspace is prioritized - listener.value.onSmartspaceMediaDataLoaded( - SMARTSPACE_KEY, - EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), - true, - ) - - // Then it should be shown immediately after any actively playing controls - assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec) - assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec) - } - @Test fun testOrderWithSmartspace_notPrioritized() { testPlayerOrdering() @@ -571,146 +552,6 @@ class MediaCarouselControllerTest(flags: FlagsParameterization) : SysuiTestCase( verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!)) } - @DisableSceneContainer - @Test - fun testMediaLoaded_ScrollToActivePlayer() { - verify(mediaDataManager).addListener(capture(listener)) - - listener.value.onMediaDataLoaded( - PLAYING_LOCAL, - null, - DATA.copy( - active = true, - isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, - resumption = false, - ), - ) - listener.value.onMediaDataLoaded( - PAUSED_LOCAL, - null, - DATA.copy( - active = true, - isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, - resumption = false, - ), - ) - runAllReady() - // adding a media recommendation card. - listener.value.onSmartspaceMediaDataLoaded( - SMARTSPACE_KEY, - EMPTY_SMARTSPACE_MEDIA_DATA, - false, - ) - mediaCarouselController.shouldScrollToKey = true - // switching between media players. - listener.value.onMediaDataLoaded( - PLAYING_LOCAL, - PLAYING_LOCAL, - DATA.copy( - active = true, - isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, - resumption = true, - ), - ) - listener.value.onMediaDataLoaded( - PAUSED_LOCAL, - PAUSED_LOCAL, - DATA.copy( - active = true, - isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, - resumption = false, - ), - ) - runAllReady() - - assertEquals( - MediaPlayerData.getMediaPlayerIndex(PAUSED_LOCAL), - mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex, - ) - } - - @DisableSceneContainer - @Test - fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() { - verify(mediaDataManager).addListener(capture(listener)) - - listener.value.onSmartspaceMediaDataLoaded( - SMARTSPACE_KEY, - EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true), - false, - ) - listener.value.onMediaDataLoaded( - PLAYING_LOCAL, - null, - DATA.copy( - active = true, - isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, - resumption = false, - ), - ) - runAllReady() - - var playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL) - assertEquals( - playerIndex, - mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex, - ) - assertEquals(playerIndex, 0) - - // Replaying the same media player one more time. - // And check that the card stays in its position. - mediaCarouselController.shouldScrollToKey = true - listener.value.onMediaDataLoaded( - PLAYING_LOCAL, - null, - DATA.copy( - active = true, - isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, - resumption = false, - packageName = "PACKAGE_NAME", - ), - ) - runAllReady() - playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL) - assertEquals(playerIndex, 0) - } - - @DisableSceneContainer - @Test - fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() { - verify(mediaDataManager).addListener(capture(listener)) - - var result = false - mediaCarouselController.updateHostVisibility = { result = true } - - whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true) - listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false) - - assertEquals(true, result) - } - - @DisableSceneContainer - @Test - fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() { - verify(mediaDataManager).addListener(capture(listener)) - - var result = false - mediaCarouselController.updateHostVisibility = { result = true } - - whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false) - listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false) - assertEquals(false, result) - - visualStabilityCallback.value.onReorderingAllowed() - assertEquals(true, result) - } - @Test fun testGetCurrentVisibleMediaContentIntent() { val clickIntent1 = mock(PendingIntent::class.java) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt index 9543032ef5ec..88fcc706f072 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt @@ -19,16 +19,12 @@ package com.android.systemui.media.controls.ui.controller import android.animation.Animator import android.animation.AnimatorSet import android.app.PendingIntent -import android.app.smartspace.SmartspaceAction -import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager -import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color -import android.graphics.Matrix import android.graphics.drawable.Animatable2 import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable @@ -47,7 +43,6 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.provider.Settings import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.testing.TestableLooper -import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.animation.Interpolator @@ -59,7 +54,6 @@ import android.widget.TextView import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData -import androidx.media.utils.MediaConstants import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId @@ -72,19 +66,15 @@ import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.media.controls.MediaTestUtils -import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA import com.android.systemui.media.controls.domain.pipeline.MediaDataManager -import com.android.systemui.media.controls.shared.model.KEY_SMARTSPACE_APP_NAME import com.android.systemui.media.controls.shared.model.MediaAction import com.android.systemui.media.controls.shared.model.MediaButton import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.MediaDeviceData import com.android.systemui.media.controls.shared.model.MediaNotificationAction -import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.media.controls.ui.binder.SeekBarObserver import com.android.systemui.media.controls.ui.view.GutsViewHolder import com.android.systemui.media.controls.ui.view.MediaViewHolder -import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.media.dialog.MediaOutputDialogManager @@ -216,32 +206,9 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var communalSceneInteractor: CommunalSceneInteractor - @Mock private lateinit var recommendationViewHolder: RecommendationViewHolder - @Mock private lateinit var smartspaceAction: SmartspaceAction - private lateinit var smartspaceData: SmartspaceMediaData - @Mock private lateinit var coverContainer1: ViewGroup - @Mock private lateinit var coverContainer2: ViewGroup - @Mock private lateinit var coverContainer3: ViewGroup - @Mock private lateinit var recAppIconItem: CachingIconView - @Mock private lateinit var recCardTitle: TextView - @Mock private lateinit var coverItem: ImageView - @Mock private lateinit var matrix: Matrix - private lateinit var recTitle1: TextView - private lateinit var recTitle2: TextView - private lateinit var recTitle3: TextView - private lateinit var recSubtitle1: TextView - private lateinit var recSubtitle2: TextView - private lateinit var recSubtitle3: TextView - @Mock private lateinit var recProgressBar1: SeekBar - @Mock private lateinit var recProgressBar2: SeekBar - @Mock private lateinit var recProgressBar3: SeekBar @Mock private lateinit var globalSettings: GlobalSettings - private val intent = - Intent().apply { - putExtras(Bundle().also { it.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME) }) - setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } + private val intent = Intent().apply { setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } private val pendingIntent = PendingIntent.getActivity( mContext, @@ -282,7 +249,6 @@ public class MediaControlPanelTest : SysuiTestCase() { mediaOutputDialogManager, mediaCarouselController, falsingManager, - clock, logger, keyguardStateController, activityIntentHelper, @@ -304,27 +270,6 @@ public class MediaControlPanelTest : SysuiTestCase() { initMediaViewHolderMocks() initDeviceMediaData(false, DEVICE_NAME) - - // Set up recommendation view - initRecommendationViewHolderMocks() - - // Set valid recommendation data - val extras = Bundle() - extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME) - val intent = - Intent().apply { - putExtras(extras) - setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - whenever(smartspaceAction.intent).thenReturn(intent) - whenever(smartspaceAction.extras).thenReturn(extras) - smartspaceData = - EMPTY_SMARTSPACE_MEDIA_DATA.copy( - packageName = PACKAGE, - instanceId = instanceId, - recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction), - cardAction = smartspaceAction, - ) } private fun initGutsViewHolderMocks() { @@ -471,49 +416,6 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(viewHolder.loadingEffectView).thenReturn(loadingEffectView) } - /** Initialize elements for the recommendation view holder */ - private fun initRecommendationViewHolderMocks() { - recTitle1 = TextView(context) - recTitle2 = TextView(context) - recTitle3 = TextView(context) - recSubtitle1 = TextView(context) - recSubtitle2 = TextView(context) - recSubtitle3 = TextView(context) - - whenever(recommendationViewHolder.recommendations).thenReturn(view) - whenever(recommendationViewHolder.mediaAppIcons) - .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem)) - whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) - whenever(recommendationViewHolder.mediaCoverItems) - .thenReturn(listOf(coverItem, coverItem, coverItem)) - whenever(recommendationViewHolder.mediaCoverContainers) - .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3)) - whenever(recommendationViewHolder.mediaTitles) - .thenReturn(listOf(recTitle1, recTitle2, recTitle3)) - whenever(recommendationViewHolder.mediaSubtitles) - .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3)) - whenever(recommendationViewHolder.mediaProgressBars) - .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3)) - whenever(coverItem.imageMatrix).thenReturn(matrix) - - // set ids for recommendation containers - whenever(coverContainer1.id).thenReturn(1) - whenever(coverContainer2.id).thenReturn(2) - whenever(coverContainer3.id).thenReturn(3) - - whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder) - - val actionIcon = Icon.createWithResource(context, R.drawable.ic_android) - whenever(smartspaceAction.icon).thenReturn(actionIcon) - - // Needed for card and item action click - val mockContext = mock(Context::class.java) - whenever(view.context).thenReturn(mockContext) - whenever(coverContainer1.context).thenReturn(mockContext) - whenever(coverContainer2.context).thenReturn(mockContext) - whenever(coverContainer3.context).thenReturn(mockContext) - } - @After fun tearDown() { session.release() @@ -1488,169 +1390,6 @@ public class MediaControlPanelTest : SysuiTestCase() { /* ***** END guts tests for the player ***** */ - /* ***** Guts tests for the recommendations ***** */ - - @Test - fun recommendations_longClick_isFalse() { - whenever(falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)).thenReturn(true) - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - - val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) - verify(viewHolder.player).onLongClickListener = captor.capture() - - captor.value.onLongClick(viewHolder.player) - verify(mediaViewController, never()).openGuts() - verify(mediaViewController, never()).closeGuts() - } - - @Test - fun recommendations_longClickWhenGutsClosed_gutsOpens() { - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - whenever(mediaViewController.isGutsVisible).thenReturn(false) - - val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) - verify(viewHolder.player).onLongClickListener = captor.capture() - - captor.value.onLongClick(viewHolder.player) - verify(mediaViewController).openGuts() - verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId)) - } - - @Test - fun recommendations_longClickWhenGutsOpen_gutsCloses() { - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - whenever(mediaViewController.isGutsVisible).thenReturn(true) - - val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) - verify(viewHolder.player).onLongClickListener = captor.capture() - - captor.value.onLongClick(viewHolder.player) - verify(mediaViewController, never()).openGuts() - verify(mediaViewController).closeGuts(false) - } - - @Test - fun recommendations_cancelButtonClick_animation() { - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - - cancel.callOnClick() - - verify(mediaViewController).closeGuts(false) - } - - @Test - fun recommendations_settingsButtonClick() { - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - - settings.callOnClick() - verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId)) - - val captor = ArgumentCaptor.forClass(Intent::class.java) - verify(activityStarter).startActivity(captor.capture(), eq(true)) - - assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS) - } - - @Test - fun recommendations_dismissButtonClick() { - val mediaKey = "key for dismissal" - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData.copy(targetId = mediaKey)) - - assertThat(dismiss.isEnabled).isEqualTo(true) - dismiss.callOnClick() - verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId)) - verify(mediaDataManager).dismissSmartspaceRecommendation(eq(mediaKey), anyLong()) - } - - @Test - fun recommendation_gutsOpen_contentDescriptionIsForGuts() { - whenever(mediaViewController.isGutsVisible).thenReturn(true) - player.attachRecommendation(recommendationViewHolder) - - val gutsTextString = "gutsText" - whenever(gutsText.text).thenReturn(gutsTextString) - player.bindRecommendation(smartspaceData) - - val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) - verify(viewHolder.player).contentDescription = descriptionCaptor.capture() - val description = descriptionCaptor.value.toString() - - assertThat(description).isEqualTo(gutsTextString) - } - - @Test - fun recommendation_gutsClosed_contentDescriptionIsForPlayer() { - whenever(mediaViewController.isGutsVisible).thenReturn(false) - player.attachRecommendation(recommendationViewHolder) - - player.bindRecommendation(smartspaceData) - - val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) - verify(viewHolder.player).contentDescription = descriptionCaptor.capture() - val description = descriptionCaptor.value.toString() - - assertThat(description) - .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header)) - } - - @Test - fun recommendation_gutsChangesFromOpenToClosed_contentDescriptionUpdated() { - // Start out open - whenever(mediaViewController.isGutsVisible).thenReturn(true) - whenever(gutsText.text).thenReturn("gutsText") - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - - // Update to closed by long pressing - val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) - verify(viewHolder.player).onLongClickListener = captor.capture() - reset(viewHolder.player) - - whenever(mediaViewController.isGutsVisible).thenReturn(false) - captor.value.onLongClick(viewHolder.player) - - // Then content description is now the player content description - val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) - verify(viewHolder.player).contentDescription = descriptionCaptor.capture() - val description = descriptionCaptor.value.toString() - - assertThat(description) - .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header)) - } - - @Test - fun recommendation_gutsChangesFromClosedToOpen_contentDescriptionUpdated() { - // Start out closed - whenever(mediaViewController.isGutsVisible).thenReturn(false) - val gutsTextString = "gutsText" - whenever(gutsText.text).thenReturn(gutsTextString) - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - - // Update to open by long pressing - val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) - verify(viewHolder.player).onLongClickListener = captor.capture() - reset(viewHolder.player) - - whenever(mediaViewController.isGutsVisible).thenReturn(true) - captor.value.onLongClick(viewHolder.player) - - // Then content description is now the guts content description - val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java) - verify(viewHolder.player).contentDescription = descriptionCaptor.capture() - val description = descriptionCaptor.value.toString() - - assertThat(description).isEqualTo(gutsTextString) - } - - /* ***** END guts tests for the recommendations ***** */ - @Test fun actionPlayPauseClick_isLogged() { val semanticActions = @@ -1887,578 +1626,6 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test - fun recommendation_gutsClosed_longPressOpens() { - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - whenever(mediaViewController.isGutsVisible).thenReturn(false) - - val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) - verify(recommendationViewHolder.recommendations).setOnLongClickListener(captor.capture()) - - captor.value.onLongClick(recommendationViewHolder.recommendations) - verify(mediaViewController).openGuts() - verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId)) - } - - @Test - fun recommendation_settingsButtonClick_isLogged() { - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - - settings.callOnClick() - verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId)) - - val captor = ArgumentCaptor.forClass(Intent::class.java) - verify(activityStarter).startActivity(captor.capture(), eq(true)) - - assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS) - } - - @Test - fun recommendation_dismissButton_isLogged() { - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - - dismiss.callOnClick() - verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId)) - } - - @Test - fun recommendation_tapOnCard_isLogged() { - val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java) - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - - verify(recommendationViewHolder.recommendations).setOnClickListener(captor.capture()) - captor.value.onClick(recommendationViewHolder.recommendations) - - verify(logger).logRecommendationCardTap(eq(PACKAGE), eq(instanceId)) - } - - @Test - fun recommendation_tapOnItem_isLogged() { - val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java) - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(smartspaceData) - - verify(coverContainer1).setOnClickListener(captor.capture()) - captor.value.onClick(recommendationViewHolder.recommendations) - - verify(logger).logRecommendationItemTap(eq(PACKAGE), eq(instanceId), eq(0)) - } - - @Test - fun bindRecommendation_listHasTooFewRecs_notDisplayed() { - player.attachRecommendation(recommendationViewHolder) - val icon = - Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata) - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("subtitle1") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("subtitle2") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - - player.bindRecommendation(data) - - assertThat(recTitle1.text).isEqualTo("") - verify(mediaViewController, never()).refreshState() - } - - @Test - fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() { - player.attachRecommendation(recommendationViewHolder) - val icon = - Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata) - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("subtitle1") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("subtitle2") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "empty icon 1") - .setSubtitle("subtitle2") - .setIcon(null) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "empty icon 2") - .setSubtitle("subtitle2") - .setIcon(null) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - - player.bindRecommendation(data) - - assertThat(recTitle1.text).isEqualTo("") - verify(mediaViewController, never()).refreshState() - } - - @Test - fun bindRecommendation_hasTitlesAndSubtitles() { - player.attachRecommendation(recommendationViewHolder) - - val title1 = "Title1" - val title2 = "Title2" - val title3 = "Title3" - val subtitle1 = "Subtitle1" - val subtitle2 = "Subtitle2" - val subtitle3 = "Subtitle3" - val icon = - Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata) - - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", title1) - .setSubtitle(subtitle1) - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", title2) - .setSubtitle(subtitle2) - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", title3) - .setSubtitle(subtitle3) - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - player.bindRecommendation(data) - - assertThat(recTitle1.text).isEqualTo(title1) - assertThat(recTitle2.text).isEqualTo(title2) - assertThat(recTitle3.text).isEqualTo(title3) - assertThat(recSubtitle1.text).isEqualTo(subtitle1) - assertThat(recSubtitle2.text).isEqualTo(subtitle2) - assertThat(recSubtitle3.text).isEqualTo(subtitle3) - } - - @Test - fun bindRecommendation_noTitle_subtitleNotShown() { - player.attachRecommendation(recommendationViewHolder) - - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "") - .setSubtitle("fake subtitle") - .setIcon( - Icon.createWithResource( - context, - com.android.settingslib.R.drawable.ic_1x_mobiledata, - ) - ) - .setExtras(Bundle.EMPTY) - .build() - ) - ) - player.bindRecommendation(data) - - assertThat(recSubtitle1.text).isEqualTo("") - } - - @Test - fun bindRecommendation_someHaveTitles_allTitleViewsShown() { - useRealConstraintSets() - player.attachRecommendation(recommendationViewHolder) - - val icon = - Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata) - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "") - .setSubtitle("fake subtitle") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("fake subtitle") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "") - .setSubtitle("fake subtitle") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - player.bindRecommendation(data) - - assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.VISIBLE) - } - - @Test - fun bindRecommendation_someHaveSubtitles_allSubtitleViewsShown() { - useRealConstraintSets() - player.attachRecommendation(recommendationViewHolder) - - val icon = - Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata) - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "") - .setSubtitle("") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("subtitle2") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "title3") - .setSubtitle("") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - player.bindRecommendation(data) - - assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.VISIBLE) - } - - @Test - fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() { - useRealConstraintSets() - player.attachRecommendation(recommendationViewHolder) - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("") - .setIcon( - Icon.createWithResource( - context, - com.android.settingslib.R.drawable.ic_1x_mobiledata, - ) - ) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("") - .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm)) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "title3") - .setSubtitle("") - .setIcon( - Icon.createWithResource( - context, - com.android.settingslib.R.drawable.ic_3g_mobiledata, - ) - ) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - - player.bindRecommendation(data) - - assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE) - assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE) - assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE) - } - - @Test - fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() { - useRealConstraintSets() - player.attachRecommendation(recommendationViewHolder) - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "") - .setSubtitle("subtitle1") - .setIcon( - Icon.createWithResource( - context, - com.android.settingslib.R.drawable.ic_1x_mobiledata, - ) - ) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "") - .setSubtitle("subtitle2") - .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm)) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "") - .setSubtitle("subtitle3") - .setIcon( - Icon.createWithResource( - context, - com.android.settingslib.R.drawable.ic_3g_mobiledata, - ) - ) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - - player.bindRecommendation(data) - - assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE) - assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE) - assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE) - assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE) - assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE) - assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE) - } - - @Test - fun bindRecommendation_setAfterExecutors() { - val albumArt = getColorIcon(Color.RED) - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "title3") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(data) - bgExecutor.runAllReady() - mainExecutor.runAllReady() - - verify(recCardTitle).setTextColor(any<Int>()) - verify(recAppIconItem, times(3)).setImageDrawable(any<Drawable>()) - verify(coverItem, times(3)).setImageDrawable(any<Drawable>()) - verify(coverItem, times(3)).imageMatrix = any() - } - - @Test - fun bindRecommendationWithProgressBars() { - useRealConstraintSets() - val albumArt = getColorIcon(Color.RED) - val bundle = - Bundle().apply { - putInt( - MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, - MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED, - ) - putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.5) - } - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(bundle) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "title3") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(data) - - verify(recProgressBar1).setProgress(50) - verify(recProgressBar1).visibility = View.VISIBLE - verify(recProgressBar2).visibility = View.GONE - verify(recProgressBar3).visibility = View.GONE - assertThat(recSubtitle1.visibility).isEqualTo(View.GONE) - assertThat(recSubtitle2.visibility).isEqualTo(View.VISIBLE) - assertThat(recSubtitle3.visibility).isEqualTo(View.VISIBLE) - } - - @Test - fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() { - useRealConstraintSets() - val albumArt = getColorIcon(Color.RED) - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "title3") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - - // set the screen width less than the width of media controls. - player.context.resources.configuration.screenWidthDp = 350 - player.context.resources.configuration.orientation = Configuration.ORIENTATION_PORTRAIT - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(data) - - val res = player.context.resources - val displayAvailableWidth = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt() - val recCoverWidth: Int = - (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) + - res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2) - val numOfRecs = displayAvailableWidth / recCoverWidth - - assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs) - recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container -> - if (index < numOfRecs) { - assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(collapsedSet.getVisibility(container.id)) - .isEqualTo(ConstraintSet.VISIBLE) - } else { - assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) - } - } - } - - @Test - fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() { - useRealConstraintSets() - val albumArt = getColorIcon(Color.RED) - val data = - smartspaceData.copy( - recommendations = - listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "title3") - .setSubtitle("subtitle1") - .setIcon(albumArt) - .setExtras(Bundle.EMPTY) - .build(), - ) - ) - - // set the screen width less than the width of media controls. - // We should have dp width less than 378 to test. In landscape we should have 2x. - player.context.resources.configuration.screenWidthDp = 700 - player.context.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE - player.attachRecommendation(recommendationViewHolder) - player.bindRecommendation(data) - - val res = player.context.resources - val displayAvailableWidth = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt() - val recCoverWidth: Int = - (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) + - res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2) - val numOfRecs = displayAvailableWidth / recCoverWidth - - assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs) - recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container -> - if (index < numOfRecs) { - assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(collapsedSet.getVisibility(container.id)) - .isEqualTo(ConstraintSet.VISIBLE) - } else { - assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) - } - } - } - - @Test - fun addTwoRecommendationGradients_differentStates() { - // Setup redArtwork and its color scheme. - val redArt = getColorIcon(Color.RED) - val redWallpaperColor = player.getWallpaperColor(redArt) - val redColorScheme = ColorScheme(redWallpaperColor, true, Style.CONTENT) - - // Setup greenArt and its color scheme. - val greenArt = getColorIcon(Color.GREEN) - val greenWallpaperColor = player.getWallpaperColor(greenArt) - val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT) - - // Add gradient to both icons. - val redArtwork = player.addGradientToRecommendationAlbum(redArt, redColorScheme, 10, 10) - val greenArtwork = - player.addGradientToRecommendationAlbum(greenArt, greenColorScheme, 10, 10) - - // They should have different constant states as they have different gradient color. - assertThat(redArtwork.getDrawable(1).constantState) - .isNotEqualTo(greenArtwork.getDrawable(1).constantState) - } - - @Test fun onButtonClick_playsTouchRipple() { val semanticActions = MediaButton( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt index e765b6f77155..760f73c726a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt @@ -42,7 +42,6 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.media.controls.ui.view.GutsViewHolder import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaViewHolder -import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel import com.android.systemui.res.R import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView @@ -79,7 +78,6 @@ class MediaViewControllerTest : SysuiTestCase() { private val configurationController = com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context) private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0) - private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0) private val clock = FakeSystemClock() private lateinit var mainExecutor: FakeExecutor private lateinit var seekBar: SeekBar @@ -110,9 +108,6 @@ class MediaViewControllerTest : SysuiTestCase() { @Mock private lateinit var mockCopiedState: TransitionViewState @Mock private lateinit var detailWidgetState: WidgetState @Mock private lateinit var controlWidgetState: WidgetState - @Mock private lateinit var mediaTitleWidgetState: WidgetState - @Mock private lateinit var mediaSubTitleWidgetState: WidgetState - @Mock private lateinit var mediaContainerWidgetState: WidgetState @Mock private lateinit var seekBarViewModel: SeekBarViewModel @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress> @Mock private lateinit var globalSettings: GlobalSettings @@ -145,7 +140,7 @@ class MediaViewControllerTest : SysuiTestCase() { context: Context, animId: Int, motionInterpolator: Interpolator?, - vararg targets: View? + vararg targets: View?, ): AnimatorSet { return mockAnimator } @@ -158,7 +153,7 @@ class MediaViewControllerTest : SysuiTestCase() { fun testOrientationChanged_heightOfPlayerIsUpdated() { val newConfig = Configuration() - mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) + mediaViewController.attach(player) // Change the height to see the effect of orientation change. MediaViewHolder.backgroundIds.forEach { id -> mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 10 @@ -177,30 +172,8 @@ class MediaViewControllerTest : SysuiTestCase() { } @Test - fun testOrientationChanged_heightOfRecCardIsUpdated() { - val newConfig = Configuration() - - mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION) - // Change the height to see the effect of orientation change. - mediaViewController.expandedLayout - .getConstraint(RecommendationViewHolder.backgroundId) - .layout - .mHeight = 10 - newConfig.orientation = ORIENTATION_LANDSCAPE - configurationController.onConfigurationChanged(newConfig) - - assertTrue( - mediaViewController.expandedLayout - .getConstraint(RecommendationViewHolder.backgroundId) - .layout - .mHeight == - context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded) - ) - } - - @Test fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() { - mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) + mediaViewController.attach(player) player.measureState = TransitionViewState().apply { this.height = 100 @@ -224,29 +197,8 @@ class MediaViewControllerTest : SysuiTestCase() { } @Test - fun testObtainViewState_applySquishFraction_toRecommendationTransitionViewState_height() { - mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION) - recommendation.measureState = TransitionViewState().apply { this.height = 100 } - mediaHostStateHolder.expansion = 1f - val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) - val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) - mediaHostStateHolder.measurementInput = - MeasurementInput(widthMeasureSpec, heightMeasureSpec) - - // Test no squish - mediaHostStateHolder.squishFraction = 1f - assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100) - assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100) - - // Test half squish - mediaHostStateHolder.squishFraction = 0.5f - assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50) - assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100) - } - - @Test fun testObtainViewState_expandedMatchesParentHeight() { - mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) + mediaViewController.attach(player) player.measureState = TransitionViewState().apply { this.height = 100 @@ -283,7 +235,7 @@ class MediaViewControllerTest : SysuiTestCase() { .thenReturn( mutableMapOf( R.id.media_progress_bar to controlWidgetState, - R.id.header_artist to detailWidgetState + R.id.header_artist to detailWidgetState, ) ) whenever(mockCopiedState.measureHeight).thenReturn(200) @@ -311,7 +263,7 @@ class MediaViewControllerTest : SysuiTestCase() { .thenReturn( mutableMapOf( R.id.media_progress_bar to controlWidgetState, - R.id.header_artist to detailWidgetState + R.id.header_artist to detailWidgetState, ) ) whenever(mockCopiedState.measureHeight).thenReturn(200) @@ -332,46 +284,6 @@ class MediaViewControllerTest : SysuiTestCase() { verify(detailWidgetState, never()).alpha = floatThat { it > 0 } } - @Test - fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() { - whenever(mockViewState.copy()).thenReturn(mockCopiedState) - whenever(mockCopiedState.widgetStates) - .thenReturn( - mutableMapOf( - R.id.media_title to mediaTitleWidgetState, - R.id.media_subtitle to mediaSubTitleWidgetState, - R.id.media_cover1_container to mediaContainerWidgetState - ) - ) - whenever(mockCopiedState.measureHeight).thenReturn(360) - // media container widgets occupy [20, 300] - whenever(mediaContainerWidgetState.y).thenReturn(20F) - whenever(mediaContainerWidgetState.height).thenReturn(280) - whenever(mediaContainerWidgetState.alpha).thenReturn(1F) - // media title widgets occupy [320, 330] - whenever(mediaTitleWidgetState.y).thenReturn(320F) - whenever(mediaTitleWidgetState.height).thenReturn(10) - whenever(mediaTitleWidgetState.alpha).thenReturn(1F) - // media subtitle widgets occupy [340, 350] - whenever(mediaSubTitleWidgetState.y).thenReturn(340F) - whenever(mediaSubTitleWidgetState.height).thenReturn(10) - whenever(mediaSubTitleWidgetState.alpha).thenReturn(1F) - - // in current beizer, when the progress reach 0.38, the result will be 0.5 - mediaViewController.squishViewState(mockViewState, 307.6F / 360F) - verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - mediaViewController.squishViewState(mockViewState, 320F / 360F) - verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } - // media title and media subtitle are in same widget group, should be calculate together and - // have same alpha - mediaViewController.squishViewState(mockViewState, 353.8F / 360F) - verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - mediaViewController.squishViewState(mockViewState, 360F / 360F) - verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } - verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } - } - @EnableSceneContainer @Test fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt deleted file mode 100644 index 1edd405f4af6..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.domain.pipeline.interactor - -import android.content.applicationContext -import com.android.systemui.broadcast.broadcastSender -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.media.controls.data.repository.mediaFilterRepository -import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor -import com.android.systemui.plugins.activityStarter - -val Kosmos.mediaRecommendationsInteractor by - Kosmos.Fixture { - MediaRecommendationsInteractor( - applicationScope = applicationCoroutineScope, - applicationContext = applicationContext, - repository = mediaFilterRepository, - mediaDataProcessor = mediaDataProcessor, - broadcastSender = broadcastSender, - activityStarter = activityStarter, - ) - } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt index 5e6434d84538..976b4046f58d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt @@ -37,7 +37,6 @@ val Kosmos.mediaCarouselViewModel by visualStabilityProvider = visualStabilityProvider, interactor = mediaCarouselInteractor, controlInteractorFactory = mediaControlInteractorFactory, - recommendationsViewModel = mediaRecommendationsViewModel, logger = mediaUiEventLogger, mediaLogger = mediaLogger, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt deleted file mode 100644 index 34a527781979..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.controls.ui.viewmodel - -import android.content.applicationContext -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor -import com.android.systemui.media.controls.util.mediaUiEventLogger - -val Kosmos.mediaRecommendationsViewModel by - Kosmos.Fixture { - MediaRecommendationsViewModel( - applicationContext = applicationContext, - backgroundDispatcher = testDispatcher, - interactor = mediaRecommendationsInteractor, - logger = mediaUiEventLogger, - ) - } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt index 00deaafd7009..dbd1c9ce56fe 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.model import android.view.Display import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor import com.android.systemui.display.data.repository.FakePerDisplayRepository +import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -45,5 +46,5 @@ val Kosmos.sysUiStateFactory by Fixture { val Kosmos.fakeSysUIStatePerDisplayRepository by Fixture { FakePerDisplayRepository<SysUiState>() } val Kosmos.sysuiStateInteractor by Fixture { - SysUIStateDisplaysInteractor(fakeSysUIStatePerDisplayRepository) + SysUIStateDisplaysInteractor(fakeSysUIStatePerDisplayRepository, displayRepository) } diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp index 97574e6e35e3..aad534c3289c 100644 --- a/packages/Vcn/service-b/Android.bp +++ b/packages/Vcn/service-b/Android.bp @@ -29,7 +29,10 @@ filegroup { "vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java", ], }), - visibility: ["//frameworks/base/services/core"], + visibility: [ + "//frameworks/base/services/core", + "//packages/modules/Connectivity/service-t", + ], } // TODO: b/374174952 This library is only used in "service-connectivity-b-platform" diff --git a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java index b9dcc6160d68..aac217b3cc7a 100644 --- a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java +++ b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java @@ -37,9 +37,27 @@ public final class ConnectivityServiceInitializerB extends SystemService { private static final String TAG = ConnectivityServiceInitializerB.class.getSimpleName(); private final VcnManagementService mVcnManagementService; + // STOPSHIP: b/385203616 This static flag is for handling a temporary case when the mainline + // module prebuilt has updated to register the VCN but the platform change to remove + // registration is not merged. After mainline prebuilt is updated, we should merge the platform + // ASAP and remove this static check. This check is safe because both mainline and platform + // registration are triggered from the same method on the same thread. + private static boolean sIsRegistered = false; + public ConnectivityServiceInitializerB(Context context) { super(context); - mVcnManagementService = VcnManagementService.create(context); + + if (!sIsRegistered) { + mVcnManagementService = VcnManagementService.create(context); + sIsRegistered = true; + } else { + mVcnManagementService = null; + Log.e( + TAG, + "Ignore this registration since VCN is already registered. It will happen when" + + " the mainline module prebuilt has updated to register the VCN but the" + + " platform change to remove registration is not merged."); + } } @Override diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java index 57bbb4a7a0a7..90ddc43ae3ed 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java @@ -86,13 +86,6 @@ public class AutoclickTypePanel { }) public @interface Corner {} - private static final @Corner int[] CORNER_ROTATION_ORDER = { - CORNER_BOTTOM_RIGHT, - CORNER_BOTTOM_LEFT, - CORNER_TOP_LEFT, - CORNER_TOP_RIGHT - }; - // An interface exposed to {@link AutoclickController) to handle different actions on the panel, // including changing autoclick type, pausing/resuming autoclick. public interface ClickPanelControllerInterface { @@ -136,10 +129,9 @@ public class AutoclickTypePanel { // Whether autoclick is paused. private boolean mPaused = false; - // Tracks the current corner position of the panel using an index into CORNER_ROTATION_ORDER - // array. This allows the panel to cycle through screen corners in a defined sequence when - // repositioned. - private int mCurrentCornerIndex = 0; + + // The current corner position of the panel, default to bottom right. + private @Corner int mCurrentCorner = CORNER_BOTTOM_RIGHT; private final LinearLayout mLeftClickButton; private final LinearLayout mRightClickButton; @@ -257,13 +249,13 @@ public class AutoclickTypePanel { params.gravity = Gravity.START | Gravity.TOP; // Set the current corner to be bottom-left to ensure that the subsequent reposition // action rotates the panel clockwise from bottom-left towards top-left. - mCurrentCornerIndex = 1; + mCurrentCorner = CORNER_BOTTOM_LEFT; } else { // Snap to right edge. Set params.gravity to make sure x, y offsets from correct anchor. params.gravity = Gravity.END | Gravity.TOP; // Set the current corner to be top-right to ensure that the subsequent reposition // action rotates the panel clockwise from top-right towards bottom-right. - mCurrentCornerIndex = 3; + mCurrentCorner = CORNER_TOP_RIGHT; } // Apply final position: set params.x to be edge margin, params.y to maintain vertical @@ -415,10 +407,10 @@ public class AutoclickTypePanel { /** Moves the panel to the next corner in clockwise direction. */ private void moveToNextCorner() { - @Corner int nextCornerIndex = (mCurrentCornerIndex + 1) % CORNER_ROTATION_ORDER.length; - mCurrentCornerIndex = nextCornerIndex; + @Corner int nextCorner = (mCurrentCorner + 1) % 4; + mCurrentCorner = nextCorner; - setPanelPositionForCorner(mParams, mCurrentCornerIndex); + setPanelPositionForCorner(mParams, mCurrentCorner); mWindowManager.updateViewLayout(mContentView, mParams); } @@ -457,7 +449,7 @@ public class AutoclickTypePanel { String.valueOf(mParams.gravity), String.valueOf(mParams.x), String.valueOf(mParams.y), - String.valueOf(mCurrentCornerIndex) + String.valueOf(mCurrentCorner) }); Settings.Secure.putStringForUser(mContext.getContentResolver(), ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, positionString, mUserId); @@ -473,7 +465,7 @@ public class AutoclickTypePanel { ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, mUserId); if (savedPosition == null) { setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT); - mCurrentCornerIndex = 0; + mCurrentCorner = CORNER_BOTTOM_RIGHT; return; } @@ -481,7 +473,7 @@ public class AutoclickTypePanel { String[] parts = TextUtils.split(savedPosition, POSITION_DELIMITER); if (!isValidPositionParts(parts)) { setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT); - mCurrentCornerIndex = 0; + mCurrentCorner = CORNER_BOTTOM_RIGHT; return; } @@ -489,7 +481,7 @@ public class AutoclickTypePanel { mParams.gravity = Integer.parseInt(parts[0]); mParams.x = Integer.parseInt(parts[1]); mParams.y = Integer.parseInt(parts[2]); - mCurrentCornerIndex = Integer.parseInt(parts[3]); + mCurrentCorner = Integer.parseInt(parts[3]); } private boolean isValidPositionParts(String[] parts) { @@ -538,8 +530,8 @@ public class AutoclickTypePanel { @VisibleForTesting @Corner - int getCurrentCornerIndexForTesting() { - return mCurrentCornerIndex; + int getCurrentCornerForTesting() { + return mCurrentCorner; } @VisibleForTesting diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 13d367a95942..336a35e7a7e3 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -3433,8 +3433,12 @@ public class OomAdjuster { // Process has user visible activities. return PROCESS_CAPABILITY_CPU_TIME; } - if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) { - // It running a short fgs, just give it cpu time. + if (Flags.prototypeAggressiveFreezing()) { + if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) { + // Grant cpu time for short FGS even when aggressively freezing. + return PROCESS_CAPABILITY_CPU_TIME; + } + } else if (app.mServices.hasForegroundServices()) { return PROCESS_CAPABILITY_CPU_TIME; } if (app.mReceivers.numberOfCurReceivers() > 0) { diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index 7f853844c326..67e1ccc6a850 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -138,11 +138,6 @@ final class InputGestureManager { KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT ), createKeyGesture( - KeyEvent.KEYCODE_DEL, - KeyEvent.META_META_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_BACK - ), - createKeyGesture( KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_META_ON, KeyGestureEvent.KEY_GESTURE_TYPE_BACK diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index f40d0dd18213..2d937bdcc683 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -16,6 +16,8 @@ package com.android.server.location.contexthub; +import static com.android.server.location.contexthub.ContextHubTransactionManager.RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT; + import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; @@ -44,6 +46,9 @@ import com.android.internal.annotations.GuardedBy; import java.util.Collection; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; @@ -100,7 +105,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub private final Object mOpenSessionLock = new Object(); - static class SessionInfo { + static class Session { enum SessionState { /* The session is pending acceptance from the remote endpoint. */ PENDING, @@ -119,7 +124,15 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub */ private final Set<Integer> mPendingSequenceNumbers = new HashSet<>(); - SessionInfo(HubEndpointInfo remoteEndpointInfo, boolean remoteInitiated) { + /** + * Stores the history of received messages that are timestamped. We use a LinkedHashMap to + * guarantee insertion ordering for easier manipulation of removing expired entries. + * + * <p>The key is the sequence number, and the value is the timestamp in milliseconds. + */ + private final LinkedHashMap<Integer, Long> mRxMessageHistoryMap = new LinkedHashMap<>(); + + Session(HubEndpointInfo remoteEndpointInfo, boolean remoteInitiated) { mRemoteEndpointInfo = remoteEndpointInfo; mRemoteInitiated = remoteInitiated; } @@ -157,11 +170,43 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub consumer.accept(sequenceNumber); } } + + public boolean isInMessageHistory(HubMessage message) { + // Clean up the history + Iterator<Map.Entry<Integer, Long>> iterator = + mRxMessageHistoryMap.entrySet().iterator(); + long nowMillis = System.currentTimeMillis(); + while (iterator.hasNext()) { + Map.Entry<Integer, Long> nextEntry = iterator.next(); + long expiryMillis = RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT.toMillis(); + if (nowMillis >= nextEntry.getValue() + expiryMillis) { + iterator.remove(); + } + break; + } + + return mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber()); + } + + public void addMessageToHistory(HubMessage message) { + if (mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber())) { + long value = mRxMessageHistoryMap.get(message.getMessageSequenceNumber()); + Log.w( + TAG, + "Message already exists in history (inserted @ " + + value + + " ms): " + + message); + return; + } + mRxMessageHistoryMap.put( + message.getMessageSequenceNumber(), System.currentTimeMillis()); + } } /** A map between a session ID which maps to its current state. */ @GuardedBy("mOpenSessionLock") - private final SparseArray<SessionInfo> mSessionInfoMap = new SparseArray<>(); + private final SparseArray<Session> mSessionMap = new SparseArray<>(); /** The package name of the app that created the endpoint */ private final String mPackageName; @@ -232,7 +277,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub synchronized (mOpenSessionLock) { try { - mSessionInfoMap.put(sessionId, new SessionInfo(destination, false)); + mSessionMap.put(sessionId, new Session(destination, false)); mHubInterface.openEndpointSession( sessionId, halEndpointInfo.id, mHalEndpointInfo.id, serviceDescriptor); } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { @@ -263,8 +308,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub super.unregister_enforcePermission(); synchronized (mOpenSessionLock) { // Iterate in reverse since cleanupSessionResources will remove the entry - for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) { - int id = mSessionInfoMap.keyAt(i); + for (int i = mSessionMap.size() - 1; i >= 0; i--) { + int id = mSessionMap.keyAt(i); halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE); cleanupSessionResources(id); } @@ -290,14 +335,14 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub public void openSessionRequestComplete(int sessionId) { super.openSessionRequestComplete_enforcePermission(); synchronized (mOpenSessionLock) { - SessionInfo info = mSessionInfoMap.get(sessionId); + Session info = mSessionMap.get(sessionId); if (info == null) { throw new IllegalArgumentException( "openSessionRequestComplete for invalid session id=" + sessionId); } try { mHubInterface.endpointSessionOpenComplete(sessionId); - info.setSessionState(SessionInfo.SessionState.ACTIVE); + info.setSessionState(Session.SessionState.ACTIVE); } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { Log.e(TAG, "Exception while calling endpointSessionOpenComplete", e); } @@ -310,7 +355,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub int sessionId, HubMessage message, IContextHubTransactionCallback callback) { super.sendMessage_enforcePermission(); synchronized (mOpenSessionLock) { - SessionInfo info = mSessionInfoMap.get(sessionId); + Session info = mSessionMap.get(sessionId); if (info == null) { throw new IllegalArgumentException( "sendMessage for invalid session id=" + sessionId); @@ -393,9 +438,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } else { synchronized (mOpenSessionLock) { // Iterate in reverse since cleanupSessionResources will remove the entry - for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) { - int id = mSessionInfoMap.keyAt(i); - HubEndpointInfo target = mSessionInfoMap.get(id).getRemoteEndpointInfo(); + for (int i = mSessionMap.size() - 1; i >= 0; i--) { + int id = mSessionMap.keyAt(i); + HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo(); if (!hasEndpointPermissions(target)) { halCloseEndpointSessionNoThrow(id, Reason.PERMISSION_DENIED); onCloseEndpointSession(id, Reason.PERMISSION_DENIED); @@ -415,13 +460,13 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub sb.append("wakelock: ").append(mWakeLock); } synchronized (mOpenSessionLock) { - if (mSessionInfoMap.size() != 0) { + if (mSessionMap.size() != 0) { sb.append(System.lineSeparator()); sb.append(" sessions: "); sb.append(System.lineSeparator()); } - for (int i = 0; i < mSessionInfoMap.size(); i++) { - int id = mSessionInfoMap.keyAt(i); + for (int i = 0; i < mSessionMap.size(); i++) { + int id = mSessionMap.keyAt(i); int count = i + 1; sb.append( " " @@ -429,7 +474,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub + ". id=" + id + ", remote:" - + mSessionInfoMap.get(id).getRemoteEndpointInfo()); + + mSessionMap.get(id).getRemoteEndpointInfo()); sb.append(System.lineSeparator()); } } @@ -485,23 +530,23 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub Log.w(TAG, "Unknown session ID in onEndpointSessionOpenComplete: id=" + sessionId); return; } - mSessionInfoMap.get(sessionId).setSessionState(SessionInfo.SessionState.ACTIVE); + mSessionMap.get(sessionId).setSessionState(Session.SessionState.ACTIVE); } invokeCallback((consumer) -> consumer.onSessionOpenComplete(sessionId)); } /* package */ void onMessageReceived(int sessionId, HubMessage message) { - byte code = onMessageReceivedInternal(sessionId, message); - if (code != ErrorCode.OK && message.isResponseRequired()) { - sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), code); + byte errorCode = onMessageReceivedInternal(sessionId, message); + if (errorCode != ErrorCode.OK && message.isResponseRequired()) { + sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), errorCode); } } /* package */ void onMessageDeliveryStatusReceived( int sessionId, int sequenceNumber, byte errorCode) { synchronized (mOpenSessionLock) { - SessionInfo info = mSessionInfoMap.get(sessionId); + Session info = mSessionMap.get(sessionId); if (info == null || !info.isActive()) { Log.w(TAG, "Received delivery status for invalid session: id=" + sessionId); return; @@ -517,7 +562,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub /* package */ boolean hasSessionId(int sessionId) { synchronized (mOpenSessionLock) { - return mSessionInfoMap.contains(sessionId); + return mSessionMap.contains(sessionId); } } @@ -531,8 +576,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } } synchronized (mOpenSessionLock) { - for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) { - int id = mSessionInfoMap.keyAt(i); + for (int i = mSessionMap.size() - 1; i >= 0; i--) { + int id = mSessionMap.keyAt(i); onCloseEndpointSession(id, Reason.HUB_RESET); } } @@ -555,7 +600,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId); return Optional.of(Reason.UNSPECIFIED); } - mSessionInfoMap.put(sessionId, new SessionInfo(initiator, true)); + mSessionMap.put(sessionId, new Session(initiator, true)); } boolean success = @@ -567,7 +612,6 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } private byte onMessageReceivedInternal(int sessionId, HubMessage message) { - HubEndpointInfo remote; synchronized (mOpenSessionLock) { if (!isSessionActive(sessionId)) { Log.e( @@ -578,29 +622,36 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub + message); return ErrorCode.PERMANENT_ERROR; } - remote = mSessionInfoMap.get(sessionId).getRemoteEndpointInfo(); - } + HubEndpointInfo remote = mSessionMap.get(sessionId).getRemoteEndpointInfo(); + if (mSessionMap.get(sessionId).isInMessageHistory(message)) { + Log.e(TAG, "Dropping duplicate message: " + message); + return ErrorCode.TRANSIENT_ERROR; + } - try { - Binder.withCleanCallingIdentity( - () -> { - if (!notePermissions(remote)) { - throw new RuntimeException( - "Dropping message from " - + remote - + ". " - + mPackageName - + " doesn't have permission"); - } - }); - } catch (RuntimeException e) { - Log.e(TAG, e.getMessage()); - return ErrorCode.PERMISSION_DENIED; - } + try { + Binder.withCleanCallingIdentity( + () -> { + if (!notePermissions(remote)) { + throw new RuntimeException( + "Dropping message from " + + remote + + ". " + + mPackageName + + " doesn't have permission"); + } + }); + } catch (RuntimeException e) { + Log.e(TAG, e.getMessage()); + return ErrorCode.PERMISSION_DENIED; + } - boolean success = - invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message)); - return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR; + boolean success = + invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message)); + if (success) { + mSessionMap.get(sessionId).addMessageToHistory(message); + } + return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR; + } } /** @@ -634,7 +685,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub */ private boolean cleanupSessionResources(int sessionId) { synchronized (mOpenSessionLock) { - SessionInfo info = mSessionInfoMap.get(sessionId); + Session info = mSessionMap.get(sessionId); if (info != null) { if (!info.isRemoteInitiated()) { mEndpointManager.returnSessionId(sessionId); @@ -644,7 +695,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub mTransactionManager.onMessageDeliveryResponse( sequenceNumber, /* success= */ false); }); - mSessionInfoMap.remove(sessionId); + mSessionMap.remove(sessionId); } return info != null; } @@ -656,7 +707,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub */ private boolean isSessionActive(int sessionId) { synchronized (mOpenSessionLock) { - return hasSessionId(sessionId) && mSessionInfoMap.get(sessionId).isActive(); + return hasSessionId(sessionId) && mSessionMap.get(sessionId).isActive(); } } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 398894bf5273..cec5a93a2a15 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -817,7 +817,13 @@ public final class NotificationRecord { } if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization()) && signals.containsKey(KEY_SUMMARIZATION)) { - mSummarization = signals.getString(KEY_SUMMARIZATION); + CharSequence summary = signals.getCharSequence(KEY_SUMMARIZATION, + signals.getString(KEY_SUMMARIZATION)); + if (summary != null) { + mSummarization = summary.toString(); + } else { + mSummarization = null; + } EventLogTags.writeNotificationAdjusted(getKey(), KEY_SUMMARIZATION, Boolean.toString(mSummarization != null)); } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index d3513053caf3..66e9e772e063 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1792,7 +1792,7 @@ public class ShortcutService extends IShortcutService.Stub { void injectPostToHandlerDebounced(@NonNull final Object token, @NonNull final Runnable r) { Objects.requireNonNull(token); Objects.requireNonNull(r); - synchronized (mServiceLock) { + synchronized (mHandler) { mHandler.removeCallbacksAndMessages(token); mHandler.postDelayed(r, token, CALLBACK_DELAY); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 22f20028eb9c..46dc75817a36 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3801,7 +3801,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { return true; } } - // fall through + break; case KeyEvent.KEYCODE_ESCAPE: if (firstDown && event.isMetaPressed()) { notifyKeyGestureCompleted(event, diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 340115a7d465..5d8f57866f7d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -43,6 +43,7 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -107,6 +108,7 @@ import android.os.PowerManagerInternal; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; @@ -698,7 +700,7 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test - @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY) + @EnableFlags({Flags.FLAG_USE_CPU_TIME_CAPABILITY, Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING}) public void testUpdateOomAdjFreezeState_bindingFromShortFgs() { // Setting up a started short FGS within app1. final ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService); @@ -744,6 +746,44 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY) + @DisableFlags(Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING) + public void testUpdateOomAdjFreezeState_bindingFromFgs() { + final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + mProcessStateController.setHasForegroundServices(app.mServices, true, + FOREGROUND_SERVICE_TYPE_SPECIAL_USE, false); + + final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + // App with a foreground service binds to app2 + bindService(app2, app, null, null, 0, mock(IBinder.class)); + + setProcessesToLru(app, app2); + updateOomAdj(app); + + assertCpuTime(app); + assertCpuTime(app2); + } + + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY) + @DisableFlags(Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING) + public void testUpdateOomAdjFreezeState_soloFgs() { + final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + mProcessStateController.setHasForegroundServices(app.mServices, true, + FOREGROUND_SERVICE_TYPE_SPECIAL_USE, false); + + setProcessesToLru(app); + updateOomAdj(app); + + assertCpuTime(app); + } + + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY) public void testUpdateOomAdjFreezeState_receivers() { final ProcessRecord app = makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java index f7b16c808c50..dd089fcb1d70 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java @@ -201,11 +201,11 @@ public class AutoclickTypePanelTest { public void moveToNextCorner_positionButton_rotatesThroughAllPositions() { // Define all positions in sequence int[][] expectedPositions = { - {0, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}, - {1, Gravity.START | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}, - {2, Gravity.START | Gravity.TOP, /*x=*/ 15, /*y=*/ 30}, - {3, Gravity.END | Gravity.TOP, /*x=*/ 15, /*y=*/ 30}, - {0, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90} + {CORNER_BOTTOM_RIGHT, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}, + {CORNER_BOTTOM_LEFT, Gravity.START | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}, + {CORNER_TOP_LEFT, Gravity.START | Gravity.TOP, /*x=*/ 15, /*y=*/ 30}, + {CORNER_TOP_RIGHT, Gravity.END | Gravity.TOP, /*x=*/ 15, /*y=*/ 30}, + {CORNER_BOTTOM_RIGHT, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90} }; // Check initial position @@ -270,7 +270,7 @@ public class AutoclickTypePanelTest { int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels; // Verify initial corner is bottom-right. - assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()) + assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()) .isEqualTo(CORNER_BOTTOM_RIGHT); dispatchDragSequence(contentView, @@ -279,7 +279,7 @@ public class AutoclickTypePanelTest { // Verify snapping to the right. assertThat(params.gravity).isEqualTo(Gravity.END | Gravity.TOP); - assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()) + assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()) .isEqualTo(CORNER_TOP_RIGHT); } @@ -293,7 +293,7 @@ public class AutoclickTypePanelTest { int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels; // Verify initial corner is bottom-right. - assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()) + assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()) .isEqualTo(CORNER_BOTTOM_RIGHT); dispatchDragSequence(contentView, @@ -302,7 +302,7 @@ public class AutoclickTypePanelTest { // Verify snapping to the left. assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP); - assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()) + assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()) .isEqualTo(CORNER_BOTTOM_LEFT); } @@ -319,7 +319,7 @@ public class AutoclickTypePanelTest { // Verify panel is positioned at default bottom-right corner. WindowManager.LayoutParams params = panel.getLayoutParamsForTesting(); - assertThat(panel.getCurrentCornerIndexForTesting()).isEqualTo(CORNER_BOTTOM_RIGHT); + assertThat(panel.getCurrentCornerForTesting()).isEqualTo(CORNER_BOTTOM_RIGHT); assertThat(params.gravity).isEqualTo(Gravity.END | Gravity.BOTTOM); assertThat(params.x).isEqualTo(15); // Default edge margin. assertThat(params.y).isEqualTo(90); // Default bottom offset. @@ -353,7 +353,7 @@ public class AutoclickTypePanelTest { assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP); assertThat(params.x).isEqualTo(15); assertThat(params.y).isEqualTo(30); - assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo( + assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()).isEqualTo( CORNER_TOP_LEFT); } @@ -392,7 +392,7 @@ public class AutoclickTypePanelTest { assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP); assertThat(params.x).isEqualTo(15); // PANEL_EDGE_MARGIN assertThat(params.y).isEqualTo(panelLocation[1] + 10); - assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo( + assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()).isEqualTo( CORNER_BOTTOM_LEFT); } @@ -453,7 +453,7 @@ public class AutoclickTypePanelTest { private void verifyPanelPosition(int[] expectedPosition) { WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting(); - assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo( + assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()).isEqualTo( expectedPosition[0]); assertThat(params.gravity).isEqualTo(expectedPosition[1]); assertThat(params.x).isEqualTo(expectedPosition[2]); diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java index 1de864cb4eb0..565a9b6c1c44 100644 --- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java @@ -17,6 +17,7 @@ package com.android.server.location.contexthub; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.timeout; @@ -42,12 +43,10 @@ import android.os.Binder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.util.Log; + import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; -import java.util.Collections; -import java.util.List; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -57,6 +56,9 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Collections; +import java.util.List; + @RunWith(AndroidJUnit4.class) @Presubmit public class ContextHubEndpointTest { @@ -73,6 +75,12 @@ public class ContextHubEndpointTest { private static final String TARGET_ENDPOINT_NAME = "Example target endpoint"; private static final int TARGET_ENDPOINT_ID = 1; + private static final int SAMPLE_MESSAGE_TYPE = 1234; + private static final HubMessage SAMPLE_MESSAGE = + new HubMessage.Builder(SAMPLE_MESSAGE_TYPE, new byte[] {1, 2, 3, 4, 5}) + .setResponseRequired(true) + .build(); + private ContextHubClientManager mClientManager; private ContextHubEndpointManager mEndpointManager; private HubInfoRegistry mHubInfoRegistry; @@ -229,23 +237,34 @@ public class ContextHubEndpointTest { assertThat(mTransactionManager.numReliableMessageTransactionPending()).isEqualTo(0); } + @Test + public void testDuplicateMessageRejected() throws RemoteException { + IContextHubEndpoint endpoint = registerExampleEndpoint(); + int sessionId = openTestSession(endpoint); + + mEndpointManager.onMessageReceived(sessionId, SAMPLE_MESSAGE); + ArgumentCaptor<HubMessage> messageCaptor = ArgumentCaptor.forClass(HubMessage.class); + verify(mMockCallback).onMessageReceived(eq(sessionId), messageCaptor.capture()); + assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_MESSAGE); + + // Send a duplicate message and confirm it can be rejected + mEndpointManager.onMessageReceived(sessionId, SAMPLE_MESSAGE); + ArgumentCaptor<MessageDeliveryStatus> statusCaptor = + ArgumentCaptor.forClass(MessageDeliveryStatus.class); + verify(mMockEndpointCommunications) + .sendMessageDeliveryStatusToEndpoint(eq(sessionId), statusCaptor.capture()); + assertThat(statusCaptor.getValue().messageSequenceNumber) + .isEqualTo(SAMPLE_MESSAGE.getMessageSequenceNumber()); + assertThat(statusCaptor.getValue().errorCode).isEqualTo(ErrorCode.TRANSIENT_ERROR); + + unregisterExampleEndpoint(endpoint); + } + /** A helper method to create a session and validates reliable message sending. */ private void testMessageTransactionInternal( IContextHubEndpoint endpoint, boolean deliverMessageStatus) throws RemoteException { - HubEndpointInfo targetInfo = - new HubEndpointInfo( - TARGET_ENDPOINT_NAME, - TARGET_ENDPOINT_ID, - ENDPOINT_PACKAGE_NAME, - Collections.emptyList()); - int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null); - mEndpointManager.onEndpointSessionOpenComplete(sessionId); + int sessionId = openTestSession(endpoint); - final int messageType = 1234; - HubMessage message = - new HubMessage.Builder(messageType, new byte[] {1, 2, 3, 4, 5}) - .setResponseRequired(true) - .build(); IContextHubTransactionCallback callback = new IContextHubTransactionCallback.Stub() { @Override @@ -258,13 +277,13 @@ public class ContextHubEndpointTest { Log.i(TAG, "Received onTransactionComplete callback, result=" + result); } }; - endpoint.sendMessage(sessionId, message, callback); + endpoint.sendMessage(sessionId, SAMPLE_MESSAGE, callback); ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(mMockEndpointCommunications, timeout(1000)) .sendMessageToEndpoint(eq(sessionId), messageCaptor.capture()); Message halMessage = messageCaptor.getValue(); - assertThat(halMessage.type).isEqualTo(message.getMessageType()); - assertThat(halMessage.content).isEqualTo(message.getMessageBody()); + assertThat(halMessage.type).isEqualTo(SAMPLE_MESSAGE.getMessageType()); + assertThat(halMessage.content).isEqualTo(SAMPLE_MESSAGE.getMessageBody()); assertThat(mTransactionManager.numReliableMessageTransactionPending()).isEqualTo(1); if (deliverMessageStatus) { @@ -308,4 +327,16 @@ public class ContextHubEndpointTest { .isEqualTo(expectedInfo.getIdentifier().getHub()); assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0); } + + private int openTestSession(IContextHubEndpoint endpoint) throws RemoteException { + HubEndpointInfo targetInfo = + new HubEndpointInfo( + TARGET_ENDPOINT_NAME, + TARGET_ENDPOINT_ID, + ENDPOINT_PACKAGE_NAME, + Collections.emptyList()); + int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null); + mEndpointManager.onEndpointSessionOpenComplete(sessionId); + return sessionId; + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 4391152220c0..e9cf036d0fb3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -1009,6 +1009,65 @@ public class NotificationRecordTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_NM_SUMMARIZATION) + public void testSummarization_null() { + StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, groupId /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + assertThat(record.getSummarization()).isNull(); + + Bundle signals = new Bundle(); + signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, null); + record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId())); + + record.applyAdjustments(); + + assertThat(record.getSummarization()).isNull(); + } + + @Test + @EnableFlags(Flags.FLAG_NM_SUMMARIZATION) + public void testSummarization_charSequence() { + CharSequence summary = "hello"; + StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, groupId /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + assertThat(record.getSummarization()).isNull(); + + Bundle signals = new Bundle(); + signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, summary); + record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId())); + + record.applyAdjustments(); + + assertThat(record.getSummarization()).isEqualTo(summary.toString()); + } + + @Test + @EnableFlags(Flags.FLAG_NM_SUMMARIZATION) + public void testSummarization_string() { + String summary = "hello"; + StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, groupId /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + assertThat(record.getSummarization()).isNull(); + + Bundle signals = new Bundle(); + signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, summary); + record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId())); + + record.applyAdjustments(); + + assertThat(record.getSummarization()).isEqualTo(summary); + } + + @Test public void testSensitiveContent() { StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index 6d8a48799112..3ca019728c2b 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -150,8 +150,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT}, KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON}, - {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL}, - KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DEL, META_ON}, {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH}, KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0}, diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 531f51604507..9e57fd3c1a7b 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -30,6 +30,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.ParcelFileDescriptor; +import android.os.UserHandle; import com.android.internal.telecom.IVideoProvider; import com.android.server.telecom.flags.Flags; @@ -680,6 +681,7 @@ public final class Call { private final @CallDirection int mCallDirection; private final @Connection.VerificationStatus int mCallerNumberVerificationStatus; private final Uri mContactPhotoUri; + private final UserHandle mAssociatedUser; /** * Whether the supplied capabilities supports the specified capability. @@ -1081,6 +1083,16 @@ public final class Call { return mCallerNumberVerificationStatus; } + /** + * Gets the user that originated the call + * @return The user + * + * @hide + */ + public UserHandle getAssociatedUser() { + return mAssociatedUser; + } + @Override public boolean equals(Object o) { if (o instanceof Details) { @@ -1107,7 +1119,8 @@ public final class Call { Objects.equals(mCallDirection, d.mCallDirection) && Objects.equals(mCallerNumberVerificationStatus, d.mCallerNumberVerificationStatus) && - Objects.equals(mContactPhotoUri, d.mContactPhotoUri); + Objects.equals(mContactPhotoUri, d.mContactPhotoUri) && + Objects.equals(mAssociatedUser, d.mAssociatedUser); } return false; } @@ -1133,7 +1146,8 @@ public final class Call { mContactDisplayName, mCallDirection, mCallerNumberVerificationStatus, - mContactPhotoUri); + mContactPhotoUri, + mAssociatedUser); } /** {@hide} */ @@ -1158,7 +1172,8 @@ public final class Call { String contactDisplayName, int callDirection, int callerNumberVerificationStatus, - Uri contactPhotoUri) { + Uri contactPhotoUri, + UserHandle originatingUser) { mState = state; mTelecomCallId = telecomCallId; mHandle = handle; @@ -1180,6 +1195,7 @@ public final class Call { mCallDirection = callDirection; mCallerNumberVerificationStatus = callerNumberVerificationStatus; mContactPhotoUri = contactPhotoUri; + mAssociatedUser = originatingUser; } /** {@hide} */ @@ -1205,7 +1221,8 @@ public final class Call { parcelableCall.getContactDisplayName(), parcelableCall.getCallDirection(), parcelableCall.getCallerNumberVerificationStatus(), - parcelableCall.getContactPhotoUri() + parcelableCall.getContactPhotoUri(), + parcelableCall.getAssociatedUser() ); } @@ -2631,7 +2648,8 @@ public final class Call { mDetails.getContactDisplayName(), mDetails.getCallDirection(), mDetails.getCallerNumberVerificationStatus(), - mDetails.getContactPhotoUri() + mDetails.getContactPhotoUri(), + mDetails.getAssociatedUser() ); fireDetailsChanged(mDetails); } diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 6a1318982e77..bd004e5e6231 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -16,14 +16,17 @@ package android.telecom; +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; +import android.os.UserHandle; import android.telecom.Call.Details.CallDirection; import com.android.internal.telecom.IVideoProvider; @@ -70,6 +73,7 @@ public final class ParcelableCall implements Parcelable { private String mContactDisplayName; private String mActiveChildCallId; private Uri mContactPhotoUri; + private UserHandle mAssociatedUser; public ParcelableCallBuilder setId(String id) { mId = id; @@ -230,6 +234,11 @@ public final class ParcelableCall implements Parcelable { return this; } + public ParcelableCallBuilder setAssociatedUser(UserHandle user) { + mAssociatedUser = user; + return this; + } + public ParcelableCall createParcelableCall() { return new ParcelableCall( mId, @@ -262,7 +271,8 @@ public final class ParcelableCall implements Parcelable { mCallerNumberVerificationStatus, mContactDisplayName, mActiveChildCallId, - mContactPhotoUri); + mContactPhotoUri, + mAssociatedUser); } public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) { @@ -300,6 +310,7 @@ public final class ParcelableCall implements Parcelable { newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName; newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId; newBuilder.mContactPhotoUri = parcelableCall.mContactPhotoUri; + newBuilder.mAssociatedUser = parcelableCall.mAssociatedUser; return newBuilder; } } @@ -336,6 +347,7 @@ public final class ParcelableCall implements Parcelable { private final String mContactDisplayName; private final String mActiveChildCallId; // Only valid for CDMA conferences private final Uri mContactPhotoUri; + private final UserHandle mAssociatedUser; public ParcelableCall( String id, @@ -368,7 +380,8 @@ public final class ParcelableCall implements Parcelable { int callerNumberVerificationStatus, String contactDisplayName, String activeChildCallId, - Uri contactPhotoUri + Uri contactPhotoUri, + UserHandle associatedUser ) { mId = id; mState = state; @@ -401,6 +414,7 @@ public final class ParcelableCall implements Parcelable { mContactDisplayName = contactDisplayName; mActiveChildCallId = activeChildCallId; mContactPhotoUri = contactPhotoUri; + mAssociatedUser = associatedUser; } /** The unique ID of the call. */ @@ -624,6 +638,13 @@ public final class ParcelableCall implements Parcelable { return mContactPhotoUri; } + /** + * @return the originating user + */ + public @NonNull UserHandle getAssociatedUser() { + return mAssociatedUser; + } + /** * @return On a CDMA conference with two participants, returns the ID of the child call that's @@ -666,6 +687,9 @@ public final class ParcelableCall implements Parcelable { source.readList(conferenceableCallIds, classLoader, java.lang.String.class); Bundle intentExtras = source.readBundle(classLoader); Bundle extras = source.readBundle(classLoader); + if (extras == null) { + extras = new Bundle(); + } int supportedAudioRoutes = source.readInt(); boolean isRttCallChanged = source.readByte() == 1; ParcelableRttCall rttCall = source.readParcelable(classLoader, android.telecom.ParcelableRttCall.class); @@ -675,6 +699,8 @@ public final class ParcelableCall implements Parcelable { String contactDisplayName = source.readString(); String activeChildCallId = source.readString(); Uri contactPhotoUri = source.readParcelable(classLoader, Uri.class); + UserHandle associatedUser = source.readParcelable(classLoader, UserHandle.class); + extras.putParcelable(Intent.EXTRA_USER_HANDLE, associatedUser); return new ParcelableCallBuilder() .setId(id) .setState(state) @@ -707,6 +733,7 @@ public final class ParcelableCall implements Parcelable { .setContactDisplayName(contactDisplayName) .setActiveChildCallId(activeChildCallId) .setContactPhotoUri(contactPhotoUri) + .setAssociatedUser(associatedUser) .createParcelableCall(); } @@ -757,6 +784,7 @@ public final class ParcelableCall implements Parcelable { destination.writeString(mContactDisplayName); destination.writeString(mActiveChildCallId); destination.writeParcelable(mContactPhotoUri, 0); + destination.writeParcelable(mAssociatedUser, 0); } @Override diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index c666fb7e05f1..2799f6c79215 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -382,14 +382,6 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "META + DEL -> Back", - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_DEL), - KeyGestureEvent.KEY_GESTURE_TYPE_BACK, - intArrayOf(KeyEvent.KEYCODE_DEL), - KeyEvent.META_META_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( "META + ESC -> Back", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ESCAPE), KeyGestureEvent.KEY_GESTURE_TYPE_BACK, diff --git a/tools/fonts/Android.bp b/tools/fonts/Android.bp index f8629f9bd0b8..07caa9a979d9 100644 --- a/tools/fonts/Android.bp +++ b/tools/fonts/Android.bp @@ -23,11 +23,6 @@ package { python_defaults { name: "fonts_python_defaults", - version: { - py3: { - embedded_launcher: true, - }, - }, } python_binary_host { diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp index ddacf57c3a3e..43f21221ae5a 100644 --- a/tools/lint/fix/Android.bp +++ b/tools/lint/fix/Android.bp @@ -25,11 +25,6 @@ python_binary_host { name: "lint_fix", main: "soong_lint_fix.py", srcs: ["soong_lint_fix.py"], - version: { - py3: { - embedded_launcher: true, - }, - }, } python_library_host { diff --git a/tools/lint/global/integration_tests/Android.bp b/tools/lint/global/integration_tests/Android.bp index 05ba405d2c52..f88709375c98 100644 --- a/tools/lint/global/integration_tests/Android.bp +++ b/tools/lint/global/integration_tests/Android.bp @@ -65,9 +65,4 @@ python_test_host { "AndroidGlobalLintTestNoAidl_py", "AndroidGlobalLintTestMissingAnnotation_py", ], - version: { - py3: { - embedded_launcher: true, - }, - }, } |