Use Expandable instead of View in FooterActionsViewModel (1/2)

This CL replaces the reference to View by Expandable in
FooterActionsViewModel. That way, this view model can now be used by
Compose code as well.

Bug: 242040009
Test: atest FooterActionsViewModelTest
Change-Id: I6674d9a39d5daf699d67f1bb8e8ae317c85e9240
This commit is contained in:
Jordan Demeulenaere
2022-10-19 16:54:30 +02:00
parent 5d83ecce88
commit e61069746b
26 changed files with 384 additions and 278 deletions

View File

@@ -25,7 +25,6 @@ import android.graphics.Rect
import android.os.Looper
import android.util.Log
import android.util.MathUtils
import android.view.GhostView
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@@ -86,6 +85,9 @@ constructor(
*/
val sourceIdentity: Any
/** The CUJ associated to this controller. */
val cuj: DialogCuj?
/**
* Move the drawing of the source in the overlay of [viewGroup].
*
@@ -142,7 +144,31 @@ constructor(
* controlled by this controller.
*/
// TODO(b/252723237): Make this non-nullable
fun jankConfigurationBuilder(cuj: Int): InteractionJankMonitor.Configuration.Builder?
fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder?
companion object {
/**
* Create a [Controller] that can animate [source] to and from a dialog.
*
* Important: The view must be attached to a [ViewGroup] when calling this function and
* during the animation. For safety, this method will return null when it is not.
*
* Note: The background of [view] should be a (rounded) rectangle so that it can be
* properly animated.
*/
fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
if (source.parent !is ViewGroup) {
Log.e(
TAG,
"Skipping animation as view $source is not attached to a ViewGroup",
Exception(),
)
return null
}
return ViewDialogLaunchAnimatorController(source, cuj)
}
}
}
/**
@@ -172,7 +198,12 @@ constructor(
cuj: DialogCuj? = null,
animateBackgroundBoundsChange: Boolean = false
) {
show(dialog, createController(view), cuj, animateBackgroundBoundsChange)
val controller = Controller.fromView(view, cuj)
if (controller == null) {
dialog.show()
} else {
show(dialog, controller, animateBackgroundBoundsChange)
}
}
/**
@@ -187,10 +218,10 @@ constructor(
* Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
* made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
*/
@JvmOverloads
fun show(
dialog: Dialog,
controller: Controller,
cuj: DialogCuj? = null,
animateBackgroundBoundsChange: Boolean = false
) {
if (Looper.myLooper() != Looper.getMainLooper()) {
@@ -207,7 +238,10 @@ constructor(
it.dialog.window.decorView.viewRootImpl == controller.viewRoot
}
val animateFrom =
animatedParent?.dialogContentWithBackground?.let { createController(it) } ?: controller
animatedParent?.dialogContentWithBackground?.let {
Controller.fromView(it, controller.cuj)
}
?: controller
if (animatedParent == null && animateFrom !is LaunchableView) {
// Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -244,96 +278,12 @@ constructor(
animateBackgroundBoundsChange,
animatedParent,
isForTesting,
cuj,
)
openedDialogs.add(animatedDialog)
animatedDialog.start()
}
/** Create a [Controller] that can animate [source] to & from a dialog. */
private fun createController(source: View): Controller {
return object : Controller {
override val viewRoot: ViewRootImpl
get() = source.viewRootImpl
override val sourceIdentity: Any = source
override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
// Create a temporary ghost of the source (which will make it invisible) and add it
// to the host dialog.
GhostView.addGhost(source, viewGroup)
// The ghost of the source was just created, so the source is currently invisible.
// We need to make sure that it stays invisible as long as the dialog is shown or
// animating.
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
}
override fun stopDrawingInOverlay() {
// Note: here we should remove the ghost from the overlay, but in practice this is
// already done by the launch controllers created below.
// Make sure we allow the source to change its visibility again.
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
source.visibility = View.VISIBLE
}
override fun createLaunchController(): LaunchAnimator.Controller {
val delegate = GhostedViewLaunchAnimatorController(source)
return object : LaunchAnimator.Controller by delegate {
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
// Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
// ghost (that ghosts only the source content, and not its background) will
// be added right after this by the delegate and will be animated.
GhostView.removeGhost(source)
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
// We hide the source when the dialog is showing. We will make this view
// visible again when dismissing the dialog. This does nothing if the source
// implements [LaunchableView], as it's already INVISIBLE in that case.
source.visibility = View.INVISIBLE
}
}
}
override fun createExitController(): LaunchAnimator.Controller {
return GhostedViewLaunchAnimatorController(source)
}
override fun shouldAnimateExit(): Boolean {
// The source should be invisible by now, if it's not then something else changed
// its visibility and we probably don't want to run the animation.
if (source.visibility != View.INVISIBLE) {
return false
}
return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
}
override fun onExitAnimationCancelled() {
// Make sure we allow the source to change its visibility again.
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
// If the view is invisible it's probably because of us, so we make it visible
// again.
if (source.visibility == View.INVISIBLE) {
source.visibility = View.VISIBLE
}
}
override fun jankConfigurationBuilder(
cuj: Int
): InteractionJankMonitor.Configuration.Builder? {
return InteractionJankMonitor.Configuration.Builder.withView(cuj, source)
}
}
}
/**
* Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will
* allow for dismissing the whole stack.
@@ -563,9 +513,6 @@ private class AnimatedDialog(
* Whether synchronization should be disabled, which can be useful if we are running in a test.
*/
private val forceDisableSynchronization: Boolean,
/** Interaction to which the dialog animation is associated. */
private val cuj: DialogCuj? = null
) {
/**
* The DecorView of this dialog window.
@@ -618,8 +565,9 @@ private class AnimatedDialog(
private var hasInstrumentedJank = false
fun start() {
val cuj = controller.cuj
if (cuj != null) {
val config = controller.jankConfigurationBuilder(cuj.cujType)
val config = controller.jankConfigurationBuilder()
if (config != null) {
if (cuj.tag != null) {
config.setTag(cuj.tag)
@@ -917,7 +865,7 @@ private class AnimatedDialog(
}
if (hasInstrumentedJank) {
interactionJankMonitor.end(cuj!!.cujType)
interactionJankMonitor.end(controller.cuj!!.cujType)
}
}
)

View File

@@ -30,7 +30,12 @@ interface Expandable {
*/
fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller?
// TODO(b/230830644): Introduce DialogLaunchAnimator and a function to expose it here.
/**
* Create a [DialogLaunchAnimator.Controller] that can be used to expand this [Expandable] into
* a Dialog, or return `null` if this [Expandable] should not be animated (e.g. if it is
* currently not attached or visible).
*/
fun dialogLaunchController(cuj: DialogCuj? = null): DialogLaunchAnimator.Controller?
companion object {
/**
@@ -39,6 +44,7 @@ interface Expandable {
* Note: The background of [view] should be a (rounded) rectangle so that it can be properly
* animated.
*/
@JvmStatic
fun fromView(view: View): Expandable {
return object : Expandable {
override fun activityLaunchController(
@@ -46,6 +52,12 @@ interface Expandable {
): ActivityLaunchAnimator.Controller? {
return ActivityLaunchAnimator.Controller.fromView(view, cujType)
}
override fun dialogLaunchController(
cuj: DialogCuj?
): DialogLaunchAnimator.Controller? {
return DialogLaunchAnimator.Controller.fromView(view, cuj)
}
}
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2022 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.animation
import android.view.GhostView
import android.view.View
import android.view.ViewGroup
import android.view.ViewRootImpl
import com.android.internal.jank.InteractionJankMonitor
/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
class ViewDialogLaunchAnimatorController
internal constructor(
private val source: View,
override val cuj: DialogCuj?,
) : DialogLaunchAnimator.Controller {
override val viewRoot: ViewRootImpl
get() = source.viewRootImpl
override val sourceIdentity: Any = source
override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
// Create a temporary ghost of the source (which will make it invisible) and add it
// to the host dialog.
GhostView.addGhost(source, viewGroup)
// The ghost of the source was just created, so the source is currently invisible.
// We need to make sure that it stays invisible as long as the dialog is shown or
// animating.
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
}
override fun stopDrawingInOverlay() {
// Note: here we should remove the ghost from the overlay, but in practice this is
// already done by the launch controllers created below.
// Make sure we allow the source to change its visibility again.
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
source.visibility = View.VISIBLE
}
override fun createLaunchController(): LaunchAnimator.Controller {
val delegate = GhostedViewLaunchAnimatorController(source)
return object : LaunchAnimator.Controller by delegate {
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
// Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
// ghost (that ghosts only the source content, and not its background) will
// be added right after this by the delegate and will be animated.
GhostView.removeGhost(source)
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
// We hide the source when the dialog is showing. We will make this view
// visible again when dismissing the dialog. This does nothing if the source
// implements [LaunchableView], as it's already INVISIBLE in that case.
source.visibility = View.INVISIBLE
}
}
}
override fun createExitController(): LaunchAnimator.Controller {
return GhostedViewLaunchAnimatorController(source)
}
override fun shouldAnimateExit(): Boolean {
// The source should be invisible by now, if it's not then something else changed
// its visibility and we probably don't want to run the animation.
if (source.visibility != View.INVISIBLE) {
return false
}
return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
}
override fun onExitAnimationCancelled() {
// Make sure we allow the source to change its visibility again.
(source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
// If the view is invisible it's probably because of us, so we make it visible
// again.
if (source.visibility == View.INVISIBLE) {
source.visibility = View.VISIBLE
}
}
override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
val type = cuj?.cujType ?: return null
return InteractionJankMonitor.Configuration.Builder.withView(type, source)
}
}

View File

@@ -40,17 +40,16 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.roundToInt
/** A controller that can control animated launches. */
/** A controller that can control animated launches from an [Expandable]. */
interface ExpandableController {
/** Create an [ActivityLaunchAnimator.Controller] to animate into an Activity. */
fun forActivity(): ActivityLaunchAnimator.Controller
/** Create a [DialogLaunchAnimator.Controller] to animate into a Dialog. */
fun forDialog(): DialogLaunchAnimator.Controller
/** The [Expandable] controlled by this controller. */
val expandable: Expandable
}
/**
@@ -120,13 +119,26 @@ internal class ExpandableControllerImpl(
private val layoutDirection: LayoutDirection,
private val isComposed: State<Boolean>,
) : ExpandableController {
override fun forActivity(): ActivityLaunchAnimator.Controller {
return activityController()
}
override val expandable: Expandable =
object : Expandable {
override fun activityLaunchController(
cujType: Int?,
): ActivityLaunchAnimator.Controller? {
if (!isComposed.value) {
return null
}
override fun forDialog(): DialogLaunchAnimator.Controller {
return dialogController()
}
return activityController(cujType)
}
override fun dialogLaunchController(cuj: DialogCuj?): DialogLaunchAnimator.Controller? {
if (!isComposed.value) {
return null
}
return dialogController(cuj)
}
}
/**
* Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
@@ -233,7 +245,7 @@ internal class ExpandableControllerImpl(
}
/** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
private fun activityController(): ActivityLaunchAnimator.Controller {
private fun activityController(cujType: Int?): ActivityLaunchAnimator.Controller {
val delegate = launchController()
return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -248,10 +260,11 @@ internal class ExpandableControllerImpl(
}
}
private fun dialogController(): DialogLaunchAnimator.Controller {
private fun dialogController(cuj: DialogCuj?): DialogLaunchAnimator.Controller {
return object : DialogLaunchAnimator.Controller {
override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
override val sourceIdentity: Any = this@ExpandableControllerImpl
override val cuj: DialogCuj? = cuj
override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
val newOverlay = viewGroup.overlay as ViewGroupOverlay
@@ -294,9 +307,7 @@ internal class ExpandableControllerImpl(
isDialogShowing.value = false
}
override fun jankConfigurationBuilder(
cuj: Int
): InteractionJankMonitor.Configuration.Builder? {
override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
// TODO(b/252723237): Add support for jank monitoring when animating from a
// Composable.
return null

View File

@@ -116,6 +116,7 @@ import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.animation.Expandable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -448,10 +449,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
*
* @param keyguardShowing True if keyguard is showing
* @param isDeviceProvisioned True if device is provisioned
* @param view The view from which we should animate the dialog when showing it
* @param expandable The expandable from which we should animate the dialog when
* showing it
*/
public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
@Nullable View view) {
@Nullable Expandable expandable) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
if (mDialog != null && mDialog.isShowing()) {
@@ -463,7 +465,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mDialog.dismiss();
mDialog = null;
} else {
handleShow(view);
handleShow(expandable);
}
}
@@ -495,7 +497,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
}
}
protected void handleShow(@Nullable View view) {
protected void handleShow(@Nullable Expandable expandable) {
awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
@@ -507,10 +509,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
// Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
if (view != null) {
mDialogLaunchAnimator.showFromView(mDialog, view,
new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG));
DialogLaunchAnimator.Controller controller =
expandable != null ? expandable.dialogLaunchController(
new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG)) : null;
if (controller != null) {
mDialogLaunchAnimator.show(mDialog, controller);
} else {
mDialog.show();
}

View File

@@ -52,6 +52,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -98,10 +99,10 @@ interface FgsManagerController {
fun init()
/**
* Show the foreground services dialog. The dialog will be expanded from [viewLaunchedFrom] if
* Show the foreground services dialog. The dialog will be expanded from [expandable] if
* it's not `null`.
*/
fun showDialog(viewLaunchedFrom: View?)
fun showDialog(expandable: Expandable?)
/** Add a [OnNumberOfPackagesChangedListener]. */
fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener)
@@ -367,7 +368,7 @@ class FgsManagerControllerImpl @Inject constructor(
override fun shouldUpdateFooterVisibility() = dialog == null
override fun showDialog(viewLaunchedFrom: View?) {
override fun showDialog(expandable: Expandable?) {
synchronized(lock) {
if (dialog == null) {
@@ -403,16 +404,18 @@ class FgsManagerControllerImpl @Inject constructor(
}
mainExecutor.execute {
viewLaunchedFrom
?.let {
dialogLaunchAnimator.showFromView(
dialog, it,
cuj = DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG
)
val controller =
expandable?.dialogLaunchController(
DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG,
)
} ?: dialog.show()
)
if (controller != null) {
dialogLaunchAnimator.show(dialog, controller)
} else {
dialog.show()
}
}
backgroundExecutor.execute {

View File

@@ -32,6 +32,7 @@ import com.android.internal.logging.nano.MetricsProto
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -156,7 +157,7 @@ internal class FooterActionsController @Inject constructor(
startSettingsActivity()
} else if (v === powerMenuLite) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
globalActionsDialog?.showOrHideDialog(false, true, v)
globalActionsDialog?.showOrHideDialog(false, true, Expandable.fromView(powerMenuLite))
}
}

View File

@@ -29,6 +29,7 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.systemui.R;
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.dagger.QSScope;
@@ -130,7 +131,7 @@ public class QSFgsManagerFooter implements View.OnClickListener,
@Override
public void onClick(View view) {
mFgsManagerController.showDialog(mRootView);
mFgsManagerController.showDialog(Expandable.fromView(view));
}
public void refreshState() {

View File

@@ -39,6 +39,7 @@ import androidx.annotation.Nullable;
import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.common.shared.model.Icon;
import com.android.systemui.dagger.qualifiers.Background;
@@ -169,7 +170,7 @@ public class QSSecurityFooter extends ViewController<View>
// TODO(b/242040009): Remove this.
public void showDeviceMonitoringDialog() {
mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, mView);
mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, Expandable.fromView(mView));
}
public void refreshState() {

View File

@@ -75,6 +75,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.R;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.animation.Expandable;
import com.android.systemui.common.shared.model.ContentDescription;
import com.android.systemui.common.shared.model.Icon;
import com.android.systemui.dagger.SysUISingleton;
@@ -190,8 +191,9 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
}
/** Show the device monitoring dialog. */
public void showDeviceMonitoringDialog(Context quickSettingsContext, @Nullable View view) {
createDialog(quickSettingsContext, view);
public void showDeviceMonitoringDialog(Context quickSettingsContext,
@Nullable Expandable expandable) {
createDialog(quickSettingsContext, expandable);
}
/**
@@ -440,7 +442,7 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
}
}
private void createDialog(Context quickSettingsContext, @Nullable View view) {
private void createDialog(Context quickSettingsContext, @Nullable Expandable expandable) {
mShouldUseSettingsButton.set(false);
mBgHandler.post(() -> {
String settingsButtonText = getSettingsButton();
@@ -453,9 +455,12 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
? settingsButtonText : getNegativeButton(), this);
mDialog.setView(dialogView);
if (view != null && view.isAggregatedVisible()) {
mDialogLaunchAnimator.showFromView(mDialog, view, new DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG));
DialogLaunchAnimator.Controller controller =
expandable != null ? expandable.dialogLaunchController(new DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG))
: null;
if (controller != null) {
mDialogLaunchAnimator.show(mDialog, controller);
} else {
mDialog.show();
}

View File

@@ -23,13 +23,11 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import android.provider.Settings
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.internal.logging.nano.MetricsProto
import com.android.internal.util.FrameworkStatsLog
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
@@ -74,37 +72,27 @@ interface FooterActionsInteractor {
val deviceMonitoringDialogRequests: Flow<Unit>
/**
* Show the device monitoring dialog, expanded from [view].
*
* Important: [view] must be associated to the same [Context] as the [Quick Settings fragment]
* [com.android.systemui.qs.QSFragment].
*/
// TODO(b/230830644): Replace view by Expandable interface.
fun showDeviceMonitoringDialog(view: View)
/**
* Show the device monitoring dialog.
* Show the device monitoring dialog, expanded from [expandable] if it's not null.
*
* Important: [quickSettingsContext] *must* be the [Context] associated to the [Quick Settings
* fragment][com.android.systemui.qs.QSFragment].
*/
// TODO(b/230830644): Replace view by Expandable interface.
fun showDeviceMonitoringDialog(quickSettingsContext: Context)
fun showDeviceMonitoringDialog(quickSettingsContext: Context, expandable: Expandable?)
/** Show the foreground services dialog. */
// TODO(b/230830644): Replace view by Expandable interface.
fun showForegroundServicesDialog(view: View)
fun showForegroundServicesDialog(expandable: Expandable)
/** Show the power menu dialog. */
// TODO(b/230830644): Replace view by Expandable interface.
fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View)
fun showPowerMenuDialog(
globalActionsDialogLite: GlobalActionsDialogLite,
expandable: Expandable,
)
/** Show the settings. */
fun showSettings(expandable: Expandable)
/** Show the user switcher. */
// TODO(b/230830644): Replace view by Expandable interface.
fun showUserSwitcher(view: View)
fun showUserSwitcher(context: Context, expandable: Expandable)
}
@SysUISingleton
@@ -147,28 +135,32 @@ constructor(
null,
)
override fun showDeviceMonitoringDialog(view: View) {
qsSecurityFooterUtils.showDeviceMonitoringDialog(view.context, view)
DevicePolicyEventLogger.createEvent(
FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED
)
.write()
override fun showDeviceMonitoringDialog(
quickSettingsContext: Context,
expandable: Expandable?,
) {
qsSecurityFooterUtils.showDeviceMonitoringDialog(quickSettingsContext, expandable)
if (expandable != null) {
DevicePolicyEventLogger.createEvent(
FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED
)
.write()
}
}
override fun showDeviceMonitoringDialog(quickSettingsContext: Context) {
qsSecurityFooterUtils.showDeviceMonitoringDialog(quickSettingsContext, /* view= */ null)
override fun showForegroundServicesDialog(expandable: Expandable) {
fgsManagerController.showDialog(expandable)
}
override fun showForegroundServicesDialog(view: View) {
fgsManagerController.showDialog(view)
}
override fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View) {
override fun showPowerMenuDialog(
globalActionsDialogLite: GlobalActionsDialogLite,
expandable: Expandable,
) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
globalActionsDialogLite.showOrHideDialog(
/* keyguardShowing= */ false,
/* isDeviceProvisioned= */ true,
view,
expandable,
)
}
@@ -189,21 +181,21 @@ constructor(
)
}
override fun showUserSwitcher(view: View) {
override fun showUserSwitcher(context: Context, expandable: Expandable) {
if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
userSwitchDialogController.showDialog(view)
userSwitchDialogController.showDialog(context, expandable)
return
}
val intent =
Intent(view.context, UserSwitcherActivity::class.java).apply {
Intent(context, UserSwitcherActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}
activityStarter.startActivity(
intent,
true /* dismissShade */,
ActivityLaunchAnimator.Controller.fromView(view, null),
expandable.activityLaunchController(),
true /* showOverlockscreenwhenlocked */,
UserHandle.SYSTEM,
)

View File

@@ -31,6 +31,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.people.ui.view.PeopleViewBinder.bind
@@ -125,7 +126,7 @@ object FooterActionsViewBinder {
launch {
viewModel.security.collect { security ->
if (previousSecurity != security) {
bindSecurity(securityHolder, security)
bindSecurity(view.context, securityHolder, security)
previousSecurity = security
}
}
@@ -159,6 +160,7 @@ object FooterActionsViewBinder {
}
private fun bindSecurity(
quickSettingsContext: Context,
securityHolder: TextButtonViewHolder,
security: FooterActionsSecurityButtonViewModel?,
) {
@@ -171,9 +173,12 @@ object FooterActionsViewBinder {
// Make sure that the chevron is visible and that the button is clickable if there is a
// listener.
val chevron = securityHolder.chevron
if (security.onClick != null) {
val onClick = security.onClick
if (onClick != null) {
securityView.isClickable = true
securityView.setOnClickListener(security.onClick)
securityView.setOnClickListener {
onClick(quickSettingsContext, Expandable.fromView(securityView))
}
chevron.isVisible = true
} else {
securityView.isClickable = false
@@ -205,7 +210,9 @@ object FooterActionsViewBinder {
foregroundServicesWithNumberView.isVisible = false
foregroundServicesWithTextView.isVisible = true
foregroundServicesWithTextView.setOnClickListener(foregroundServices.onClick)
foregroundServicesWithTextView.setOnClickListener {
foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
}
foregroundServicesWithTextHolder.text.text = foregroundServices.text
foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges
} else {
@@ -213,7 +220,9 @@ object FooterActionsViewBinder {
foregroundServicesWithTextView.isVisible = false
foregroundServicesWithNumberView.visibility = View.VISIBLE
foregroundServicesWithNumberView.setOnClickListener(foregroundServices.onClick)
foregroundServicesWithNumberView.setOnClickListener {
foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
}
foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges
@@ -229,7 +238,7 @@ object FooterActionsViewBinder {
}
buttonView.setBackgroundResource(model.background)
buttonView.setOnClickListener(model.onClick)
buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
val icon = model.icon
val iconView = button.icon

View File

@@ -17,7 +17,7 @@
package com.android.systemui.qs.footer.ui.viewmodel
import android.annotation.DrawableRes
import android.view.View
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
/**
@@ -29,7 +29,5 @@ data class FooterActionsButtonViewModel(
val icon: Icon,
val iconTint: Int?,
@DrawableRes val background: Int,
// TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
// or activity.
val onClick: (View) -> Unit,
val onClick: (Expandable) -> Unit,
)

View File

@@ -16,7 +16,7 @@
package com.android.systemui.qs.footer.ui.viewmodel
import android.view.View
import com.android.systemui.animation.Expandable
/** A ViewModel for the foreground services button. */
data class FooterActionsForegroundServicesButtonViewModel(
@@ -24,5 +24,5 @@ data class FooterActionsForegroundServicesButtonViewModel(
val text: String,
val displayText: Boolean,
val hasNewChanges: Boolean,
val onClick: (View) -> Unit,
val onClick: (Expandable) -> Unit,
)

View File

@@ -16,12 +16,13 @@
package com.android.systemui.qs.footer.ui.viewmodel
import android.view.View
import android.content.Context
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
/** A ViewModel for the security button. */
data class FooterActionsSecurityButtonViewModel(
val icon: Icon,
val text: String,
val onClick: ((View) -> Unit)?,
val onClick: ((quickSettingsContext: Context, Expandable) -> Unit)?,
)

View File

@@ -18,7 +18,6 @@ package com.android.systemui.qs.footer.ui.viewmodel
import android.content.Context
import android.util.Log
import android.view.View
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -199,50 +198,51 @@ class FooterActionsViewModel(
*/
suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
footerActionsInteractor.deviceMonitoringDialogRequests.collect {
footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext)
footerActionsInteractor.showDeviceMonitoringDialog(
quickSettingsContext,
expandable = null,
)
}
}
private fun onSecurityButtonClicked(view: View) {
private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
footerActionsInteractor.showDeviceMonitoringDialog(view)
footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable)
}
private fun onForegroundServiceButtonClicked(view: View) {
private fun onForegroundServiceButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
footerActionsInteractor.showForegroundServicesDialog(view)
footerActionsInteractor.showForegroundServicesDialog(expandable)
}
private fun onUserSwitcherClicked(view: View) {
private fun onUserSwitcherClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
footerActionsInteractor.showUserSwitcher(view)
footerActionsInteractor.showUserSwitcher(context, expandable)
}
// TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
// or activity.
private fun onSettingsButtonClicked(view: View) {
private fun onSettingsButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
footerActionsInteractor.showSettings(Expandable.fromView(view))
footerActionsInteractor.showSettings(expandable)
}
private fun onPowerButtonClicked(view: View) {
private fun onPowerButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, view)
footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable)
}
private fun userSwitcherButton(

View File

@@ -23,13 +23,13 @@ import android.content.DialogInterface.BUTTON_NEUTRAL
import android.content.Intent
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -77,10 +77,10 @@ class UserSwitchDialogController @VisibleForTesting constructor(
* Show a [UserDialog].
*
* Populate the dialog with information from and adapter obtained from
* [userDetailViewAdapterProvider] and show it as launched from [view].
* [userDetailViewAdapterProvider] and show it as launched from [expandable].
*/
fun showDialog(view: View) {
with(dialogFactory(view.context)) {
fun showDialog(context: Context, expandable: Expandable) {
with(dialogFactory(context)) {
setShowForAllUsers(true)
setCanceledOnTouchOutside(true)
@@ -112,13 +112,19 @@ class UserSwitchDialogController @VisibleForTesting constructor(
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
dialogLaunchAnimator.showFromView(
this, view,
cuj = DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG
val controller =
expandable.dialogLaunchController(
DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
)
)
if (controller != null) {
dialogLaunchAnimator.show(
this,
controller,
)
} else {
show()
}
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
}

View File

@@ -26,6 +26,7 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Expandable;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
@@ -67,7 +68,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
ActivityLaunchAnimator.Controller.fromView(v, null),
true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM);
} else {
mUserSwitchDialogController.showDialog(v);
mUserSwitchDialogController.showDialog(v.getContext(), Expandable.fromView(v));
}
}
};

View File

@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone.userswitcher
import android.content.Intent
import android.os.UserHandle
import android.view.View
import com.android.systemui.animation.Expandable
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
@@ -75,7 +76,7 @@ class StatusBarUserSwitcherControllerImpl @Inject constructor(
null /* ActivityLaunchAnimator.Controller */,
true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
} else {
userSwitcherDialogController.showDialog(view)
userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
}
}

View File

@@ -36,6 +36,7 @@ import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -190,7 +191,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
mUiEventLogger.log(
LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground);
mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
Expandable.fromView(mUserAvatarViewWithBackground));
});
mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {

View File

@@ -22,7 +22,6 @@ import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -52,6 +51,7 @@ import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.systemui.R;
@@ -97,6 +97,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
private static final int DEFAULT_ICON_ID = R.drawable.ic_info_outline;
private ViewGroup mRootView;
private ViewGroup mSecurityFooterView;
private TextView mFooterText;
private TestableImageView mPrimaryFooterIcon;
private QSSecurityFooter mFooter;
@@ -121,21 +122,26 @@ public class QSSecurityFooterTest extends SysuiTestCase {
Looper looper = mTestableLooper.getLooper();
Handler mainHandler = new Handler(looper);
when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
mRootView = (ViewGroup) new LayoutInflaterBuilder(mContext)
mSecurityFooterView = (ViewGroup) new LayoutInflaterBuilder(mContext)
.replace("ImageView", TestableImageView.class)
.build().inflate(R.layout.quick_settings_security_footer, null, false);
mFooterUtils = new QSSecurityFooterUtils(getContext(),
getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator);
mFooter = new QSSecurityFooter(mRootView, mainHandler, mSecurityController, looper,
mBroadcastDispatcher, mFooterUtils);
mFooterText = mRootView.findViewById(R.id.footer_text);
mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
mFooter = new QSSecurityFooter(mSecurityFooterView, mainHandler, mSecurityController,
looper, mBroadcastDispatcher, mFooterUtils);
mFooterText = mSecurityFooterView.findViewById(R.id.footer_text);
mPrimaryFooterIcon = mSecurityFooterView.findViewById(R.id.primary_footer_icon);
when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
.thenReturn(DEVICE_OWNER_COMPONENT);
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
// mSecurityFooterView must have a ViewGroup parent so that
// DialogLaunchAnimator.Controller.fromView() does not return null.
mRootView = new FrameLayout(mContext);
mRootView.addView(mSecurityFooterView);
ViewUtils.attachView(mRootView);
mFooter.init();
@@ -153,7 +159,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
assertEquals(View.GONE, mRootView.getVisibility());
assertEquals(View.GONE, mSecurityFooterView.getVisibility());
}
@Test
@@ -165,7 +171,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management),
mFooterText.getText());
assertEquals(View.VISIBLE, mRootView.getVisibility());
assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
@@ -181,7 +187,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management,
MANAGING_ORGANIZATION),
mFooterText.getText());
assertEquals(View.VISIBLE, mRootView.getVisibility());
assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
@@ -200,7 +206,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(mContext.getString(
R.string.quick_settings_financed_disclosure_named_management,
MANAGING_ORGANIZATION), mFooterText.getText());
assertEquals(View.VISIBLE, mRootView.getVisibility());
assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
@@ -217,7 +223,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
assertEquals(View.GONE, mRootView.getVisibility());
assertEquals(View.GONE, mSecurityFooterView.getVisibility());
}
@Test
@@ -227,8 +233,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
assertFalse(mRootView.isClickable());
assertEquals(View.GONE, mRootView.findViewById(R.id.footer_icon).getVisibility());
assertFalse(mSecurityFooterView.isClickable());
assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
}
@Test
@@ -241,8 +247,9 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
assertTrue(mRootView.isClickable());
assertEquals(View.VISIBLE, mRootView.findViewById(R.id.footer_icon).getVisibility());
assertTrue(mSecurityFooterView.isClickable());
assertEquals(View.VISIBLE,
mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
}
@Test
@@ -254,8 +261,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
assertFalse(mRootView.isClickable());
assertEquals(View.GONE, mRootView.findViewById(R.id.footer_icon).getVisibility());
assertFalse(mSecurityFooterView.isClickable());
assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
}
@Test
@@ -734,11 +741,11 @@ public class QSSecurityFooterTest extends SysuiTestCase {
@Test
public void testDialogUsesDialogLauncher() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
mFooter.onClick(mRootView);
mFooter.onClick(mSecurityFooterView);
mTestableLooper.processAllMessages();
verify(mDialogLaunchAnimator).showFromView(any(), eq(mRootView), any());
verify(mDialogLaunchAnimator).show(any(), any());
}
@Test
@@ -775,7 +782,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
mTestableLooper.processAllMessages();
verify(mDialogLaunchAnimator).showFromView(dialogCaptor.capture(), any(), any());
verify(mDialogLaunchAnimator).show(dialogCaptor.capture(), any());
AlertDialog dialog = dialogCaptor.getValue();
dialog.create();
@@ -817,8 +824,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
verify(mBroadcastDispatcher).registerReceiverWithHandler(captor.capture(), any(), any(),
any());
// Pretend view is not visible temporarily
mRootView.onVisibilityAggregated(false);
// Pretend view is not attached anymore.
mRootView.removeView(mSecurityFooterView);
captor.getValue().onReceive(mContext,
new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG));
mTestableLooper.processAllMessages();

