diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 58e3d398553c5..49a01df96530a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -356,10 +356,6 @@
android:exported="false"
android:finishOnTaskLaunch="true" />
-
()
/**
- * 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()
+ }
+ }
}
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
index 543b7d77243b1..9495ee6f31396 100644
--- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -16,78 +16,74 @@
~ limitations under the License.
-->
-
-
+
-
-
-
-
+
-
+
-
+
-
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index c122829c01b60..e43a149a6cd95 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -17,8 +17,7 @@
+ android:orientation="vertical">
implements
DataSaverController.Listener{
private final DataSaverController mDataSaverController;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
@Inject
public DataSaverTile(
@@ -58,11 +59,13 @@ public class DataSaverTile extends QSTileImpl 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 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() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 24b9208d4ed17..8ff75cb3662d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -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
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
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
} else if (mController.isRecording()) {
stopRecording();
} else {
- mUiHandler.post(() -> showPrompt());
+ mUiHandler.post(() -> showPrompt(view));
}
refreshState();
}
@@ -136,15 +146,33 @@ public class ScreenRecordTile extends QSTileImpl
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() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
index 93828b3bcc997..79f7ac3aad3d9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -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()
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 060d7b1a8ab88..1a08878cecfe3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -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 {
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);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index df766f3625e42..1fb88dfe9b523 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -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 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 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,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 54278066b5d3c..209df6b54f8f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -60,9 +60,20 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
assertEquals(0, dialog.findViewById(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()
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)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 964ce01312bf0..e4c5299a0cc52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -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 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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index b7cc651dc24bb..013e58ed99d7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -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);
}