Merge "Entrance and exit animations for work profile first run." into tm-qpr-dev
This commit is contained in:
@@ -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"/>
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/actions_container"
|
||||
android:layout_width="0dp"
|
||||
@@ -127,57 +127,28 @@
|
||||
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
|
||||
android:elevation="7dp"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_end="0dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/screenshot_message_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
|
||||
android:layout_marginVertical="4dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
|
||||
android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
|
||||
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
|
||||
android:elevation="4dp"
|
||||
android:background="@drawable/action_chip_container_background"
|
||||
android:visibility="gone"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintTop_toBottomOf="@id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/screenshot_message_icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:src="@drawable/ic_work_app_badge"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/screenshot_message_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/screenshot_message_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
|
||||
app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/message_dismiss_button"
|
||||
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
|
||||
android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
|
||||
app:layout_constraintStart_toEndOf="@id/screenshot_message_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:contentDescription="@string/screenshot_dismiss_work_profile">
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/overlay_dismiss_button_margin"
|
||||
android:src="@drawable/overlay_cancel"/>
|
||||
</FrameLayout>
|
||||
|
||||
>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.android.systemui.screenshot.DraggableConstraintLayout>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<ImageView
|
||||
android:id="@+id/screenshot_message_icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:src="@drawable/ic_work_app_badge"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/screenshot_message_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/screenshot_message_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
|
||||
app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/message_dismiss_button"
|
||||
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
|
||||
android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
|
||||
app:layout_constraintStart_toEndOf="@id/screenshot_message_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:contentDescription="@string/screenshot_dismiss_work_profile">
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/overlay_dismiss_button_margin"
|
||||
android:src="@drawable/overlay_cancel"/>
|
||||
</FrameLayout>
|
||||
</merge>
|
||||
@@ -337,7 +337,7 @@
|
||||
<!-- Used for both start and bottom margin of the preview, relative to the action container -->
|
||||
<dimen name="overlay_preview_container_margin">8dp</dimen>
|
||||
<dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
|
||||
<dimen name="overlay_action_container_margin_bottom">4dp</dimen>
|
||||
<dimen name="overlay_action_container_margin_bottom">6dp</dimen>
|
||||
<dimen name="overlay_bg_protection_height">242dp</dimen>
|
||||
<dimen name="overlay_action_container_corner_radius">18dp</dimen>
|
||||
<dimen name="overlay_action_container_padding_vertical">4dp</dimen>
|
||||
|
||||
@@ -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<ImageView>(R.id.screenshot_message_icon)
|
||||
imageView.setImageDrawable(appIcon)
|
||||
}
|
||||
val messageContent =
|
||||
messageContainer.requireViewById<TextView>(R.id.screenshot_message_content)
|
||||
messageContent.text =
|
||||
messageContainer.context.getString(
|
||||
R.string.screenshot_work_profile_notification,
|
||||
appName
|
||||
)
|
||||
messageContainer.requireViewById<View>(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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -65,7 +65,7 @@ public class WorkProfileMessageControllerTest {
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private WorkProfileMessageController.WorkProfileMessageDisplay mMessageDisplay;
|
||||
private MessageContainerController mMessageDisplay;
|
||||
@Mock
|
||||
private Drawable mActivityIcon;
|
||||
@Mock
|
||||
|
||||
Reference in New Issue
Block a user