View File

@@ -23,13 +23,13 @@ import android.os.UserHandle
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.nano.MetricsProto
import com.android.internal.logging.testing.FakeMetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
@@ -70,13 +70,13 @@ class FooterActionsInteractorTest : SysuiTestCase() {
val underTest = utils.footerActionsInteractor(qsSecurityFooterUtils = qsSecurityFooterUtils)
val quickSettingsContext = mock<Context>()
underTest.showDeviceMonitoringDialog(quickSettingsContext)
underTest.showDeviceMonitoringDialog(quickSettingsContext, null)
verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
val view = mock<View>()
whenever(view.context).thenReturn(quickSettingsContext)
underTest.showDeviceMonitoringDialog(view)
verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
val expandable = mock<Expandable>()
underTest.showDeviceMonitoringDialog(quickSettingsContext, expandable)
verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, expandable)
}
@Test
@@ -85,8 +85,8 @@ class FooterActionsInteractorTest : SysuiTestCase() {
val underTest = utils.footerActionsInteractor(uiEventLogger = uiEventLogger)
val globalActionsDialogLite = mock<GlobalActionsDialogLite>()
val view = mock<View>()
underTest.showPowerMenuDialog(globalActionsDialogLite, view)
val expandable = mock<Expandable>()
underTest.showPowerMenuDialog(globalActionsDialogLite, expandable)
// Event is logged.
val logs = uiEventLogger.logs
@@ -99,7 +99,7 @@ class FooterActionsInteractorTest : SysuiTestCase() {
.showOrHideDialog(
/* keyguardShowing= */ false,
/* isDeviceProvisioned= */ true,
view,
expandable,
)
}
@@ -167,11 +167,11 @@ class FooterActionsInteractorTest : SysuiTestCase() {
userSwitchDialogController = userSwitchDialogController,
)
val view = mock<View>()
underTest.showUserSwitcher(view)
val expandable = mock<Expandable>()
underTest.showUserSwitcher(context, expandable)
// Dialog is shown.
verify(userSwitchDialogController).showDialog(view)
verify(userSwitchDialogController).showDialog(context, expandable)
}
@Test
@@ -184,12 +184,9 @@ class FooterActionsInteractorTest : SysuiTestCase() {
activityStarter = activityStarter,
)
// The clicked view. The context is necessary because it's used to build the intent, that
// we check below.
val view = mock<View>()
whenever(view.context).thenReturn(context)
underTest.showUserSwitcher(view)
// The clicked expandable.
val expandable = mock<Expandable>()
underTest.showUserSwitcher(context, expandable)
// Dialog is shown.
val intentCaptor = argumentCaptor<Intent>()

