diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp index 46adfeba0fb0a..f7bcf1faf33ac 100644 --- a/packages/SystemUI/animation/Android.bp +++ b/packages/SystemUI/animation/Android.bp @@ -39,5 +39,5 @@ android_library { ], manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 4540b77d0a40b..c3f6a5d18b74d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -48,9 +48,16 @@ private const val TAG = "ActivityLaunchAnimator" * nicely into the starting window. */ class ActivityLaunchAnimator( - private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS) + /** The animator used when animating a View into an app. */ + private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS), + + /** The animator used when animating a Dialog into an app. */ + // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to + // TIMINGS.contentBeforeFadeOutDuration. + private val dialogToAppAnimator: LaunchAnimator = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS) ) { companion object { + /** The timings when animating a View into an app. */ @JvmField val TIMINGS = LaunchAnimator.Timings( totalDuration = 500L, @@ -60,6 +67,17 @@ class ActivityLaunchAnimator( contentAfterFadeInDuration = 183L ) + /** + * The timings when animating a Dialog into an app. We need to wait at least 200ms before + * showing the app (which is under the dialog window) so that the dialog window dim is fully + * faded out, to avoid flicker. + */ + val DIALOG_TIMINGS = TIMINGS.copy( + contentBeforeFadeOutDuration = 200L, + contentAfterFadeInDelay = 200L + ) + + /** The interpolators when animating a View or a dialog into an app. */ val INTERPOLATORS = LaunchAnimator.Interpolators( positionInterpolator = Interpolators.EMPHASIZED, positionXInterpolator = createPositionXInterpolator(), @@ -297,11 +315,18 @@ class ActivityLaunchAnimator( } } + /** + * Whether this controller is controlling a dialog launch. This will be used to adapt the + * timings, making sure we don't show the app until the dialog dim had the time to fade out. + */ + // TODO(b/218989950): Remove this. + val isDialogLaunch: Boolean + get() = false + /** * The intent was started. If [willAnimate] is false, nothing else will happen and the * animation will not be started. */ - @JvmDefault fun onIntentStarted(willAnimate: Boolean) {} /** @@ -309,7 +334,6 @@ class ActivityLaunchAnimator( * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called * before the cancellation. */ - @JvmDefault fun onLaunchAnimationCancelled() {} } @@ -317,7 +341,9 @@ class ActivityLaunchAnimator( inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() { private val launchContainer = controller.launchContainer private val context = launchContainer.context - private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer) + private val transactionApplierView = + controller.openingWindowSyncView ?: controller.launchContainer + private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView) private val matrix = Matrix() private val invertMatrix = Matrix() @@ -405,6 +431,13 @@ class ActivityLaunchAnimator( val callback = this@ActivityLaunchAnimator.callback!! val windowBackgroundColor = callback.getBackgroundColor(window.taskInfo) + // Make sure we use the modified timings when animating a dialog into an app. + val launchAnimator = if (controller.isDialogLaunch) { + dialogToAppAnimator + } else { + launchAnimator + } + // TODO(b/184121838): We should somehow get the top and bottom radius of the window // instead of recomputing isExpandingFullyAbove here. val isExpandingFullyAbove = @@ -440,19 +473,29 @@ class ActivityLaunchAnimator( progress: Float, linearProgress: Float ) { - applyStateToWindow(window, state) + // Apply the state to the window only if it is visible, i.e. when the expanding + // view is *not* visible. + if (!state.visible) { + applyStateToWindow(window, state) + } navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } + listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } delegate.onLaunchAnimationProgress(state, progress, linearProgress) } } - // We draw a hole when the additional layer is fading out to reveal the opening window. animation = launchAnimator.startAnimation( controller, endState, windowBackgroundColor, drawHole = true) } private fun applyStateToWindow(window: RemoteAnimationTarget, state: LaunchAnimator.State) { + if (transactionApplierView.viewRootImpl == null) { + // If the view root we synchronize with was detached, don't apply any transaction + // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw). + return + } + val screenBounds = window.screenSpaceBounds val centerX = (screenBounds.left + screenBounds.right) / 2f val centerY = (screenBounds.top + screenBounds.bottom) / 2f @@ -510,6 +553,12 @@ class ActivityLaunchAnimator( state: LaunchAnimator.State, linearProgress: Float ) { + if (transactionApplierView.viewRootImpl == null) { + // If the view root we synchronize with was detached, don't apply any transaction + // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw). + return + } + val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 3051d8056a89e..a3c5649e3fecf 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -19,7 +19,6 @@ package com.android.systemui.animation import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator -import android.app.ActivityManager import android.app.Dialog import android.graphics.Color import android.graphics.Rect @@ -28,12 +27,12 @@ import android.service.dreams.IDreamManager import android.util.Log import android.util.MathUtils import android.view.GhostView -import android.view.SurfaceControl import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewRootImpl +import android.view.WindowInsets import android.view.WindowManager +import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS import android.widget.FrameLayout import kotlin.math.roundToInt @@ -42,12 +41,17 @@ private const val TAG = "DialogLaunchAnimator" /** * A class that allows dialogs to be started in a seamless way from a view that is transforming * nicely into the starting dialog. + * + * This animator also allows to easily animate a dialog into an activity. + * + * @see showFromView + * @see showFromDialog + * @see createActivityLaunchController */ class DialogLaunchAnimator @JvmOverloads constructor( private val dreamManager: IDreamManager, private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS), - // TODO(b/217621394): Remove special handling for low-RAM devices after animation sync is fixed - private var forceDisableSynchronization: Boolean = ActivityManager.isLowRamDeviceStatic() + private val isForTesting: Boolean = false ) { private companion object { private val TIMINGS = ActivityLaunchAnimator.TIMINGS @@ -113,7 +117,7 @@ class DialogLaunchAnimator @JvmOverloads constructor( dialog = dialog, animateBackgroundBoundsChange, animatedParent, - forceDisableSynchronization + isForTesting ) openedDialogs.add(animatedDialog) @@ -140,6 +144,100 @@ class DialogLaunchAnimator @JvmOverloads constructor( showFromView(dialog, view, animateBackgroundBoundsChange) } + /** + * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from the + * dialog that contains [View]. Note that the dialog must have been show using [showFromView] + * and be currently showing, otherwise this will return null. + * + * The returned controller will take care of dismissing the dialog at the right time after the + * activity started, when the dialog to app animation is done (or when it is cancelled). If this + * method returns null, then the dialog won't be dismissed. + * + * @param view any view inside the dialog to animate. + */ + @JvmOverloads + fun createActivityLaunchController( + view: View, + cujType: Int? = null + ): ActivityLaunchAnimator.Controller? { + val animatedDialog = openedDialogs + .firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl } + ?: return null + + // At this point, we know that the intent of the caller is to dismiss the dialog to show + // an app, so we disable the exit animation into the touch surface because we will never + // want to run it anyways. + animatedDialog.exitAnimationDisabled = true + + val dialog = animatedDialog.dialog + + // Don't animate if the dialog is not showing. + if (!dialog.isShowing) { + return null + } + + val dialogContentWithBackground = animatedDialog.dialogContentWithBackground ?: return null + val controller = + ActivityLaunchAnimator.Controller.fromView(dialogContentWithBackground, cujType) + ?: return null + + // Wrap the controller into one that will instantly dismiss the dialog when the animation is + // done or dismiss it normally (fading it out) if the animation is cancelled. + return object : ActivityLaunchAnimator.Controller by controller { + override val isDialogLaunch = true + + override fun onIntentStarted(willAnimate: Boolean) { + controller.onIntentStarted(willAnimate) + + if (!willAnimate) { + dialog.dismiss() + } + } + + override fun onLaunchAnimationCancelled() { + controller.onLaunchAnimationCancelled() + enableDialogDismiss() + dialog.dismiss() + } + + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + controller.onLaunchAnimationStart(isExpandingFullyAbove) + + // Make sure the dialog is not dismissed during the animation. + disableDialogDismiss() + + // If this dialog was shown from a cascade of other dialogs, make sure those ones + // are dismissed too. + animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss() + + // Remove the dim. + dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + } + + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + controller.onLaunchAnimationEnd(isExpandingFullyAbove) + + // Hide the dialog then dismiss it to instantly dismiss it without playing the + // animation. + dialog.hide() + enableDialogDismiss() + dialog.dismiss() + } + + private fun disableDialogDismiss() { + dialog.setDismissOverride { /* Do nothing */ } + } + + private fun enableDialogDismiss() { + // We don't set the override to null given that [AnimatedDialog.OnDialogDismissed] + // will still properly dismiss the dialog but will also make sure to clean up + // everything (like making sure that the touched view that triggered the dialog is + // made VISIBLE again). + dialog.setDismissOverride(animatedDialog::onDialogDismissed) + } + } + } + /** * Ensure that all dialogs currently shown won't animate into their touch surface when * dismissed. @@ -358,6 +456,21 @@ private class AnimatedDialog( // Make sure the dialog is visible instantly and does not do any window animation. window.attributes.windowAnimations = R.style.Animation_LaunchAnimation + // Ensure that the animation is not clipped by the display cut-out when animating this + // dialog into an app. + window.attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + window.attributes = window.attributes + + // We apply the insets ourselves to make sure that the paddings are set on the correct + // View. + window.setDecorFitsSystemWindows(false) + val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup) + viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsets.Type.displayCutout()) + view.setPadding(insets.left, insets.top, insets.right, insets.bottom) + WindowInsets.CONSUMED + } + // Start the animation once the background view is properly laid out. dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { override fun onLayoutChange( @@ -421,45 +534,12 @@ private class AnimatedDialog( * (or inversely, removed from the UI when the touch surface is made visible). */ private fun synchronizeNextDraw(then: () -> Unit) { - if (forceDisableSynchronization || - !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null || - !decorView.isAttachedToWindow || decorView.viewRootImpl == null) { - // No need to synchronize if either the touch surface or dialog view is not attached - // to a window. + if (forceDisableSynchronization) { then() return } - // Consume the next frames of both view roots to make sure the ghost view is drawn at - // exactly the same time as when the touch surface is made invisible. - var remainingTransactions = 0 - val mergedTransactions = SurfaceControl.Transaction() - - fun onTransaction(transaction: SurfaceControl.Transaction?) { - remainingTransactions-- - transaction?.let { mergedTransactions.merge(it) } - - if (remainingTransactions == 0) { - mergedTransactions.apply() - then() - } - } - - fun consumeNextDraw(viewRootImpl: ViewRootImpl) { - if (viewRootImpl.consumeNextDraw(::onTransaction)) { - remainingTransactions++ - - // Make sure we trigger a traversal. - viewRootImpl.view.invalidate() - } - } - - consumeNextDraw(touchSurface.viewRootImpl) - consumeNextDraw(decorView.viewRootImpl) - - if (remainingTransactions == 0) { - then() - } + ViewRootSync.synchronizeNextDraw(touchSurface, decorView, then) } private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { @@ -523,7 +603,7 @@ private class AnimatedDialog( ) } - private fun onDialogDismissed() { + fun onDialogDismissed() { if (Looper.myLooper() != Looper.getMainLooper()) { dialog.context.mainExecutor.execute { onDialogDismissed() } return diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt index 77386cf2ff10a..a4c5c30252209 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt @@ -77,8 +77,8 @@ class LaunchAnimator( * This will be used to: * - Get the associated [Context]. * - Compute whether we are expanding fully above the launch container. - * - Apply surface transactions in sync with RenderThread when animating an activity - * launch. + * - Get to overlay to which we initially put the window background layer, until the + * opening window is made visible (see [openingWindowSyncView]). * * This container can be changed to force this [Controller] to animate the expanding view * inside a different location, for instance to ensure correct layering during the @@ -86,6 +86,18 @@ class LaunchAnimator( */ var launchContainer: ViewGroup + /** + * The [View] with which the opening app window should be synchronized with once it starts + * to be visible. + * + * We will also move the window background layer to this view's overlay once the opening + * window is visible. + * + * If null, this will default to [launchContainer]. + */ + val openingWindowSyncView: View? + get() = null + /** * Return the [State] of the view that will be animated. We will animate from this state to * the final window state. @@ -100,11 +112,9 @@ class LaunchAnimator( * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding * fully above the [launchContainer]. */ - @JvmDefault fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {} /** The animation made progress and the expandable view [state] should be updated. */ - @JvmDefault fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {} /** @@ -112,7 +122,6 @@ class LaunchAnimator( * called previously. This is typically used to clean up the resources initialized when the * animation was started. */ - @JvmDefault fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {} } @@ -154,7 +163,7 @@ class LaunchAnimator( } /** The timings (durations and delays) used by this animator. */ - class Timings( + data class Timings( /** The total duration of the animation. */ val totalDuration: Long, @@ -257,8 +266,17 @@ class LaunchAnimator( animator.duration = timings.totalDuration animator.interpolator = LINEAR + // Whether we should move the [windowBackgroundLayer] into the overlay of + // [Controller.openingWindowSyncView] once the opening app window starts to be visible. + val openingWindowSyncView = controller.openingWindowSyncView + val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay + val moveBackgroundLayerWhenAppIsVisible = openingWindowSyncView != null && + openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl + val launchContainerOverlay = launchContainer.overlay var cancelled = false + var movedBackgroundLayer = false + animator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?, isReverse: Boolean) { if (DEBUG) { @@ -278,6 +296,10 @@ class LaunchAnimator( } controller.onLaunchAnimationEnd(isExpandingFullyAbove) launchContainerOverlay.remove(windowBackgroundLayer) + + if (moveBackgroundLayerWhenAppIsVisible) { + openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + } } }) @@ -318,11 +340,29 @@ class LaunchAnimator( timings.contentBeforeFadeOutDuration ) < 1 + if (moveBackgroundLayerWhenAppIsVisible && !state.visible && !movedBackgroundLayer) { + // The expanding view is not visible, so the opening app is visible. If this is the + // first frame when it happens, trigger a one-off sync and move the background layer + // in its new container. + movedBackgroundLayer = true + + launchContainerOverlay.remove(windowBackgroundLayer) + openingWindowSyncViewOverlay!!.add(windowBackgroundLayer) + + ViewRootSync.synchronizeNextDraw(launchContainer, openingWindowSyncView, then = {}) + } + + val container = if (movedBackgroundLayer) { + openingWindowSyncView!! + } else { + controller.launchContainer + } + applyStateToWindowBackgroundLayer( windowBackgroundLayer, state, linearProgress, - launchContainer, + container, drawHole ) controller.onLaunchAnimationProgress(state, progress, linearProgress) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt new file mode 100644 index 0000000000000..5b3e45c9704dc --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt @@ -0,0 +1,75 @@ +package com.android.systemui.animation + +import android.app.ActivityManager +import android.view.SurfaceControl +import android.view.View +import android.view.ViewRootImpl + +/** A util class to synchronize 2 view roots. */ +// TODO(b/200284684): Remove this class. +object ViewRootSync { + // TODO(b/217621394): Remove special handling for low-RAM devices after animation sync is fixed + private val forceDisableSynchronization = ActivityManager.isLowRamDeviceStatic() + + /** + * Synchronize the next draw between the view roots of [view] and [otherView], then run [then]. + * + * Note that in some cases, the synchronization might not be possible (e.g. WM consumed the + * next transactions) or disabled (temporarily, on low ram devices). In this case, [then] will + * be called without synchronizing. + */ + fun synchronizeNextDraw( + view: View, + otherView: View, + then: () -> Unit + ) { + if (forceDisableSynchronization || + !view.isAttachedToWindow || view.viewRootImpl == null || + !otherView.isAttachedToWindow || otherView.viewRootImpl == null || + view.viewRootImpl == otherView.viewRootImpl) { + // No need to synchronize if either the touch surface or dialog view is not attached + // to a window. + then() + return + } + + // Consume the next frames of both view roots to make sure the ghost view is drawn at + // exactly the same time as when the touch surface is made invisible. + var remainingTransactions = 0 + val mergedTransactions = SurfaceControl.Transaction() + + fun onTransaction(transaction: SurfaceControl.Transaction?) { + remainingTransactions-- + transaction?.let { mergedTransactions.merge(it) } + + if (remainingTransactions == 0) { + mergedTransactions.apply() + then() + } + } + + fun consumeNextDraw(viewRootImpl: ViewRootImpl) { + if (viewRootImpl.consumeNextDraw(::onTransaction)) { + remainingTransactions++ + + // Make sure we trigger a traversal. + viewRootImpl.view.invalidate() + } + } + + consumeNextDraw(view.viewRootImpl) + consumeNextDraw(otherView.viewRootImpl) + + if (remainingTransactions == 0) { + then() + } + } + + /** + * A Java-friendly API for [synchronizeNextDraw]. + */ + @JvmStatic + fun synchronizeNextDraw(view: View, otherView: View, then: Runnable) { + synchronizeNextDraw(view, otherView, then::run) + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 5b29719d109c9..36ecb84e29102 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -204,7 +204,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { d.setColorFilter(new PorterDuffColorFilter( Utils.getColorAccentDefaultColor(mContext), PorterDuff.Mode.SRC_IN)); mTitleIcon.setImageDrawable(d); - mContainerLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW)); + mContainerLayout.setOnClickListener(mController::launchBluetoothPairing); } } @@ -241,11 +241,5 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { notifyDataSetChanged(); } } - - private void onItemClick(int customizedItem) { - if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { - mController.launchBluetoothPairing(); - } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 1c6b7dc222bb4..da27f45f4c710 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -56,6 +56,7 @@ import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; @@ -533,12 +534,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { return false; } - void launchBluetoothPairing() { - // Dismissing a dialog into its touch surface and starting an activity at the same time - // looks bad, so let's make sure the dialog just fades out quickly. - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); + void launchBluetoothPairing(View view) { + ActivityLaunchAnimator.Controller controller = + mDialogLaunchAnimator.createActivityLaunchController(view); + + if (controller == null) { + mCallback.dismissDialog(); + } - mCallback.dismissDialog(); Intent launchIntent = new Intent(ACTION_BLUETOOTH_PAIRING_SETTINGS) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -553,10 +556,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { deepLinkIntent.putExtra( Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, PAGE_CONNECTED_DEVICES_KEY); - mActivityStarter.startActivity(deepLinkIntent, true); + mActivityStarter.startActivity(deepLinkIntent, true, controller); return; } - mActivityStarter.startActivity(launchIntent, true); + mActivityStarter.startActivity(launchIntent, true, controller); } void launchMediaOutputGroupDialog(View mediaOutputDialog) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index e8d27eccc8232..bcc02b497f00e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -36,6 +36,7 @@ import com.android.internal.app.MediaRouteDialogPresenter; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -191,10 +192,16 @@ public class CastTile extends QSTileImpl { mContext, ROUTE_TYPE_REMOTE_DISPLAY, v -> { - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); - holder.mDialog.dismiss(); + ActivityLaunchAnimator.Controller controller = + mDialogLaunchAnimator.createActivityLaunchController(v); + + if (controller == null) { + holder.mDialog.dismiss(); + } + mActivityStarter - .postStartActivityDismissingKeyguard(getLongClickIntent(), 0); + .postStartActivityDismissingKeyguard(getLongClickIntent(), 0, + controller); }); holder.init(dialog); SystemUIDialog.setShowForAllUsers(dialog, true); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java index 4fe155cfaeb97..e1d20706c625b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -164,7 +164,7 @@ public class InternetAdapter extends RecyclerView.Adapter mInternetDialogController.launchWifiNetworkDetailsSetting( - wifiEntry.getKey())); + wifiEntry.getKey(), v)); return; } mWifiListLayout.setOnClickListener(v -> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 8b6ddb44460b6..d1c784457c9fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -355,8 +355,8 @@ public class InternetDialog extends SystemUIDialog implements isChecked, false); } }); - mConnectedWifListLayout.setOnClickListener(v -> onClickConnectedWifi()); - mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton()); + mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi); + mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton); mWiFiToggle.setOnCheckedChangeListener( (buttonView, isChecked) -> { if (mWifiManager == null) return; @@ -519,7 +519,7 @@ public class InternetDialog extends SystemUIDialog implements if (TextUtils.isEmpty(mWifiScanNotifyText.getText())) { final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo( AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION, - v -> mInternetDialogController.launchWifiScanningSetting()); + mInternetDialogController::launchWifiScanningSetting); mWifiScanNotifyText.setText(AnnotationLinkSpan.linkify( getContext().getText(R.string.wifi_scan_notify_message), linkInfo)); mWifiScanNotifyText.setMovementMethod(LinkMovementMethod.getInstance()); @@ -527,15 +527,16 @@ public class InternetDialog extends SystemUIDialog implements mWifiScanNotifyLayout.setVisibility(View.VISIBLE); } - void onClickConnectedWifi() { + void onClickConnectedWifi(View view) { if (mConnectedWifiEntry == null) { return; } - mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey()); + mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey(), + view); } - void onClickSeeMoreButton() { - mInternetDialogController.launchNetworkSetting(); + void onClickSeeMoreButton(View view) { + mInternetDialogController.launchNetworkSetting(view); } CharSequence getDialogTitleText() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index f89b7a3c09719..b3bc3be852fb4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -72,6 +72,7 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.settingslib.wifi.WifiUtils; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; @@ -620,36 +621,32 @@ public class InternetDialogController implements AccessPointController.AccessPoi return summary; } - void launchNetworkSetting() { - // Dismissing a dialog into its touch surface and starting an activity at the same time - // looks bad, so let's make sure the dialog just fades out quickly. - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); - mCallback.dismissDialog(); + private void startActivity(Intent intent, View view) { + ActivityLaunchAnimator.Controller controller = + mDialogLaunchAnimator.createActivityLaunchController(view); - mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0); + if (controller == null) { + mCallback.dismissDialog(); + } + + mActivityStarter.postStartActivityDismissingKeyguard(intent, 0, controller); } - void launchWifiNetworkDetailsSetting(String key) { + void launchNetworkSetting(View view) { + startActivity(getSettingsIntent(), view); + } + + void launchWifiNetworkDetailsSetting(String key, View view) { Intent intent = getWifiDetailsSettingsIntent(key); if (intent != null) { - // Dismissing a dialog into its touch surface and starting an activity at the same time - // looks bad, so let's make sure the dialog just fades out quickly. - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); - mCallback.dismissDialog(); - - mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + startActivity(intent, view); } } - void launchWifiScanningSetting() { - // Dismissing a dialog into its touch surface and starting an activity at the same time - // looks bad, so let's make sure the dialog just fades out quickly. - mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); - mCallback.dismissDialog(); - + void launchWifiScanningSetting(View view) { final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + startActivity(intent, view); } void connectCarrierNetwork() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt index 8c8c5c8c00972..88aa734df2b3a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.user import android.app.Dialog import android.content.Context import android.content.DialogInterface +import android.content.DialogInterface.BUTTON_NEUTRAL import android.content.Intent import android.provider.Settings import android.view.LayoutInflater @@ -84,16 +85,20 @@ class UserSwitchDialogController @VisibleForTesting constructor( setPositiveButton(R.string.quick_settings_done) { _, _ -> uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE) } - setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ -> + setNeutralButton(R.string.quick_settings_more_user_settings, { _, _ -> if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS) + val controller = dialogLaunchAnimator.createActivityLaunchController( + getButton(BUTTON_NEUTRAL)) + + if (controller == null) { + dismiss() + } + activityStarter.postStartActivityDismissingKeyguard( - USER_SETTINGS_INTENT, - 0 - ) + USER_SETTINGS_INTENT, 0, controller) } - } + }, false /* dismissOnClick */) val gridFrame = LayoutInflater.from(this.context) .inflate(R.layout.qs_user_dialog_content, null) setView(gridFrame) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index f13334e55555e..484b7ee7cb090 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -2580,9 +2580,9 @@ public class StatusBar extends CoreStartable implements return controllerFromStatusBar.get(); } - if (dismissShade && rootView == mNotificationShadeWindowView) { - // We are animating a view in the shade. We have to make sure that we collapse it when - // the animation ends or is cancelled. + if (dismissShade) { + // If the view is not in the status bar, then we are animating a view in the shade. + // We have to make sure that we collapse it when the animation ends or is cancelled. return new StatusBarLaunchAnimatorController(animationController, this, true /* isLaunchForActivity */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index 2ba37c2ec29f9..09fca100749c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -1,5 +1,6 @@ package com.android.systemui.statusbar.phone +import android.view.View import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.LaunchAnimator @@ -12,6 +13,11 @@ class StatusBarLaunchAnimatorController( private val statusBar: StatusBar, private val isLaunchForActivity: Boolean = true ) : ActivityLaunchAnimator.Controller by delegate { + // Always sync the opening window with the shade, given that we draw a hole punch in the shade + // of the same size and position as the opening app to make it visible. + override val openingWindowSyncView: View? + get() = statusBar.notificationShadeWindowView + override fun onIntentStarted(willAnimate: Boolean) { delegate.onIntentStarted(willAnimate) if (!willAnimate) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 6e1ec9ce703ac..97225284f2085 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -44,6 +44,9 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; +import java.util.ArrayList; +import java.util.List; + /** * Base class for dialogs that should appear over panels and keyguard. * @@ -68,6 +71,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh private int mLastConfigurationWidthDp = -1; private int mLastConfigurationHeightDp = -1; + private List mOnCreateRunnables = new ArrayList<>(); + public SystemUIDialog(Context context) { this(context, R.style.Theme_SystemUI_Dialog); } @@ -110,6 +115,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mLastConfigurationWidthDp = config.screenWidthDp; mLastConfigurationHeightDp = config.screenHeightDp; updateWindowSize(); + + for (int i = 0; i < mOnCreateRunnables.size(); i++) { + mOnCreateRunnables.get(i).run(); + } } private void updateWindowSize() { @@ -197,16 +206,67 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh setMessage(mContext.getString(resId)); } + /** + * Set a listener to be invoked when the positive button of the dialog is pressed. The dialog + * will automatically be dismissed when the button is clicked. + */ public void setPositiveButton(int resId, OnClickListener onClick) { - setButton(BUTTON_POSITIVE, mContext.getString(resId), onClick); + setPositiveButton(resId, onClick, true /* dismissOnClick */); } + /** + * Set a listener to be invoked when the positive button of the dialog is pressed. The dialog + * will be dismissed when the button is clicked iff {@code dismissOnClick} is true. + */ + public void setPositiveButton(int resId, OnClickListener onClick, boolean dismissOnClick) { + setButton(BUTTON_POSITIVE, resId, onClick, dismissOnClick); + } + + /** + * Set a listener to be invoked when the negative button of the dialog is pressed. The dialog + * will automatically be dismissed when the button is clicked. + */ public void setNegativeButton(int resId, OnClickListener onClick) { - setButton(BUTTON_NEGATIVE, mContext.getString(resId), onClick); + setNegativeButton(resId, onClick, true /* dismissOnClick */); } + /** + * Set a listener to be invoked when the negative button of the dialog is pressed. The dialog + * will be dismissed when the button is clicked iff {@code dismissOnClick} is true. + */ + public void setNegativeButton(int resId, OnClickListener onClick, boolean dismissOnClick) { + setButton(BUTTON_NEGATIVE, resId, onClick, dismissOnClick); + } + + /** + * Set a listener to be invoked when the neutral button of the dialog is pressed. The dialog + * will automatically be dismissed when the button is clicked. + */ public void setNeutralButton(int resId, OnClickListener onClick) { - setButton(BUTTON_NEUTRAL, mContext.getString(resId), onClick); + setNeutralButton(resId, onClick, true /* dismissOnClick */); + } + + /** + * Set a listener to be invoked when the neutral button of the dialog is pressed. The dialog + * will be dismissed when the button is clicked iff {@code dismissOnClick} is true. + */ + public void setNeutralButton(int resId, OnClickListener onClick, boolean dismissOnClick) { + setButton(BUTTON_NEUTRAL, resId, onClick, dismissOnClick); + } + + private void setButton(int whichButton, int resId, OnClickListener onClick, + boolean dismissOnClick) { + if (dismissOnClick) { + setButton(whichButton, mContext.getString(resId), onClick); + } else { + // Set a null OnClickListener to make sure the button is still created and shown. + setButton(whichButton, mContext.getString(resId), (OnClickListener) null); + + // When the dialog is created, set the click listener but don't dismiss the dialog when + // it is clicked. + mOnCreateRunnables.add(() -> getButton(whichButton).setOnClickListener( + view -> onClick.onClick(this, whichButton))); + } } public static void setShowForAllUsers(Dialog dialog, boolean show) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index 589eeb5e07614..d5df9fe0c2e84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -46,7 +46,7 @@ import kotlin.concurrent.thread @RunWithLooper class ActivityLaunchAnimatorTest : SysuiTestCase() { private val launchContainer = LinearLayout(mContext) - private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) + private val testLaunchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) @Mock lateinit var callback: ActivityLaunchAnimator.Callback @Mock lateinit var listener: ActivityLaunchAnimator.Listener @Spy private val controller = TestLaunchAnimatorController(launchContainer) @@ -58,7 +58,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { @Before fun setup() { - activityLaunchAnimator = ActivityLaunchAnimator(launchAnimator) + activityLaunchAnimator = ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator) activityLaunchAnimator.callback = callback activityLaunchAnimator.addListener(listener) } @@ -129,13 +129,11 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { @Test fun animatesIfActivityIsAlreadyOpenAndIsOnKeyguard() { `when`(callback.isOnKeyguard()).thenReturn(true) - val animator = ActivityLaunchAnimator(launchAnimator) - animator.callback = callback val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java) var animationAdapter: RemoteAnimationAdapter? = null - startIntentWithAnimation(animator) { adapter -> + startIntentWithAnimation(activityLaunchAnimator) { adapter -> animationAdapter = adapter ActivityManager.START_DELIVERED_TO_TOP } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index 61e78f5cb2fce..c84729fcf2e21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -20,6 +20,7 @@ import com.android.systemui.SysuiTestCase import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertNotNull +import junit.framework.Assert.assertNull import junit.framework.Assert.assertTrue import org.junit.After import org.junit.Before @@ -43,7 +44,7 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { @Before fun setUp() { dialogLaunchAnimator = DialogLaunchAnimator( - dreamManager, launchAnimator, forceDisableSynchronization = true) + dreamManager, launchAnimator, isForTesting = true) } @After @@ -92,11 +93,6 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { // Clicking the transparent background should dismiss the dialog. runOnMainThreadAndWaitForIdleSync { - // TODO(b/204561691): Remove this call to disableAllCurrentDialogsExitAnimations() and - // make sure that the test still pass on git_master/cf_x86_64_phone-userdebug in - // Forrest. - dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() - transparentBackground.performClick() } assertFalse(dialog.isShowing) @@ -110,7 +106,6 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertTrue(firstDialog.isShowing) assertTrue(secondDialog.isShowing) runOnMainThreadAndWaitForIdleSync { - dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() dialogLaunchAnimator.dismissStack(secondDialog) } @@ -118,6 +113,38 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertFalse(secondDialog.isShowing) } + @Test + fun testActivityLaunchControllerFromDialog() { + val firstDialog = createAndShowDialog() + val secondDialog = createDialogAndShowFromDialog(firstDialog) + + val controller = + dialogLaunchAnimator.createActivityLaunchController(secondDialog.contentView)!! + + // The dialog shouldn't be dismissable during the animation. + runOnMainThreadAndWaitForIdleSync { + controller.onLaunchAnimationStart(isExpandingFullyAbove = true) + secondDialog.dismiss() + } + assertTrue(secondDialog.isShowing) + + // Both dialogs should be dismissed at the end of the animation. + runOnMainThreadAndWaitForIdleSync { + controller.onLaunchAnimationEnd(isExpandingFullyAbove = true) + } + assertFalse(firstDialog.isShowing) + assertFalse(secondDialog.isShowing) + } + + @Test + fun testActivityLaunchFromHiddenDialog() { + val dialog = createAndShowDialog() + runOnMainThreadAndWaitForIdleSync { + dialog.hide() + } + assertNull(dialogLaunchAnimator.createActivityLaunchController(dialog.contentView)) + } + private fun createAndShowDialog(): TestDialog { return runOnMainThreadAndWaitForIdleSync { val touchSurfaceRoot = LinearLayout(context) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 421ae034c0542..11326e76b25e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -59,6 +59,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { private MediaDevice mMediaDevice2 = mock(MediaDevice.class); private Icon mIcon = mock(Icon.class); private IconCompat mIconCompat = mock(IconCompat.class); + private View mDialogLaunchView = mock(View.class); private MediaOutputAdapter mMediaOutputAdapter; private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder; @@ -245,7 +246,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2); mViewHolder.mContainerLayout.performClick(); - verify(mMediaOutputController).launchBluetoothPairing(); + verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 0d6554103dac0..a2959e2fb917a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -136,6 +136,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { private LocationController mLocationController; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; + @Mock + private View mDialogLaunchView; private TestableResources mTestableResources; private InternetDialogController mInternetDialogController; @@ -384,7 +386,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void launchWifiNetworkDetailsSetting_withNoWifiEntryKey_doNothing() { - mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */); + mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */, + mDialogLaunchView); verify(mActivityStarter, never()) .postStartActivityDismissingKeyguard(any(Intent.class), anyInt()); @@ -392,9 +395,11 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void launchWifiNetworkDetailsSetting_withWifiEntryKey_startActivity() { - mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key"); + mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key", + mDialogLaunchView); - verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt()); + verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt(), + any()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index ed35dcbbcfab2..cf97bdae9af29 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -458,7 +458,8 @@ public class InternetDialogTest extends SysuiTestCase { public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() { mSeeAll.performClick(); - verify(mInternetDialogController).launchNetworkSetting(); + verify(mInternetDialogController).launchNetworkSetting( + mDialogView.requireViewById(R.id.see_all_layout)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt index 8695b2990b6a5..030c65a0576a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.provider.Settings import android.testing.AndroidTestingRunner import android.view.View +import android.widget.Button import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase @@ -63,6 +64,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { @Mock private lateinit var launchView: View @Mock + private lateinit var neutralButton: Button + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator @Mock private lateinit var uiEventLogger: UiEventLogger @@ -130,14 +133,17 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { controller.showDialog(launchView) - verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor)) + verify(dialog) + .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */) + `when`(dialog.getButton(DialogInterface.BUTTON_NEUTRAL)).thenReturn(neutralButton) clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL) verify(activityStarter) .postStartActivityDismissingKeyguard( argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)), - eq(0) + eq(0), + eq(null) ) verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS) } @@ -148,7 +154,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { controller.showDialog(launchView) - verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor)) + verify(dialog) + .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */) clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)