diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 496eb6e6130eb..7e8bc2c1577e2 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -31,7 +31,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actions_container"
app:layout_constraintEnd_toEndOf="@+id/actions_container"
- app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"/>
+ app:layout_constraintBottom_toTopOf="@id/guideline"/>
+
+
-
-
-
-
-
-
-
-
-
+ >
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
new file mode 100644
index 0000000000000..c794d91c02bed
--- /dev/null
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index dfc01501a9997..f3bef920d3113 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -337,7 +337,7 @@
8dp
8dp
- 4dp
+ 6dp
242dp
18dp
4dp
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
new file mode 100644
index 0000000000000..1e531ba7a6e03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -0,0 +1,113 @@
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.drawable.Drawable
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import android.view.ViewTreeObserver
+import android.view.animation.AccelerateDecelerateInterpolator
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.constraintlayout.widget.Guideline
+import com.android.systemui.R
+
+/**
+ * MessageContainerController controls the display of content in the screenshot message container.
+ */
+class MessageContainerController
+constructor(
+ parent: ViewGroup,
+) {
+ private val guideline: Guideline = parent.requireViewById(R.id.guideline)
+ private val messageContainer: ViewGroup =
+ parent.requireViewById(R.id.screenshot_message_container)
+
+ /**
+ * Show a notification under the screenshot view indicating that a work profile screenshot has
+ * been taken and which app can be used to view it.
+ *
+ * @param appName The name of the app to use to view screenshots
+ * @param appIcon Optional icon for the relevant files app
+ * @param onDismiss Runnable to be run when the user dismisses this message
+ */
+ fun showWorkProfileMessage(appName: CharSequence, appIcon: Drawable?, onDismiss: Runnable) {
+ // Eventually this container will support multiple notification types, but for now just make
+ // sure we don't double inflate.
+ if (messageContainer.childCount == 0) {
+ View.inflate(
+ messageContainer.context,
+ R.layout.screenshot_work_profile_first_run,
+ messageContainer
+ )
+ }
+ if (appIcon != null) {
+ // Replace the default icon if one is provided.
+ val imageView: ImageView =
+ messageContainer.requireViewById(R.id.screenshot_message_icon)
+ imageView.setImageDrawable(appIcon)
+ }
+ val messageContent =
+ messageContainer.requireViewById(R.id.screenshot_message_content)
+ messageContent.text =
+ messageContainer.context.getString(
+ R.string.screenshot_work_profile_notification,
+ appName
+ )
+ messageContainer.requireViewById(R.id.message_dismiss_button).setOnClickListener {
+ animateOutMessageContainer()
+ onDismiss.run()
+ }
+
+ // Need the container to be fully measured before animating in (to know animation offset
+ // destination)
+ messageContainer.viewTreeObserver.addOnPreDrawListener(
+ object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ messageContainer.viewTreeObserver.removeOnPreDrawListener(this)
+ animateInMessageContainer()
+ return false
+ }
+ }
+ )
+ }
+
+ private fun animateInMessageContainer() {
+ if (messageContainer.visibility == View.VISIBLE) return
+
+ messageContainer.visibility = View.VISIBLE
+ getAnimator(true).start()
+ }
+
+ private fun animateOutMessageContainer() {
+ getAnimator(false).apply {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ super.onAnimationEnd(animation)
+ messageContainer.visibility = View.INVISIBLE
+ }
+ }
+ )
+ start()
+ }
+ }
+
+ private fun getAnimator(animateIn: Boolean): Animator {
+ val params = messageContainer.layoutParams as MarginLayoutParams
+ val offset = messageContainer.height + params.topMargin + params.bottomMargin
+ val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f)
+ with(anim) {
+ duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS
+ interpolator = AccelerateDecelerateInterpolator()
+ addUpdateListener { valueAnimator: ValueAnimator ->
+ val interpolation = valueAnimator.animatedValue as Float
+ guideline.setGuidelineEnd((interpolation * offset).toInt())
+ messageContainer.alpha = interpolation
+ }
+ }
+ return anim
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 9f7d4f08b42d2..ab13962b405db 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -293,6 +293,7 @@ public class ScreenshotController {
};
private ScreenshotView mScreenshotView;
+ private MessageContainerController mMessageContainerController;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
@@ -631,6 +632,7 @@ public class ScreenshotController {
// Inflate the screenshot layout
mScreenshotView = (ScreenshotView)
LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+ mMessageContainerController = new MessageContainerController(mScreenshotView);
mScreenshotView.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
@@ -1193,7 +1195,8 @@ public class ScreenshotController {
private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
mScreenshotView.setChipIntents(imageData);
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
- mWorkProfileMessageController.onScreenshotTaken(imageData.owner, mScreenshotView);
+ mWorkProfileMessageController.onScreenshotTaken(imageData.owner,
+ mMessageContainerController);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 8b73a57ffa46a..afba7ad24692e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -33,7 +33,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
@@ -82,7 +81,6 @@ import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -102,8 +100,7 @@ import java.util.ArrayList;
* Handles the visual elements and animations for the screenshot flow.
*/
public class ScreenshotView extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener,
- WorkProfileMessageController.WorkProfileMessageDisplay {
+ ViewTreeObserver.OnComputeInternalInsetsListener {
interface ScreenshotViewCallback {
void onUserInteraction();
@@ -123,7 +120,7 @@ public class ScreenshotView extends FrameLayout implements
private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
- private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
+ public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
@@ -142,8 +139,6 @@ public class ScreenshotView extends FrameLayout implements
private ImageView mScrollingScrim;
private DraggableConstraintLayout mScreenshotStatic;
- private ViewGroup mMessageContainer;
- private TextView mMessageContent;
private ImageView mScreenshotPreview;
private ImageView mScreenshotBadge;
private View mScreenshotPreviewBorder;
@@ -295,8 +290,11 @@ public class ScreenshotView extends FrameLayout implements
mDismissButton.getBoundsOnScreen(tmpRect);
swipeRegion.op(tmpRect, Region.Op.UNION);
- mMessageContainer.findViewById(R.id.message_dismiss_button).getBoundsOnScreen(tmpRect);
- swipeRegion.op(tmpRect, Region.Op.UNION);
+ View messageDismiss = findViewById(R.id.message_dismiss_button);
+ if (messageDismiss != null) {
+ messageDismiss.getBoundsOnScreen(tmpRect);
+ swipeRegion.op(tmpRect, Region.Op.UNION);
+ }
return swipeRegion;
}
@@ -352,39 +350,11 @@ public class ScreenshotView extends FrameLayout implements
}
}
- /**
- * Show a notification under the screenshot view indicating that a work profile screenshot has
- * been taken and which app can be used to view it.
- *
- * @param appName The name of the app to use to view screenshots
- * @param appIcon Optional icon for the relevant files app
- * @param onDismiss Runnable to be run when the user dismisses this message
- */
- @Override
- public void showWorkProfileMessage(CharSequence appName, @Nullable Drawable appIcon,
- Runnable onDismiss) {
- if (appIcon != null) {
- // Replace the default icon if one is provided.
- ImageView imageView = mMessageContainer.findViewById(R.id.screenshot_message_icon);
- imageView.setImageDrawable(appIcon);
- }
- mMessageContent.setText(
- mContext.getString(R.string.screenshot_work_profile_notification, appName));
- mMessageContainer.setVisibility(VISIBLE);
- mMessageContainer.findViewById(R.id.message_dismiss_button).setOnClickListener((v) -> {
- mMessageContainer.setVisibility(View.GONE);
- onDismiss.run();
- });
- }
-
@Override // View
protected void onFinishInflate() {
+ super.onFinishInflate();
mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
- mMessageContainer =
- requireNonNull(mScreenshotStatic.findViewById(R.id.screenshot_message_container));
- mMessageContent =
- requireNonNull(mMessageContainer.findViewById(R.id.screenshot_message_content));
mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
mScreenshotPreviewBorder = requireNonNull(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index 5d7e56f6c98a3..b4a07d4321a93 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -40,10 +40,10 @@ constructor(
) {
/**
- * Determine if a message should be shown to the user, send message details to messageDisplay if
- * appropriate.
+ * Determine if a message should be shown to the user, send message details to
+ * MessageContainerController if appropriate.
*/
- fun onScreenshotTaken(userHandle: UserHandle, messageDisplay: WorkProfileMessageDisplay) {
+ fun onScreenshotTaken(userHandle: UserHandle, messageContainer: MessageContainerController) {
if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
var badgedIcon: Drawable? = null
var label: CharSequence? = null
@@ -65,7 +65,9 @@ constructor(
val badgedLabel =
packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
- messageDisplay.showWorkProfileMessage(badgedLabel, badgedIcon) { onMessageDismissed() }
+ messageContainer.showWorkProfileMessage(badgedLabel, badgedIcon) {
+ onMessageDismissed()
+ }
}
}
@@ -89,15 +91,6 @@ constructor(
private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
- /** UI that can show work profile messages (ScreenshotView in practice) */
- interface WorkProfileMessageDisplay {
- /**
- * Show the given message and icon, calling onDismiss if the user explicitly dismisses the
- * message.
- */
- fun showWorkProfileMessage(text: CharSequence, icon: Drawable?, onDismiss: Runnable)
- }
-
companion object {
const val TAG = "WorkProfileMessageCtrl"
const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index bd04b3ccc039c..e8905ab22bc0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -65,7 +65,7 @@ public class WorkProfileMessageControllerTest {
@Mock
private Context mContext;
@Mock
- private WorkProfileMessageController.WorkProfileMessageDisplay mMessageDisplay;
+ private MessageContainerController mMessageDisplay;
@Mock
private Drawable mActivityIcon;
@Mock