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:exported="false"
|
||||||
android:finishOnTaskLaunch="true" />
|
android:finishOnTaskLaunch="true" />
|
||||||
|
|
||||||
<activity android:name=".screenrecord.ScreenRecordDialog"
|
|
||||||
android:theme="@style/ScreenRecord"
|
|
||||||
android:showForAllUsers="true"
|
|
||||||
android:excludeFromRecents="true" />
|
|
||||||
<service android:name=".screenrecord.RecordingService" />
|
<service android:name=".screenrecord.RecordingService" />
|
||||||
|
|
||||||
<receiver android:name=".SysuiRestartReceiver"
|
<receiver android:name=".SysuiRestartReceiver"
|
||||||
|
|||||||
@@ -16,11 +16,16 @@
|
|||||||
|
|
||||||
package com.android.systemui.animation
|
package com.android.systemui.animation
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.animation.ValueAnimator
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.Rect
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.util.MathUtils
|
||||||
import android.view.GhostView
|
import android.view.GhostView
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
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.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||||
import android.view.WindowManagerPolicyConstants
|
import android.view.WindowManagerPolicyConstants
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private const val TAG = "DialogLaunchAnimator"
|
private const val TAG = "DialogLaunchAnimator"
|
||||||
|
|
||||||
@@ -52,7 +58,8 @@ class DialogLaunchAnimator(
|
|||||||
private val currentAnimations = hashSetOf<DialogLaunchAnimation>()
|
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
|
* 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
|
* 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
|
* must call dismiss(), hide() and show() on the [Dialog] returned by this function to actually
|
||||||
* dismiss, hide or show the dialog.
|
* 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()) {
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
throw IllegalStateException(
|
throw IllegalStateException(
|
||||||
"showFromView must be called from the main thread and dialog must be created in " +
|
"showFromView must be called from the main thread and dialog must be created in " +
|
||||||
@@ -78,7 +90,8 @@ class DialogLaunchAnimator(
|
|||||||
|
|
||||||
val launchAnimation = DialogLaunchAnimation(
|
val launchAnimation = DialogLaunchAnimation(
|
||||||
context, launchAnimator, hostDialogProvider, view,
|
context, launchAnimator, hostDialogProvider, view,
|
||||||
onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog)
|
onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog,
|
||||||
|
animateBackgroundBoundsChange)
|
||||||
val hostDialog = launchAnimation.hostDialog
|
val hostDialog = launchAnimation.hostDialog
|
||||||
currentAnimations.add(launchAnimation)
|
currentAnimations.add(launchAnimation)
|
||||||
|
|
||||||
@@ -208,7 +221,10 @@ private class DialogLaunchAnimation(
|
|||||||
private val onDialogDismissed: (DialogLaunchAnimation) -> Unit,
|
private val onDialogDismissed: (DialogLaunchAnimation) -> Unit,
|
||||||
|
|
||||||
/** The original dialog whose content will be shown and animate in/out in [hostDialog]. */
|
/** 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
|
* 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)
|
private val hostDialogRoot = FrameLayout(context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The content view of [originalDialog], which will be stolen from that dialog and added to
|
* The parent of the original dialog content view, that serves as a fake window that will have
|
||||||
* [hostDialogRoot].
|
* 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]
|
* The background color of [originalDialogView], taking into consideration the [originalDialog]
|
||||||
@@ -246,6 +263,11 @@ private class DialogLaunchAnimation(
|
|||||||
|
|
||||||
private var isTouchSurfaceGhostDrawn = false
|
private var isTouchSurfaceGhostDrawn = false
|
||||||
private var isOriginalDialogViewLaidOut = false
|
private var isOriginalDialogViewLaidOut = false
|
||||||
|
private var backgroundLayoutListener = if (animateBackgroundBoundsChange) {
|
||||||
|
AnimatedBoundsLayoutListener()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
// Show the host (fullscreen) dialog, to which we will add the stolen dialog view.
|
// 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) {
|
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.
|
// Close the dialog when clicking outside of it.
|
||||||
hostDialogRoot.setOnClickListener { hostDialog.dismiss() }
|
hostDialogRoot.setOnClickListener { hostDialog.dismiss() }
|
||||||
dialogView.isClickable = true
|
dialogView.isClickable = true
|
||||||
@@ -394,17 +413,13 @@ private class DialogLaunchAnimation(
|
|||||||
throw IllegalStateException("Dialogs with no backgrounds on window are not supported")
|
throw IllegalStateException("Dialogs with no backgrounds on window are not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogView.setBackgroundResource(backgroundRes)
|
// Add a parent view to the original dialog view to which we will set the original dialog
|
||||||
originalDialogBackgroundColor =
|
// window background. This View serves as a fake window with background, so that we are sure
|
||||||
GhostedViewLaunchAnimatorController.findGradientDrawable(dialogView.background!!)
|
// that we don't override the dialog view paddings with the window background that usually
|
||||||
?.color
|
// has insets.
|
||||||
?.defaultColor ?: Color.BLACK
|
dialogContentParent.setBackgroundResource(backgroundRes)
|
||||||
|
|
||||||
// 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)
|
|
||||||
hostDialogRoot.addView(
|
hostDialogRoot.addView(
|
||||||
dialogView,
|
dialogContentParent,
|
||||||
|
|
||||||
// We give it the size of its original dialog window.
|
// We give it the size of its original dialog window.
|
||||||
FrameLayout.LayoutParams(
|
FrameLayout.LayoutParams(
|
||||||
@@ -413,10 +428,31 @@ private class DialogLaunchAnimation(
|
|||||||
Gravity.CENTER
|
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.
|
// 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(
|
override fun onLayoutChange(
|
||||||
view: View,
|
view: View,
|
||||||
left: Int,
|
left: Int,
|
||||||
@@ -428,7 +464,7 @@ private class DialogLaunchAnimation(
|
|||||||
oldRight: Int,
|
oldRight: Int,
|
||||||
oldBottom: Int
|
oldBottom: Int
|
||||||
) {
|
) {
|
||||||
dialogView.removeOnLayoutChangeListener(this)
|
dialogContentParent.removeOnLayoutChangeListener(this)
|
||||||
|
|
||||||
isOriginalDialogViewLaidOut = true
|
isOriginalDialogViewLaidOut = true
|
||||||
maybeStartLaunchAnimation()
|
maybeStartLaunchAnimation()
|
||||||
@@ -479,6 +515,13 @@ private class DialogLaunchAnimation(
|
|||||||
if (dismissRequested) {
|
if (dismissRequested) {
|
||||||
hostDialog.dismiss()
|
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
|
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
|
// 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
|
// removed only once we draw the touch surface, to avoid flickering that would
|
||||||
@@ -578,12 +625,10 @@ private class DialogLaunchAnimation(
|
|||||||
onLaunchAnimationStart: () -> Unit = {},
|
onLaunchAnimationStart: () -> Unit = {},
|
||||||
onLaunchAnimationEnd: () -> Unit = {}
|
onLaunchAnimationEnd: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val dialogView = this.originalDialogView!!
|
|
||||||
|
|
||||||
// Create 2 ghost controllers to animate both the dialog and the touch surface in the host
|
// Create 2 ghost controllers to animate both the dialog and the touch surface in the host
|
||||||
// dialog.
|
// dialog.
|
||||||
val startView = if (isLaunching) touchSurface else dialogView
|
val startView = if (isLaunching) touchSurface else dialogContentParent
|
||||||
val endView = if (isLaunching) dialogView else touchSurface
|
val endView = if (isLaunching) dialogContentParent else touchSurface
|
||||||
val startViewController = GhostedViewLaunchAnimatorController(startView)
|
val startViewController = GhostedViewLaunchAnimatorController(startView)
|
||||||
val endViewController = GhostedViewLaunchAnimatorController(endView)
|
val endViewController = GhostedViewLaunchAnimatorController(endView)
|
||||||
startViewController.launchContainer = hostDialogRoot
|
startViewController.launchContainer = hostDialogRoot
|
||||||
@@ -662,4 +707,81 @@ private class DialogLaunchAnimation(
|
|||||||
|
|
||||||
return (touchSurface.parent as? View)?.isShown ?: true
|
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,18 +16,15 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:sysui="http://schemas.android.com/apk/res-auto"
|
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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="24dp"
|
android:padding="24dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
>
|
>
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -89,5 +86,4 @@
|
|||||||
style="@style/Widget.QSDialog.Button"
|
style="@style/Widget.QSDialog.Button"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</FrameLayout>
|
|
||||||
@@ -17,8 +17,7 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical">
|
||||||
android:background="@drawable/rounded_bg_full">
|
|
||||||
|
|
||||||
<!-- Scrollview is necessary to fit everything in landscape layout -->
|
<!-- Scrollview is necessary to fit everything in landscape layout -->
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import com.android.systemui.ForegroundServicesDialog;
|
|||||||
import com.android.systemui.keyguard.WorkLockActivity;
|
import com.android.systemui.keyguard.WorkLockActivity;
|
||||||
import com.android.systemui.people.PeopleSpaceActivity;
|
import com.android.systemui.people.PeopleSpaceActivity;
|
||||||
import com.android.systemui.people.widget.LaunchConversationActivity;
|
import com.android.systemui.people.widget.LaunchConversationActivity;
|
||||||
import com.android.systemui.screenrecord.ScreenRecordDialog;
|
|
||||||
import com.android.systemui.screenshot.LongScreenshotActivity;
|
import com.android.systemui.screenshot.LongScreenshotActivity;
|
||||||
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
|
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
|
||||||
import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
|
import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
|
||||||
@@ -67,12 +66,6 @@ public abstract class DefaultActivityBinder {
|
|||||||
@ClassKey(BrightnessDialog.class)
|
@ClassKey(BrightnessDialog.class)
|
||||||
public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
|
public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
|
||||||
|
|
||||||
/** Inject into ScreenRecordDialog */
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@ClassKey(ScreenRecordDialog.class)
|
|
||||||
public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
|
|
||||||
|
|
||||||
/** Inject into UsbDebuggingActivity. */
|
/** Inject into UsbDebuggingActivity. */
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
package com.android.systemui.qs.tiles;
|
package com.android.systemui.qs.tiles;
|
||||||
|
|
||||||
import android.content.DialogInterface.OnClickListener;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
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.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||||
import com.android.systemui.Prefs;
|
import com.android.systemui.Prefs;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||||
import com.android.systemui.dagger.qualifiers.Background;
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
import com.android.systemui.plugins.ActivityStarter;
|
import com.android.systemui.plugins.ActivityStarter;
|
||||||
@@ -47,6 +47,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
|
|||||||
DataSaverController.Listener{
|
DataSaverController.Listener{
|
||||||
|
|
||||||
private final DataSaverController mDataSaverController;
|
private final DataSaverController mDataSaverController;
|
||||||
|
private final DialogLaunchAnimator mDialogLaunchAnimator;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DataSaverTile(
|
public DataSaverTile(
|
||||||
@@ -58,11 +59,13 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
|
|||||||
StatusBarStateController statusBarStateController,
|
StatusBarStateController statusBarStateController,
|
||||||
ActivityStarter activityStarter,
|
ActivityStarter activityStarter,
|
||||||
QSLogger qsLogger,
|
QSLogger qsLogger,
|
||||||
DataSaverController dataSaverController
|
DataSaverController dataSaverController,
|
||||||
|
DialogLaunchAnimator dialogLaunchAnimator
|
||||||
) {
|
) {
|
||||||
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
|
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
|
||||||
statusBarStateController, activityStarter, qsLogger);
|
statusBarStateController, activityStarter, qsLogger);
|
||||||
mDataSaverController = dataSaverController;
|
mDataSaverController = dataSaverController;
|
||||||
|
mDialogLaunchAnimator = dialogLaunchAnimator;
|
||||||
mDataSaverController.observe(getLifecycle(), this);
|
mDataSaverController.observe(getLifecycle(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,19 +86,28 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
|
|||||||
toggleDataSaver();
|
toggleDataSaver();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Shows dialog first
|
|
||||||
|
// 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);
|
SystemUIDialog dialog = new SystemUIDialog(mContext);
|
||||||
dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
|
dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
|
||||||
dialog.setMessage(com.android.internal.R.string.data_saver_description);
|
dialog.setMessage(com.android.internal.R.string.data_saver_description);
|
||||||
dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
|
dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
|
||||||
(OnClickListener) (dialogInterface, which) -> {
|
(dialogInterface, which) -> {
|
||||||
toggleDataSaver();
|
toggleDataSaver();
|
||||||
Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
|
Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
|
||||||
});
|
});
|
||||||
dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
|
dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
|
||||||
dialog.setShowForAllUsers(true);
|
dialog.setShowForAllUsers(true);
|
||||||
|
|
||||||
|
if (view != null) {
|
||||||
|
mDialogLaunchAnimator.showFromView(dialog, view);
|
||||||
|
} else {
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void toggleDataSaver() {
|
private void toggleDataSaver() {
|
||||||
mState.value = !mDataSaverController.isDataSaverEnabled();
|
mState.value = !mDataSaverController.isDataSaverEnabled();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import com.android.internal.logging.MetricsLogger;
|
import com.android.internal.logging.MetricsLogger;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||||
import com.android.systemui.dagger.qualifiers.Background;
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
import com.android.systemui.plugins.ActivityStarter;
|
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.logging.QSLogger;
|
||||||
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
||||||
import com.android.systemui.screenrecord.RecordingController;
|
import com.android.systemui.screenrecord.RecordingController;
|
||||||
|
import com.android.systemui.screenrecord.ScreenRecordDialog;
|
||||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||||
|
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -49,10 +52,13 @@ import javax.inject.Inject;
|
|||||||
public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
||||||
implements RecordingController.RecordingStateChangeCallback {
|
implements RecordingController.RecordingStateChangeCallback {
|
||||||
private static final String TAG = "ScreenRecordTile";
|
private static final String TAG = "ScreenRecordTile";
|
||||||
private RecordingController mController;
|
private final RecordingController mController;
|
||||||
private KeyguardDismissUtil mKeyguardDismissUtil;
|
private final KeyguardDismissUtil mKeyguardDismissUtil;
|
||||||
|
private final KeyguardStateController mKeyguardStateController;
|
||||||
|
private final Callback mCallback = new Callback();
|
||||||
|
private final DialogLaunchAnimator mDialogLaunchAnimator;
|
||||||
|
|
||||||
private long mMillisUntilFinished = 0;
|
private long mMillisUntilFinished = 0;
|
||||||
private Callback mCallback = new Callback();
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ScreenRecordTile(
|
public ScreenRecordTile(
|
||||||
@@ -65,13 +71,17 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
|||||||
ActivityStarter activityStarter,
|
ActivityStarter activityStarter,
|
||||||
QSLogger qsLogger,
|
QSLogger qsLogger,
|
||||||
RecordingController controller,
|
RecordingController controller,
|
||||||
KeyguardDismissUtil keyguardDismissUtil
|
KeyguardDismissUtil keyguardDismissUtil,
|
||||||
|
KeyguardStateController keyguardStateController,
|
||||||
|
DialogLaunchAnimator dialogLaunchAnimator
|
||||||
) {
|
) {
|
||||||
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
|
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
|
||||||
statusBarStateController, activityStarter, qsLogger);
|
statusBarStateController, activityStarter, qsLogger);
|
||||||
mController = controller;
|
mController = controller;
|
||||||
mController.observe(this, mCallback);
|
mController.observe(this, mCallback);
|
||||||
mKeyguardDismissUtil = keyguardDismissUtil;
|
mKeyguardDismissUtil = keyguardDismissUtil;
|
||||||
|
mKeyguardStateController = keyguardStateController;
|
||||||
|
mDialogLaunchAnimator = dialogLaunchAnimator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -89,7 +99,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
|||||||
} else if (mController.isRecording()) {
|
} else if (mController.isRecording()) {
|
||||||
stopRecording();
|
stopRecording();
|
||||||
} else {
|
} else {
|
||||||
mUiHandler.post(() -> showPrompt());
|
mUiHandler.post(() -> showPrompt(view));
|
||||||
}
|
}
|
||||||
refreshState();
|
refreshState();
|
||||||
}
|
}
|
||||||
@@ -136,15 +146,33 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
|||||||
return mContext.getString(R.string.quick_settings_screen_record_label);
|
return mContext.getString(R.string.quick_settings_screen_record_label);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPrompt() {
|
private void showPrompt(@Nullable View view) {
|
||||||
// Close QS, otherwise the dialog appears beneath it
|
// 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();
|
getHost().collapsePanels();
|
||||||
Intent intent = mController.getPromptIntent();
|
};
|
||||||
|
ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext,
|
||||||
|
onStartRecordingClicked);
|
||||||
|
|
||||||
ActivityStarter.OnDismissAction dismissAction = () -> {
|
ActivityStarter.OnDismissAction dismissAction = () -> {
|
||||||
mHost.getUserContext().startActivity(intent);
|
if (shouldAnimateFromView) {
|
||||||
|
mDialogLaunchAnimator.showFromView(dialog, view);
|
||||||
|
} else {
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false, false);
|
|
||||||
|
mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false /* requiresShadeOpen */,
|
||||||
|
true /* afterKeyguardDone */);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelCountdown() {
|
private void cancelCountdown() {
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ class InternetDialogFactory @Inject constructor(
|
|||||||
canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
|
canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
|
||||||
executor)
|
executor)
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
dialogLaunchAnimator.showFromView(internetDialog!!, view)
|
dialogLaunchAnimator.showFromView(internetDialog!!, view,
|
||||||
|
animateBackgroundBoundsChange = true)
|
||||||
} else {
|
} else {
|
||||||
internetDialog?.show()
|
internetDialog?.show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package com.android.systemui.screenrecord;
|
|||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
@@ -27,10 +26,12 @@ import android.os.UserHandle;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||||
import com.android.systemui.dagger.SysUISingleton;
|
import com.android.systemui.dagger.SysUISingleton;
|
||||||
|
import com.android.systemui.settings.UserContextProvider;
|
||||||
import com.android.systemui.statusbar.policy.CallbackController;
|
import com.android.systemui.statusbar.policy.CallbackController;
|
||||||
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
@@ -44,15 +45,13 @@ import javax.inject.Inject;
|
|||||||
public class RecordingController
|
public class RecordingController
|
||||||
implements CallbackController<RecordingController.RecordingStateChangeCallback> {
|
implements CallbackController<RecordingController.RecordingStateChangeCallback> {
|
||||||
private static final String TAG = "RecordingController";
|
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 mIsStarting;
|
||||||
private boolean mIsRecording;
|
private boolean mIsRecording;
|
||||||
private PendingIntent mStopIntent;
|
private PendingIntent mStopIntent;
|
||||||
private CountDownTimer mCountDownTimer = null;
|
private CountDownTimer mCountDownTimer = null;
|
||||||
private BroadcastDispatcher mBroadcastDispatcher;
|
private BroadcastDispatcher mBroadcastDispatcher;
|
||||||
|
private UserContextProvider mUserContextProvider;
|
||||||
|
|
||||||
protected static final String INTENT_UPDATE_STATE =
|
protected static final String INTENT_UPDATE_STATE =
|
||||||
"com.android.systemui.screenrecord.UPDATE_STATE";
|
"com.android.systemui.screenrecord.UPDATE_STATE";
|
||||||
@@ -88,20 +87,16 @@ public class RecordingController
|
|||||||
* Create a new RecordingController
|
* Create a new RecordingController
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public RecordingController(BroadcastDispatcher broadcastDispatcher) {
|
public RecordingController(BroadcastDispatcher broadcastDispatcher,
|
||||||
|
UserContextProvider userContextProvider) {
|
||||||
mBroadcastDispatcher = broadcastDispatcher;
|
mBroadcastDispatcher = broadcastDispatcher;
|
||||||
|
mUserContextProvider = userContextProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Create a dialog to show screen recording options to the user. */
|
||||||
* Get an intent to show screen recording options to the user.
|
public ScreenRecordDialog createScreenRecordDialog(Context context,
|
||||||
*/
|
@Nullable Runnable onStartRecordingClicked) {
|
||||||
public Intent getPromptIntent() {
|
return new ScreenRecordDialog(context, this, mUserContextProvider, onStartRecordingClicked);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import android.app.PendingIntent;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
@@ -34,34 +33,38 @@ import android.widget.Spinner;
|
|||||||
import android.widget.Switch;
|
import android.widget.Switch;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.settings.UserContextProvider;
|
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 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 DELAY_MS = 3000;
|
||||||
private static final long INTERVAL_MS = 1000;
|
private static final long INTERVAL_MS = 1000;
|
||||||
private static final String TAG = "ScreenRecordDialog";
|
|
||||||
|
|
||||||
private final RecordingController mController;
|
private final RecordingController mController;
|
||||||
private final UserContextProvider mUserContextProvider;
|
private final UserContextProvider mUserContextProvider;
|
||||||
|
@Nullable
|
||||||
|
private final Runnable mOnStartRecordingClicked;
|
||||||
private Switch mTapsSwitch;
|
private Switch mTapsSwitch;
|
||||||
private Switch mAudioSwitch;
|
private Switch mAudioSwitch;
|
||||||
private Spinner mOptions;
|
private Spinner mOptions;
|
||||||
private List<ScreenRecordingAudioSource> mModes;
|
|
||||||
|
|
||||||
@Inject
|
public ScreenRecordDialog(Context context, RecordingController controller,
|
||||||
public ScreenRecordDialog(RecordingController controller,
|
UserContextProvider userContextProvider, @Nullable Runnable onStartRecordingClicked) {
|
||||||
UserContextProvider userContextProvider) {
|
super(context);
|
||||||
mController = controller;
|
mController = controller;
|
||||||
mUserContextProvider = userContextProvider;
|
mUserContextProvider = userContextProvider;
|
||||||
|
mOnStartRecordingClicked = onStartRecordingClicked;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -69,37 +72,35 @@ public class ScreenRecordDialog extends Activity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
Window window = getWindow();
|
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.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
|
||||||
window.setGravity(Gravity.TOP);
|
|
||||||
|
window.setGravity(Gravity.CENTER);
|
||||||
setTitle(R.string.screenrecord_name);
|
setTitle(R.string.screenrecord_name);
|
||||||
|
|
||||||
setContentView(R.layout.screen_record_dialog);
|
setContentView(R.layout.screen_record_dialog);
|
||||||
|
|
||||||
TextView cancelBtn = findViewById(R.id.button_cancel);
|
TextView cancelBtn = findViewById(R.id.button_cancel);
|
||||||
cancelBtn.setOnClickListener(v -> {
|
cancelBtn.setOnClickListener(v -> dismiss());
|
||||||
finish();
|
|
||||||
});
|
|
||||||
|
|
||||||
TextView startBtn = findViewById(R.id.button_start);
|
TextView startBtn = findViewById(R.id.button_start);
|
||||||
startBtn.setOnClickListener(v -> {
|
startBtn.setOnClickListener(v -> {
|
||||||
requestScreenCapture();
|
if (mOnStartRecordingClicked != null) {
|
||||||
finish();
|
// 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<>();
|
requestScreenCapture();
|
||||||
mModes.add(INTERNAL);
|
dismiss();
|
||||||
mModes.add(MIC);
|
});
|
||||||
mModes.add(MIC_AND_INTERNAL);
|
|
||||||
|
|
||||||
mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
|
mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
|
||||||
mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
|
mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
|
||||||
mOptions = findViewById(R.id.screen_recording_options);
|
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,
|
android.R.layout.simple_spinner_dropdown_item,
|
||||||
mModes);
|
MODES);
|
||||||
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
mOptions.setAdapter(a);
|
mOptions.setAdapter(a);
|
||||||
mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
|
mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
|
||||||
@@ -116,7 +117,7 @@ public class ScreenRecordDialog extends Activity {
|
|||||||
PendingIntent startIntent = PendingIntent.getForegroundService(userContext,
|
PendingIntent startIntent = PendingIntent.getForegroundService(userContext,
|
||||||
RecordingService.REQUEST_CODE,
|
RecordingService.REQUEST_CODE,
|
||||||
RecordingService.getStartIntent(
|
RecordingService.getStartIntent(
|
||||||
userContext, RESULT_OK,
|
userContext, Activity.RESULT_OK,
|
||||||
audioMode.ordinal(), showTaps),
|
audioMode.ordinal(), showTaps),
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
PendingIntent stopIntent = PendingIntent.getService(userContext,
|
PendingIntent stopIntent = PendingIntent.getService(userContext,
|
||||||
|
|||||||
@@ -60,9 +60,20 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
assertEquals(0, dialog.findViewById<ViewGroup>(android.R.id.content).childCount)
|
assertEquals(0, dialog.findViewById<ViewGroup>(android.R.id.content).childCount)
|
||||||
assertEquals(1, hostDialogContent.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
|
val hostDialogRoot = hostDialogContent.getChildAt(0) as ViewGroup
|
||||||
assertEquals(1, hostDialogRoot.childCount)
|
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
|
// Hiding/showing/dismissing the dialog should hide/show/dismiss the host dialog given that
|
||||||
// it's a ListenableDialog.
|
// it's a ListenableDialog.
|
||||||
@@ -126,6 +137,11 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class TestDialog(context: Context) : Dialog(context), ListenableDialog {
|
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>()
|
private val listeners = hashSetOf<DialogListener>()
|
||||||
val contentView = View(context)
|
val contentView = View(context)
|
||||||
var onStartCalled = false
|
var onStartCalled = false
|
||||||
@@ -138,6 +154,7 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
|
||||||
setContentView(contentView)
|
setContentView(contentView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,11 @@
|
|||||||
package com.android.systemui.qs.tiles;
|
package com.android.systemui.qs.tiles;
|
||||||
|
|
||||||
import static junit.framework.Assert.assertEquals;
|
import static junit.framework.Assert.assertEquals;
|
||||||
|
import static junit.framework.Assert.assertNotNull;
|
||||||
import static junit.framework.Assert.assertTrue;
|
import static junit.framework.Assert.assertTrue;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -34,6 +36,7 @@ import androidx.test.filters.SmallTest;
|
|||||||
import com.android.internal.logging.MetricsLogger;
|
import com.android.internal.logging.MetricsLogger;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.SysuiTestCase;
|
import com.android.systemui.SysuiTestCase;
|
||||||
|
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||||
import com.android.systemui.classifier.FalsingManagerFake;
|
import com.android.systemui.classifier.FalsingManagerFake;
|
||||||
import com.android.systemui.plugins.ActivityStarter;
|
import com.android.systemui.plugins.ActivityStarter;
|
||||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
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.qs.logging.QSLogger;
|
||||||
import com.android.systemui.screenrecord.RecordingController;
|
import com.android.systemui.screenrecord.RecordingController;
|
||||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||||
|
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
@@ -67,6 +72,10 @@ public class ScreenRecordTileTest extends SysuiTestCase {
|
|||||||
private ActivityStarter mActivityStarter;
|
private ActivityStarter mActivityStarter;
|
||||||
@Mock
|
@Mock
|
||||||
private QSLogger mQSLogger;
|
private QSLogger mQSLogger;
|
||||||
|
@Mock
|
||||||
|
private KeyguardStateController mKeyguardStateController;
|
||||||
|
@Mock
|
||||||
|
private DialogLaunchAnimator mDialogLaunchAnimator;
|
||||||
|
|
||||||
private TestableLooper mTestableLooper;
|
private TestableLooper mTestableLooper;
|
||||||
private ScreenRecordTile mTile;
|
private ScreenRecordTile mTile;
|
||||||
@@ -89,7 +98,9 @@ public class ScreenRecordTileTest extends SysuiTestCase {
|
|||||||
mActivityStarter,
|
mActivityStarter,
|
||||||
mQSLogger,
|
mQSLogger,
|
||||||
mController,
|
mController,
|
||||||
mKeyguardDismissUtil
|
mKeyguardDismissUtil,
|
||||||
|
mKeyguardStateController,
|
||||||
|
mDialogLaunchAnimator
|
||||||
);
|
);
|
||||||
|
|
||||||
mTile.initialize();
|
mTile.initialize();
|
||||||
@@ -112,7 +123,15 @@ public class ScreenRecordTileTest extends SysuiTestCase {
|
|||||||
|
|
||||||
mTile.handleClick(null /* view */);
|
mTile.handleClick(null /* view */);
|
||||||
mTestableLooper.processAllMessages();
|
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
|
// 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.SysuiTestCase;
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||||
|
import com.android.systemui.settings.UserContextProvider;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -52,6 +53,8 @@ public class RecordingControllerTest extends SysuiTestCase {
|
|||||||
private RecordingController.RecordingStateChangeCallback mCallback;
|
private RecordingController.RecordingStateChangeCallback mCallback;
|
||||||
@Mock
|
@Mock
|
||||||
private BroadcastDispatcher mBroadcastDispatcher;
|
private BroadcastDispatcher mBroadcastDispatcher;
|
||||||
|
@Mock
|
||||||
|
private UserContextProvider mUserContextProvider;
|
||||||
|
|
||||||
private RecordingController mController;
|
private RecordingController mController;
|
||||||
|
|
||||||
@@ -60,7 +63,7 @@ public class RecordingControllerTest extends SysuiTestCase {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mController = new RecordingController(mBroadcastDispatcher);
|
mController = new RecordingController(mBroadcastDispatcher, mUserContextProvider);
|
||||||
mController.addCallback(mCallback);
|
mController.addCallback(mCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user