View File

@@ -20,12 +20,12 @@ import android.content.DialogInterface
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
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,7 +64,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
@Mock
private lateinit var userDetailViewAdapter: UserDetailView.Adapter
@Mock
private lateinit var launchView: View
private lateinit var launchExpandable: Expandable
@Mock
private lateinit var neutralButton: Button
@Mock
@@ -79,7 +80,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
`when`(launchView.context).thenReturn(mContext)
`when`(dialog.context).thenReturn(mContext)
controller = UserSwitchDialogController(
@@ -94,32 +94,34 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
@Test
fun showDialog_callsDialogShow() {
controller.showDialog(launchView)
verify(dialogLaunchAnimator).showFromView(eq(dialog), eq(launchView), any(), anyBoolean())
val launchController = mock<DialogLaunchAnimator.Controller>()
`when`(launchExpandable.dialogLaunchController(any())).thenReturn(launchController)
controller.showDialog(context, launchExpandable)
verify(dialogLaunchAnimator).show(eq(dialog), eq(launchController), anyBoolean())
verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
}
@Test
fun dialog_showForAllUsers() {
controller.showDialog(launchView)
controller.showDialog(context, launchExpandable)
verify(dialog).setShowForAllUsers(true)
}
@Test
fun dialog_cancelOnTouchOutside() {
controller.showDialog(launchView)
controller.showDialog(context, launchExpandable)
verify(dialog).setCanceledOnTouchOutside(true)
}
@Test
fun adapterAndGridLinked() {
controller.showDialog(launchView)
controller.showDialog(context, launchExpandable)
verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>())
}
@Test
fun doneButtonLogsCorrectly() {
controller.showDialog(launchView)
controller.showDialog(context, launchExpandable)
verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor))
@@ -132,7 +134,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun clickSettingsButton_noFalsing_opensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
controller.showDialog(launchView)
controller.showDialog(context, launchExpandable)
verify(dialog)
.setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
@@ -153,7 +155,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun clickSettingsButton_Falsing_notOpensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
controller.showDialog(launchView)
controller.showDialog(context, launchExpandable)
verify(dialog)
.setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)

