Merge changes from topic "record-dialog-animation" into sc-v2-dev
* changes: Animate the Internet dialog height changes Animate the screen recording dialog (1/2) Animate the Data Saver QS dialog Wrap animated dialog view inside another view
This commit is contained in:
committed by
Android (Google) Code Review
commit
041b74abfa
@@ -356,10 +356,6 @@
|
||||
android:exported="false"
|
||||
android:finishOnTaskLaunch="true" />
|
||||
|
||||
<activity android:name=".screenrecord.ScreenRecordDialog"
|
||||
android:theme="@style/ScreenRecord"
|
||||
android:showForAllUsers="true"
|
||||
android:excludeFromRecents="true" />
|
||||
<service android:name=".screenrecord.RecordingService" />
|
||||
|
||||
<receiver android:name=".SysuiRestartReceiver"
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
|
||||
package com.android.systemui.animation
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.util.MathUtils
|
||||
import android.view.GhostView
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
@@ -32,6 +37,7 @@ import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
|
||||
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
import android.view.WindowManagerPolicyConstants
|
||||
import android.widget.FrameLayout
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val TAG = "DialogLaunchAnimator"
|
||||
|
||||
@@ -52,7 +58,8 @@ class DialogLaunchAnimator(
|
||||
private val currentAnimations = hashSetOf<DialogLaunchAnimation>()
|
||||
|
||||
/**
|
||||
* Show [dialog] by expanding it from [view].
|
||||
* Show [dialog] by expanding it from [view]. If [animateBackgroundBoundsChange] is true, then
|
||||
* the background of the dialog will be animated when the dialog bounds change.
|
||||
*
|
||||
* Caveats: When calling this function, the dialog content view will actually be stolen and
|
||||
* attached to a different dialog (and thus a different window) which means that the actual
|
||||
@@ -60,7 +67,12 @@ class DialogLaunchAnimator(
|
||||
* must call dismiss(), hide() and show() on the [Dialog] returned by this function to actually
|
||||
* dismiss, hide or show the dialog.
|
||||
*/
|
||||
fun showFromView(dialog: Dialog, view: View): Dialog {
|
||||
@JvmOverloads
|
||||
fun showFromView(
|
||||
dialog: Dialog,
|
||||
view: View,
|
||||
animateBackgroundBoundsChange: Boolean = false
|
||||
): Dialog {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
throw IllegalStateException(
|
||||
"showFromView must be called from the main thread and dialog must be created in " +
|
||||
@@ -78,7 +90,8 @@ class DialogLaunchAnimator(
|
||||
|
||||
val launchAnimation = DialogLaunchAnimation(
|
||||
context, launchAnimator, hostDialogProvider, view,
|
||||
onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog)
|
||||
onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog,
|
||||
animateBackgroundBoundsChange)
|
||||
val hostDialog = launchAnimation.hostDialog
|
||||
currentAnimations.add(launchAnimation)
|
||||
|
||||
@@ -208,7 +221,10 @@ private class DialogLaunchAnimation(
|
||||
private val onDialogDismissed: (DialogLaunchAnimation) -> Unit,
|
||||
|
||||
/** The original dialog whose content will be shown and animate in/out in [hostDialog]. */
|
||||
private val originalDialog: Dialog
|
||||
private val originalDialog: Dialog,
|
||||
|
||||
/** Whether we should animate the dialog background when its bounds change. */
|
||||
private val animateBackgroundBoundsChange: Boolean
|
||||
) {
|
||||
/**
|
||||
* The fullscreen dialog to which we will add the content view [originalDialogView] of
|
||||
@@ -221,10 +237,11 @@ private class DialogLaunchAnimation(
|
||||
private val hostDialogRoot = FrameLayout(context)
|
||||
|
||||
/**
|
||||
* The content view of [originalDialog], which will be stolen from that dialog and added to
|
||||
* [hostDialogRoot].
|
||||
* The parent of the original dialog content view, that serves as a fake window that will have
|
||||
* the same size as the original dialog window and to which we will set the original dialog
|
||||
* window background.
|
||||
*/
|
||||
private var originalDialogView: View? = null
|
||||
private val dialogContentParent = FrameLayout(context)
|
||||
|
||||
/**
|
||||
* The background color of [originalDialogView], taking into consideration the [originalDialog]
|
||||
@@ -246,6 +263,11 @@ private class DialogLaunchAnimation(
|
||||
|
||||
private var isTouchSurfaceGhostDrawn = false
|
||||
private var isOriginalDialogViewLaidOut = false
|
||||
private var backgroundLayoutListener = if (animateBackgroundBoundsChange) {
|
||||
AnimatedBoundsLayoutListener()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun start() {
|
||||
// Show the host (fullscreen) dialog, to which we will add the stolen dialog view.
|
||||
@@ -374,9 +396,6 @@ private class DialogLaunchAnimation(
|
||||
}
|
||||
|
||||
private fun showDialogFromView(dialogView: View) {
|
||||
// Save the dialog view for later as we will need it for the close animation.
|
||||
this.originalDialogView = dialogView
|
||||
|
||||
// Close the dialog when clicking outside of it.
|
||||
hostDialogRoot.setOnClickListener { hostDialog.dismiss() }
|
||||
dialogView.isClickable = true
|
||||
@@ -394,17 +413,13 @@ private class DialogLaunchAnimation(
|
||||
throw IllegalStateException("Dialogs with no backgrounds on window are not supported")
|
||||
}
|
||||
|
||||
dialogView.setBackgroundResource(backgroundRes)
|
||||
originalDialogBackgroundColor =
|
||||
GhostedViewLaunchAnimatorController.findGradientDrawable(dialogView.background!!)
|
||||
?.color
|
||||
?.defaultColor ?: Color.BLACK
|
||||
|
||||
// Add the dialog view to the host (fullscreen) dialog and make it invisible to make sure
|
||||
// it's not drawn yet.
|
||||
(dialogView.parent as? ViewGroup)?.removeView(dialogView)
|
||||
// Add a parent view to the original dialog view to which we will set the original dialog
|
||||
// window background. This View serves as a fake window with background, so that we are sure
|
||||
// that we don't override the dialog view paddings with the window background that usually
|
||||
// has insets.
|
||||
dialogContentParent.setBackgroundResource(backgroundRes)
|
||||
hostDialogRoot.addView(
|
||||
dialogView,
|
||||
dialogContentParent,
|
||||
|
||||
// We give it the size of its original dialog window.
|
||||
FrameLayout.LayoutParams(
|
||||
@@ -413,10 +428,31 @@ private class DialogLaunchAnimation(
|
||||
Gravity.CENTER
|
||||
)
|
||||
)
|
||||
dialogView.visibility = View.INVISIBLE
|
||||
|
||||
// Make the dialog view parent invisible for now, to make sure it's not drawn yet.
|
||||
dialogContentParent.visibility = View.INVISIBLE
|
||||
|
||||
val background = dialogContentParent.background!!
|
||||
originalDialogBackgroundColor =
|
||||
GhostedViewLaunchAnimatorController.findGradientDrawable(background)
|
||||
?.color
|
||||
?.defaultColor ?: Color.BLACK
|
||||
|
||||
// Add the dialog view to its parent (that has the original window background).
|
||||
(dialogView.parent as? ViewGroup)?.removeView(dialogView)
|
||||
dialogContentParent.addView(
|
||||
dialogView,
|
||||
|
||||
// It should match its parent size, which is sized the same as the original dialog
|
||||
// window.
|
||||
FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
|
||||
// Start the animation when the dialog is laid out in the center of the host dialog.
|
||||
dialogView.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
|
||||
dialogContentParent.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
|
||||
override fun onLayoutChange(
|
||||
view: View,
|
||||
left: Int,
|
||||
@@ -428,7 +464,7 @@ private class DialogLaunchAnimation(
|
||||
oldRight: Int,
|
||||
oldBottom: Int
|
||||
) {
|
||||
dialogView.removeOnLayoutChangeListener(this)
|
||||
dialogContentParent.removeOnLayoutChangeListener(this)
|
||||
|
||||
isOriginalDialogViewLaidOut = true
|
||||
maybeStartLaunchAnimation()
|
||||
@@ -479,6 +515,13 @@ private class DialogLaunchAnimation(
|
||||
if (dismissRequested) {
|
||||
hostDialog.dismiss()
|
||||
}
|
||||
|
||||
// If necessary, we animate the dialog background when its bounds change. We do it
|
||||
// at the end of the launch animation, because the lauch animation already correctly
|
||||
// handles bounds changes.
|
||||
if (backgroundLayoutListener != null) {
|
||||
dialogContentParent.addOnLayoutChangeListener(backgroundLayoutListener)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -548,7 +591,11 @@ private class DialogLaunchAnimation(
|
||||
}
|
||||
|
||||
touchSurface.visibility = View.VISIBLE
|
||||
originalDialogView!!.visibility = View.INVISIBLE
|
||||
dialogContentParent.visibility = View.INVISIBLE
|
||||
|
||||
if (backgroundLayoutListener != null) {
|
||||
dialogContentParent.removeOnLayoutChangeListener(backgroundLayoutListener)
|
||||
}
|
||||
|
||||
// The animated ghost was just removed. We create a temporary ghost that will be
|
||||
// removed only once we draw the touch surface, to avoid flickering that would
|
||||
@@ -578,12 +625,10 @@ private class DialogLaunchAnimation(
|
||||
onLaunchAnimationStart: () -> Unit = {},
|
||||
onLaunchAnimationEnd: () -> Unit = {}
|
||||
) {
|
||||
val dialogView = this.originalDialogView!!
|
||||
|
||||
// Create 2 ghost controllers to animate both the dialog and the touch surface in the host
|
||||
// dialog.
|
||||
val startView = if (isLaunching) touchSurface else dialogView
|
||||
val endView = if (isLaunching) dialogView else touchSurface
|
||||
val startView = if (isLaunching) touchSurface else dialogContentParent
|
||||
val endView = if (isLaunching) dialogContentParent else touchSurface
|
||||
val startViewController = GhostedViewLaunchAnimatorController(startView)
|
||||
val endViewController = GhostedViewLaunchAnimatorController(endView)
|
||||
startViewController.launchContainer = hostDialogRoot
|
||||
@@ -662,4 +707,81 @@ private class DialogLaunchAnimation(
|
||||
|
||||
return (touchSurface.parent as? View)?.isShown ?: true
|
||||
}
|
||||
|
||||
/** A layout listener to animate the change of bounds of the dialog background. */
|
||||
class AnimatedBoundsLayoutListener : View.OnLayoutChangeListener {
|
||||
companion object {
|
||||
private const val ANIMATION_DURATION = 500L
|
||||
}
|
||||
|
||||
private var lastBounds: Rect? = null
|
||||
private var currentAnimator: ValueAnimator? = null
|
||||
|
||||
override fun onLayoutChange(
|
||||
view: View,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int
|
||||
) {
|
||||
// Don't animate if bounds didn't actually change.
|
||||
if (left == oldLeft && top == oldTop && right == oldRight && bottom == oldBottom) {
|
||||
// Make sure that we that the last bounds set by the animator were not overridden.
|
||||
lastBounds?.let { bounds ->
|
||||
view.left = bounds.left
|
||||
view.top = bounds.top
|
||||
view.right = bounds.right
|
||||
view.bottom = bounds.bottom
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (lastBounds == null) {
|
||||
lastBounds = Rect(oldLeft, oldTop, oldRight, oldBottom)
|
||||
}
|
||||
|
||||
val bounds = lastBounds!!
|
||||
val startLeft = bounds.left
|
||||
val startTop = bounds.top
|
||||
val startRight = bounds.right
|
||||
val startBottom = bounds.bottom
|
||||
|
||||
currentAnimator?.cancel()
|
||||
currentAnimator = null
|
||||
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
duration = ANIMATION_DURATION
|
||||
interpolator = Interpolators.STANDARD
|
||||
|
||||
addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
currentAnimator = null
|
||||
}
|
||||
})
|
||||
|
||||
addUpdateListener { animatedValue ->
|
||||
val progress = animatedValue.animatedFraction
|
||||
|
||||
// Compute new bounds.
|
||||
bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt()
|
||||
bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt()
|
||||
bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt()
|
||||
bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt()
|
||||
|
||||
// Set the new bounds.
|
||||
view.left = bounds.left
|
||||
view.top = bounds.top
|
||||
view.right = bounds.right
|
||||
view.bottom = bounds.bottom
|
||||
}
|
||||
}
|
||||
|
||||
currentAnimator = animator
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,78 +16,74 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<FrameLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:sysui="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/qs_user_switch_dialog_title"
|
||||
android:textAppearance="@style/TextAppearance.QSDialog.Title"
|
||||
android:layout_marginBottom="32dp"
|
||||
sysui:layout_constraintTop_toTopOf="parent"
|
||||
sysui:layout_constraintStart_toStartOf="parent"
|
||||
sysui:layout_constraintEnd_toEndOf="parent"
|
||||
sysui:layout_constraintBottom_toTopOf="@id/grid"
|
||||
/>
|
||||
|
||||
<com.android.systemui.qs.PseudoGridView
|
||||
android:id="@+id/grid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="28dp"
|
||||
sysui:verticalSpacing="4dp"
|
||||
sysui:horizontalSpacing="4dp"
|
||||
sysui:fixedChildWidth="80dp"
|
||||
sysui:layout_constraintTop_toBottomOf="@id/title"
|
||||
sysui:layout_constraintStart_toStartOf="parent"
|
||||
sysui:layout_constraintEnd_toEndOf="parent"
|
||||
sysui:layout_constraintBottom_toTopOf="@id/barrier"
|
||||
android:layout_width="0dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/qs_user_switch_dialog_title"
|
||||
android:textAppearance="@style/TextAppearance.QSDialog.Title"
|
||||
android:layout_marginBottom="32dp"
|
||||
sysui:layout_constraintTop_toTopOf="parent"
|
||||
sysui:layout_constraintStart_toStartOf="parent"
|
||||
sysui:layout_constraintEnd_toEndOf="parent"
|
||||
sysui:layout_constraintBottom_toTopOf="@id/grid"
|
||||
/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
sysui:barrierDirection="top"
|
||||
sysui:constraint_referenced_ids="settings,done"
|
||||
/>
|
||||
<com.android.systemui.qs.PseudoGridView
|
||||
android:id="@+id/grid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="28dp"
|
||||
sysui:verticalSpacing="4dp"
|
||||
sysui:horizontalSpacing="4dp"
|
||||
sysui:fixedChildWidth="80dp"
|
||||
sysui:layout_constraintTop_toBottomOf="@id/title"
|
||||
sysui:layout_constraintStart_toStartOf="parent"
|
||||
sysui:layout_constraintEnd_toEndOf="parent"
|
||||
sysui:layout_constraintBottom_toTopOf="@id/barrier"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:text="@string/quick_settings_more_user_settings"
|
||||
sysui:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
sysui:layout_constraintBottom_toBottomOf="parent"
|
||||
sysui:layout_constraintStart_toStartOf="parent"
|
||||
sysui:layout_constraintEnd_toStartOf="@id/done"
|
||||
sysui:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
style="@style/Widget.QSDialog.Button.BorderButton"
|
||||
/>
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
sysui:barrierDirection="top"
|
||||
sysui:constraint_referenced_ids="settings,done"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/done"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:text="@string/quick_settings_done"
|
||||
sysui:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
sysui:layout_constraintBottom_toBottomOf="parent"
|
||||
sysui:layout_constraintStart_toEndOf="@id/settings"
|
||||
sysui:layout_constraintEnd_toEndOf="parent"
|
||||
style="@style/Widget.QSDialog.Button"
|
||||
/>
|
||||
<Button
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:text="@string/quick_settings_more_user_settings"
|
||||
sysui:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
sysui:layout_constraintBottom_toBottomOf="parent"
|
||||
sysui:layout_constraintStart_toStartOf="parent"
|
||||
sysui:layout_constraintEnd_toStartOf="@id/done"
|
||||
sysui:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
style="@style/Widget.QSDialog.Button.BorderButton"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
||||
<Button
|
||||
android:id="@+id/done"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:text="@string/quick_settings_done"
|
||||
sysui:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
sysui:layout_constraintBottom_toBottomOf="parent"
|
||||
sysui:layout_constraintStart_toEndOf="@id/settings"
|
||||
sysui:layout_constraintEnd_toEndOf="parent"
|
||||
style="@style/Widget.QSDialog.Button"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -17,8 +17,7 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_bg_full">
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Scrollview is necessary to fit everything in landscape layout -->
|
||||
<ScrollView
|
||||
|
||||
@@ -22,7 +22,6 @@ import com.android.systemui.ForegroundServicesDialog;
|
||||
import com.android.systemui.keyguard.WorkLockActivity;
|
||||
import com.android.systemui.people.PeopleSpaceActivity;
|
||||
import com.android.systemui.people.widget.LaunchConversationActivity;
|
||||
import com.android.systemui.screenrecord.ScreenRecordDialog;
|
||||
import com.android.systemui.screenshot.LongScreenshotActivity;
|
||||
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
|
||||
import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
|
||||
@@ -67,12 +66,6 @@ public abstract class DefaultActivityBinder {
|
||||
@ClassKey(BrightnessDialog.class)
|
||||
public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
|
||||
|
||||
/** Inject into ScreenRecordDialog */
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(ScreenRecordDialog.class)
|
||||
public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
|
||||
|
||||
/** Inject into UsbDebuggingActivity. */
|
||||
@Binds
|
||||
@IntoMap
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package com.android.systemui.qs.tiles;
|
||||
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -29,6 +28,7 @@ import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.systemui.Prefs;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
@@ -47,6 +47,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
|
||||
DataSaverController.Listener{
|
||||
|
||||
private final DataSaverController mDataSaverController;
|
||||
private final DialogLaunchAnimator mDialogLaunchAnimator;
|
||||
|
||||
@Inject
|
||||
public DataSaverTile(
|
||||
@@ -58,11 +59,13 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
|
||||
StatusBarStateController statusBarStateController,
|
||||
ActivityStarter activityStarter,
|
||||
QSLogger qsLogger,
|
||||
DataSaverController dataSaverController
|
||||
DataSaverController dataSaverController,
|
||||
DialogLaunchAnimator dialogLaunchAnimator
|
||||
) {
|
||||
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
|
||||
statusBarStateController, activityStarter, qsLogger);
|
||||
mDataSaverController = dataSaverController;
|
||||
mDialogLaunchAnimator = dialogLaunchAnimator;
|
||||
mDataSaverController.observe(getLifecycle(), this);
|
||||
}
|
||||
|
||||
@@ -83,18 +86,27 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
|
||||
toggleDataSaver();
|
||||
return;
|
||||
}
|
||||
// Shows dialog first
|
||||
SystemUIDialog dialog = new SystemUIDialog(mContext);
|
||||
dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
|
||||
dialog.setMessage(com.android.internal.R.string.data_saver_description);
|
||||
dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
|
||||
(OnClickListener) (dialogInterface, which) -> {
|
||||
toggleDataSaver();
|
||||
Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
|
||||
});
|
||||
dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
|
||||
dialog.setShowForAllUsers(true);
|
||||
dialog.show();
|
||||
|
||||
// Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator must be created
|
||||
// and shown on the main thread, so we post it to the UI handler.
|
||||
mUiHandler.post(() -> {
|
||||
SystemUIDialog dialog = new SystemUIDialog(mContext);
|
||||
dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
|
||||
dialog.setMessage(com.android.internal.R.string.data_saver_description);
|
||||
dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
|
||||
(dialogInterface, which) -> {
|
||||
toggleDataSaver();
|
||||
Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
|
||||
});
|
||||
dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
|
||||
dialog.setShowForAllUsers(true);
|
||||
|
||||
if (view != null) {
|
||||
mDialogLaunchAnimator.showFromView(dialog, view);
|
||||
} else {
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleDataSaver() {
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
@@ -39,7 +40,9 @@ import com.android.systemui.qs.QSHost;
|
||||
import com.android.systemui.qs.logging.QSLogger;
|
||||
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
||||
import com.android.systemui.screenrecord.RecordingController;
|
||||
import com.android.systemui.screenrecord.ScreenRecordDialog;
|
||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -49,10 +52,13 @@ import javax.inject.Inject;
|
||||
public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
||||
implements RecordingController.RecordingStateChangeCallback {
|
||||
private static final String TAG = "ScreenRecordTile";
|
||||
private RecordingController mController;
|
||||
private KeyguardDismissUtil mKeyguardDismissUtil;
|
||||
private final RecordingController mController;
|
||||
private final KeyguardDismissUtil mKeyguardDismissUtil;
|
||||
private final KeyguardStateController mKeyguardStateController;
|
||||
private final Callback mCallback = new Callback();
|
||||
private final DialogLaunchAnimator mDialogLaunchAnimator;
|
||||
|
||||
private long mMillisUntilFinished = 0;
|
||||
private Callback mCallback = new Callback();
|
||||
|
||||
@Inject
|
||||
public ScreenRecordTile(
|
||||
@@ -65,13 +71,17 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
||||
ActivityStarter activityStarter,
|
||||
QSLogger qsLogger,
|
||||
RecordingController controller,
|
||||
KeyguardDismissUtil keyguardDismissUtil
|
||||
KeyguardDismissUtil keyguardDismissUtil,
|
||||
KeyguardStateController keyguardStateController,
|
||||
DialogLaunchAnimator dialogLaunchAnimator
|
||||
) {
|
||||
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
|
||||
statusBarStateController, activityStarter, qsLogger);
|
||||
mController = controller;
|
||||
mController.observe(this, mCallback);
|
||||
mKeyguardDismissUtil = keyguardDismissUtil;
|
||||
mKeyguardStateController = keyguardStateController;
|
||||
mDialogLaunchAnimator = dialogLaunchAnimator;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,7 +99,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
||||
} else if (mController.isRecording()) {
|
||||
stopRecording();
|
||||
} else {
|
||||
mUiHandler.post(() -> showPrompt());
|
||||
mUiHandler.post(() -> showPrompt(view));
|
||||
}
|
||||
refreshState();
|
||||
}
|
||||
@@ -136,15 +146,33 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
||||
return mContext.getString(R.string.quick_settings_screen_record_label);
|
||||
}
|
||||
|
||||
private void showPrompt() {
|
||||
// Close QS, otherwise the dialog appears beneath it
|
||||
getHost().collapsePanels();
|
||||
Intent intent = mController.getPromptIntent();
|
||||
private void showPrompt(@Nullable View view) {
|
||||
// We animate from the touched view only if we are not on the keyguard, given that if we
|
||||
// are we will dismiss it which will also collapse the shade.
|
||||
boolean shouldAnimateFromView = view != null && !mKeyguardStateController.isShowing();
|
||||
|
||||
// Create the recording dialog that will collapse the shade only if we start the recording.
|
||||
Runnable onStartRecordingClicked = () -> {
|
||||
// We dismiss the shade. Since starting the recording will also dismiss the dialog, we
|
||||
// disable the exit animation which looks weird when it happens at the same time as the
|
||||
// shade collapsing.
|
||||
mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
|
||||
getHost().collapsePanels();
|
||||
};
|
||||
ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext,
|
||||
onStartRecordingClicked);
|
||||
|
||||
ActivityStarter.OnDismissAction dismissAction = () -> {
|
||||
mHost.getUserContext().startActivity(intent);
|
||||
if (shouldAnimateFromView) {
|
||||
mDialogLaunchAnimator.showFromView(dialog, view);
|
||||
} else {
|
||||
dialog.show();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false, false);
|
||||
|
||||
mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false /* requiresShadeOpen */,
|
||||
true /* afterKeyguardDone */);
|
||||
}
|
||||
|
||||
private void cancelCountdown() {
|
||||
|
||||
@@ -63,7 +63,8 @@ class InternetDialogFactory @Inject constructor(
|
||||
canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
|
||||
executor)
|
||||
if (view != null) {
|
||||
dialogLaunchAnimator.showFromView(internetDialog!!, view)
|
||||
dialogLaunchAnimator.showFromView(internetDialog!!, view,
|
||||
animateBackgroundBoundsChange = true)
|
||||
} else {
|
||||
internetDialog?.show()
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.systemui.screenrecord;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
@@ -27,10 +26,12 @@ import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.settings.UserContextProvider;
|
||||
import com.android.systemui.statusbar.policy.CallbackController;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
@@ -44,15 +45,13 @@ import javax.inject.Inject;
|
||||
public class RecordingController
|
||||
implements CallbackController<RecordingController.RecordingStateChangeCallback> {
|
||||
private static final String TAG = "RecordingController";
|
||||
private static final String SYSUI_PACKAGE = "com.android.systemui";
|
||||
private static final String SYSUI_SCREENRECORD_LAUNCHER =
|
||||
"com.android.systemui.screenrecord.ScreenRecordDialog";
|
||||
|
||||
private boolean mIsStarting;
|
||||
private boolean mIsRecording;
|
||||
private PendingIntent mStopIntent;
|
||||
private CountDownTimer mCountDownTimer = null;
|
||||
private BroadcastDispatcher mBroadcastDispatcher;
|
||||
private UserContextProvider mUserContextProvider;
|
||||
|
||||
protected static final String INTENT_UPDATE_STATE =
|
||||
"com.android.systemui.screenrecord.UPDATE_STATE";
|
||||
@@ -88,20 +87,16 @@ public class RecordingController
|
||||
* Create a new RecordingController
|
||||
*/
|
||||
@Inject
|
||||
public RecordingController(BroadcastDispatcher broadcastDispatcher) {
|
||||
public RecordingController(BroadcastDispatcher broadcastDispatcher,
|
||||
UserContextProvider userContextProvider) {
|
||||
mBroadcastDispatcher = broadcastDispatcher;
|
||||
mUserContextProvider = userContextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an intent to show screen recording options to the user.
|
||||
*/
|
||||
public Intent getPromptIntent() {
|
||||
final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
|
||||
SYSUI_SCREENRECORD_LAUNCHER);
|
||||
final Intent intent = new Intent();
|
||||
intent.setComponent(launcherComponent);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
return intent;
|
||||
/** Create a dialog to show screen recording options to the user. */
|
||||
public ScreenRecordDialog createScreenRecordDialog(Context context,
|
||||
@Nullable Runnable onStartRecordingClicked) {
|
||||
return new ScreenRecordDialog(context, this, mUserContextProvider, onStartRecordingClicked);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
@@ -34,34 +33,38 @@ import android.widget.Spinner;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.settings.UserContextProvider;
|
||||
import com.android.systemui.statusbar.phone.SystemUIDialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Activity to select screen recording options
|
||||
* Dialog to select screen recording options
|
||||
*/
|
||||
public class ScreenRecordDialog extends Activity {
|
||||
public class ScreenRecordDialog extends SystemUIDialog {
|
||||
private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC,
|
||||
MIC_AND_INTERNAL);
|
||||
private static final long DELAY_MS = 3000;
|
||||
private static final long INTERVAL_MS = 1000;
|
||||
private static final String TAG = "ScreenRecordDialog";
|
||||
|
||||
private final RecordingController mController;
|
||||
private final UserContextProvider mUserContextProvider;
|
||||
@Nullable
|
||||
private final Runnable mOnStartRecordingClicked;
|
||||
private Switch mTapsSwitch;
|
||||
private Switch mAudioSwitch;
|
||||
private Spinner mOptions;
|
||||
private List<ScreenRecordingAudioSource> mModes;
|
||||
|
||||
@Inject
|
||||
public ScreenRecordDialog(RecordingController controller,
|
||||
UserContextProvider userContextProvider) {
|
||||
public ScreenRecordDialog(Context context, RecordingController controller,
|
||||
UserContextProvider userContextProvider, @Nullable Runnable onStartRecordingClicked) {
|
||||
super(context);
|
||||
mController = controller;
|
||||
mUserContextProvider = userContextProvider;
|
||||
mOnStartRecordingClicked = onStartRecordingClicked;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,37 +72,35 @@ public class ScreenRecordDialog extends Activity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Window window = getWindow();
|
||||
// Inflate the decor view, so the attributes below are not overwritten by the theme.
|
||||
window.getDecorView();
|
||||
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
|
||||
window.setGravity(Gravity.TOP);
|
||||
|
||||
window.setGravity(Gravity.CENTER);
|
||||
setTitle(R.string.screenrecord_name);
|
||||
|
||||
setContentView(R.layout.screen_record_dialog);
|
||||
|
||||
TextView cancelBtn = findViewById(R.id.button_cancel);
|
||||
cancelBtn.setOnClickListener(v -> {
|
||||
finish();
|
||||
});
|
||||
cancelBtn.setOnClickListener(v -> dismiss());
|
||||
|
||||
TextView startBtn = findViewById(R.id.button_start);
|
||||
startBtn.setOnClickListener(v -> {
|
||||
requestScreenCapture();
|
||||
finish();
|
||||
});
|
||||
if (mOnStartRecordingClicked != null) {
|
||||
// Note that it is important to run this callback before dismissing, so that the
|
||||
// callback can disable the dialog exit animation if it wants to.
|
||||
mOnStartRecordingClicked.run();
|
||||
}
|
||||
|
||||
mModes = new ArrayList<>();
|
||||
mModes.add(INTERNAL);
|
||||
mModes.add(MIC);
|
||||
mModes.add(MIC_AND_INTERNAL);
|
||||
requestScreenCapture();
|
||||
dismiss();
|
||||
});
|
||||
|
||||
mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
|
||||
mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
|
||||
mOptions = findViewById(R.id.screen_recording_options);
|
||||
ArrayAdapter a = new ScreenRecordingAdapter(getApplicationContext(),
|
||||
ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(),
|
||||
android.R.layout.simple_spinner_dropdown_item,
|
||||
mModes);
|
||||
MODES);
|
||||
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mOptions.setAdapter(a);
|
||||
mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
|
||||
@@ -116,7 +117,7 @@ public class ScreenRecordDialog extends Activity {
|
||||
PendingIntent startIntent = PendingIntent.getForegroundService(userContext,
|
||||
RecordingService.REQUEST_CODE,
|
||||
RecordingService.getStartIntent(
|
||||
userContext, RESULT_OK,
|
||||
userContext, Activity.RESULT_OK,
|
||||
audioMode.ordinal(), showTaps),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
PendingIntent stopIntent = PendingIntent.getService(userContext,
|
||||
|
||||
@@ -60,9 +60,20 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
assertEquals(0, dialog.findViewById<ViewGroup>(android.R.id.content).childCount)
|
||||
assertEquals(1, hostDialogContent.childCount)
|
||||
|
||||
// The original dialog content is added to another view that is the same size as the
|
||||
// original dialog window.
|
||||
val hostDialogRoot = hostDialogContent.getChildAt(0) as ViewGroup
|
||||
assertEquals(1, hostDialogRoot.childCount)
|
||||
assertEquals(dialog.contentView, hostDialogRoot.getChildAt(0))
|
||||
|
||||
val dialogContentParent = hostDialogRoot.getChildAt(0) as ViewGroup
|
||||
assertEquals(1, dialogContentParent.childCount)
|
||||
assertEquals(TestDialog.DIALOG_WIDTH, dialogContentParent.layoutParams.width)
|
||||
assertEquals(TestDialog.DIALOG_HEIGHT, dialogContentParent.layoutParams.height)
|
||||
|
||||
val dialogContent = dialogContentParent.getChildAt(0)
|
||||
assertEquals(dialog.contentView, dialogContent)
|
||||
assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.width)
|
||||
assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.height)
|
||||
|
||||
// Hiding/showing/dismissing the dialog should hide/show/dismiss the host dialog given that
|
||||
// it's a ListenableDialog.
|
||||
@@ -126,6 +137,11 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
private class TestDialog(context: Context) : Dialog(context), ListenableDialog {
|
||||
companion object {
|
||||
const val DIALOG_WIDTH = 100
|
||||
const val DIALOG_HEIGHT = 200
|
||||
}
|
||||
|
||||
private val listeners = hashSetOf<DialogListener>()
|
||||
val contentView = View(context)
|
||||
var onStartCalled = false
|
||||
@@ -138,6 +154,7 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
|
||||
setContentView(contentView)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
package com.android.systemui.qs.tiles;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -34,6 +36,7 @@ import androidx.test.filters.SmallTest;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||
import com.android.systemui.classifier.FalsingManagerFake;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
@@ -41,10 +44,12 @@ import com.android.systemui.qs.QSTileHost;
|
||||
import com.android.systemui.qs.logging.QSLogger;
|
||||
import com.android.systemui.screenrecord.RecordingController;
|
||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@@ -67,6 +72,10 @@ public class ScreenRecordTileTest extends SysuiTestCase {
|
||||
private ActivityStarter mActivityStarter;
|
||||
@Mock
|
||||
private QSLogger mQSLogger;
|
||||
@Mock
|
||||
private KeyguardStateController mKeyguardStateController;
|
||||
@Mock
|
||||
private DialogLaunchAnimator mDialogLaunchAnimator;
|
||||
|
||||
private TestableLooper mTestableLooper;
|
||||
private ScreenRecordTile mTile;
|
||||
@@ -89,7 +98,9 @@ public class ScreenRecordTileTest extends SysuiTestCase {
|
||||
mActivityStarter,
|
||||
mQSLogger,
|
||||
mController,
|
||||
mKeyguardDismissUtil
|
||||
mKeyguardDismissUtil,
|
||||
mKeyguardStateController,
|
||||
mDialogLaunchAnimator
|
||||
);
|
||||
|
||||
mTile.initialize();
|
||||
@@ -112,7 +123,15 @@ public class ScreenRecordTileTest extends SysuiTestCase {
|
||||
|
||||
mTile.handleClick(null /* view */);
|
||||
mTestableLooper.processAllMessages();
|
||||
verify(mController, times(1)).getPromptIntent();
|
||||
|
||||
ArgumentCaptor<Runnable> onStartRecordingClicked = ArgumentCaptor.forClass(Runnable.class);
|
||||
verify(mController).createScreenRecordDialog(any(), onStartRecordingClicked.capture());
|
||||
|
||||
// When starting the recording, we collapse the shade and disable the dialog animation.
|
||||
assertNotNull(onStartRecordingClicked.getValue());
|
||||
onStartRecordingClicked.getValue().run();
|
||||
verify(mDialogLaunchAnimator).disableAllCurrentDialogsExitAnimations();
|
||||
verify(mHost).collapsePanels();
|
||||
}
|
||||
|
||||
// Test that the tile is active and labeled correctly when the controller is starting
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.settings.UserContextProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -52,6 +53,8 @@ public class RecordingControllerTest extends SysuiTestCase {
|
||||
private RecordingController.RecordingStateChangeCallback mCallback;
|
||||
@Mock
|
||||
private BroadcastDispatcher mBroadcastDispatcher;
|
||||
@Mock
|
||||
private UserContextProvider mUserContextProvider;
|
||||
|
||||
private RecordingController mController;
|
||||
|
||||
@@ -60,7 +63,7 @@ public class RecordingControllerTest extends SysuiTestCase {
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mController = new RecordingController(mBroadcastDispatcher);
|
||||
mController = new RecordingController(mBroadcastDispatcher, mUserContextProvider);
|
||||
mController.addCallback(mCallback);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user