View File

@@ -20,7 +20,6 @@ import android.content.Intent
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
@@ -34,8 +33,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -91,7 +90,7 @@ class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
fun testStartActivity() {
`when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
statusBarUserSwitcherContainer.callOnClick()
verify(userSwitcherDialogController).showDialog(any(View::class.java))
verify(userSwitcherDialogController).showDialog(any(), any())
`when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
statusBarUserSwitcherContainer.callOnClick()
verify(activityStarter).startActivity(any(Intent::class.java),

View File

@@ -16,7 +16,7 @@
package com.android.systemui.qs
import android.view.View
import com.android.systemui.animation.Expandable
import com.android.systemui.qs.FgsManagerController.OnDialogDismissedListener
import com.android.systemui.qs.FgsManagerController.OnNumberOfPackagesChangedListener
import kotlinx.coroutines.flow.MutableStateFlow
@@ -54,7 +54,7 @@ class FakeFgsManagerController(
override fun init() {}
override fun showDialog(viewLaunchedFrom: View?) {}
override fun showDialog(expandable: Expandable?) {}
override fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
numRunningPackagesListeners.add(listener)

View File

@@ -57,7 +57,6 @@ import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.test.TestCoroutineDispatcher
@@ -68,7 +67,6 @@ import kotlinx.coroutines.test.TestCoroutineDispatcher
class FooterActionsTestUtils(
private val context: Context,
private val testableLooper: TestableLooper,
private val fakeClock: FakeSystemClock = FakeSystemClock(),
) {
/** Enable or disable the user switcher in the settings. */
fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {