• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.screenshot;
18 
19 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
20 
21 import static com.android.internal.jank.InteractionJankMonitor.CUJ_TAKE_SCREENSHOT;
22 import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
23 import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
24 import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
25 import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
26 import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
27 import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
28 import static com.android.systemui.screenshot.LogConfig.logTag;
29 
30 import static java.util.Objects.requireNonNull;
31 
32 import android.animation.Animator;
33 import android.animation.AnimatorListenerAdapter;
34 import android.animation.AnimatorSet;
35 import android.animation.ValueAnimator;
36 import android.app.ActivityManager;
37 import android.app.Notification;
38 import android.app.PendingIntent;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.res.ColorStateList;
42 import android.content.res.Resources;
43 import android.graphics.Bitmap;
44 import android.graphics.BlendMode;
45 import android.graphics.Color;
46 import android.graphics.Insets;
47 import android.graphics.Matrix;
48 import android.graphics.PointF;
49 import android.graphics.Rect;
50 import android.graphics.Region;
51 import android.graphics.drawable.BitmapDrawable;
52 import android.graphics.drawable.ColorDrawable;
53 import android.graphics.drawable.Drawable;
54 import android.graphics.drawable.Icon;
55 import android.graphics.drawable.InsetDrawable;
56 import android.graphics.drawable.LayerDrawable;
57 import android.os.Looper;
58 import android.os.RemoteException;
59 import android.util.AttributeSet;
60 import android.util.DisplayMetrics;
61 import android.util.Log;
62 import android.util.MathUtils;
63 import android.view.Choreographer;
64 import android.view.Display;
65 import android.view.DisplayCutout;
66 import android.view.GestureDetector;
67 import android.view.LayoutInflater;
68 import android.view.MotionEvent;
69 import android.view.ScrollCaptureResponse;
70 import android.view.TouchDelegate;
71 import android.view.View;
72 import android.view.ViewGroup;
73 import android.view.ViewTreeObserver;
74 import android.view.WindowInsets;
75 import android.view.WindowManager;
76 import android.view.WindowMetrics;
77 import android.view.accessibility.AccessibilityManager;
78 import android.view.animation.AnimationUtils;
79 import android.view.animation.Interpolator;
80 import android.widget.FrameLayout;
81 import android.widget.HorizontalScrollView;
82 import android.widget.ImageView;
83 import android.widget.LinearLayout;
84 
85 import androidx.constraintlayout.widget.ConstraintLayout;
86 
87 import com.android.internal.jank.InteractionJankMonitor;
88 import com.android.internal.logging.UiEventLogger;
89 import com.android.systemui.R;
90 import com.android.systemui.flags.FeatureFlags;
91 import com.android.systemui.flags.Flags;
92 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
93 import com.android.systemui.shared.system.InputChannelCompat;
94 import com.android.systemui.shared.system.InputMonitorCompat;
95 import com.android.systemui.shared.system.QuickStepContract;
96 
97 import java.util.ArrayList;
98 
99 /**
100  * Handles the visual elements and animations for the screenshot flow.
101  */
102 public class ScreenshotView extends FrameLayout implements
103         ViewTreeObserver.OnComputeInternalInsetsListener {
104 
105     interface ScreenshotViewCallback {
onUserInteraction()106         void onUserInteraction();
107 
onDismiss()108         void onDismiss();
109 
110         /** DOWN motion event was observed outside of the touchable areas of this view. */
onTouchOutside()111         void onTouchOutside();
112     }
113 
114     private static final String TAG = logTag(ScreenshotView.class);
115 
116     private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
117     private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217;
118     // delay before starting to fade in dismiss button
119     private static final long SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS = 200;
120     private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
121     private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
122     private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
123     public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
124     private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
125     private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
126     private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
127 
128     private final Resources mResources;
129     private final Interpolator mFastOutSlowIn;
130     private final DisplayMetrics mDisplayMetrics;
131     private final float mFixedSize;
132     private final AccessibilityManager mAccessibilityManager;
133     private final GestureDetector mSwipeDetector;
134 
135     private int mDefaultDisplay = Display.DEFAULT_DISPLAY;
136     private int mNavMode;
137     private boolean mOrientationPortrait;
138     private boolean mDirectionLTR;
139 
140     private ImageView mScrollingScrim;
141     private DraggableConstraintLayout mScreenshotStatic;
142     private ImageView mScreenshotPreview;
143     private ImageView mScreenshotBadge;
144     private View mScreenshotPreviewBorder;
145     private ImageView mScrollablePreview;
146     private ImageView mScreenshotFlash;
147     private ImageView mActionsContainerBackground;
148     private HorizontalScrollView mActionsContainer;
149     private LinearLayout mActionsView;
150     private FrameLayout mDismissButton;
151     private OverlayActionChip mShareChip;
152     private OverlayActionChip mEditChip;
153     private OverlayActionChip mScrollChip;
154     private OverlayActionChip mQuickShareChip;
155 
156     private UiEventLogger mUiEventLogger;
157     private ScreenshotViewCallback mCallbacks;
158     private boolean mPendingSharedTransition;
159     private InputMonitorCompat mInputMonitor;
160     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
161     private boolean mShowScrollablePreview;
162     private String mPackageName = "";
163 
164     private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
165     private PendingInteraction mPendingInteraction;
166     // Should only be set/used if the SCREENSHOT_METADATA flag is set.
167     private ScreenshotData mScreenshotData;
168 
169     private final InteractionJankMonitor mInteractionJankMonitor;
170     private long mDefaultTimeoutOfTimeoutHandler;
171     private ActionIntentExecutor mActionExecutor;
172     private FeatureFlags mFlags;
173 
174     private enum PendingInteraction {
175         PREVIEW,
176         EDIT,
177         SHARE,
178         QUICK_SHARE
179     }
180 
ScreenshotView(Context context)181     public ScreenshotView(Context context) {
182         this(context, null);
183     }
184 
ScreenshotView(Context context, AttributeSet attrs)185     public ScreenshotView(Context context, AttributeSet attrs) {
186         this(context, attrs, 0);
187     }
188 
ScreenshotView(Context context, AttributeSet attrs, int defStyleAttr)189     public ScreenshotView(Context context, AttributeSet attrs, int defStyleAttr) {
190         this(context, attrs, defStyleAttr, 0);
191     }
192 
ScreenshotView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)193     public ScreenshotView(
194             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
195         super(context, attrs, defStyleAttr, defStyleRes);
196         mResources = mContext.getResources();
197         mInteractionJankMonitor = getInteractionJankMonitorInstance();
198 
199         mFixedSize = mResources.getDimensionPixelSize(R.dimen.overlay_x_scale);
200 
201         // standard material ease
202         mFastOutSlowIn =
203                 AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
204 
205         mDisplayMetrics = new DisplayMetrics();
206         mContext.getDisplay().getRealMetrics(mDisplayMetrics);
207 
208         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
209 
210         mSwipeDetector = new GestureDetector(mContext,
211                 new GestureDetector.SimpleOnGestureListener() {
212                     final Rect mActionsRect = new Rect();
213 
214                     @Override
215                     public boolean onScroll(
216                             MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
217                         mActionsContainer.getBoundsOnScreen(mActionsRect);
218                         // return true if we aren't in the actions bar, or if we are but it isn't
219                         // scrollable in the direction of movement
220                         return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
221                                 || !mActionsContainer.canScrollHorizontally((int) distanceX);
222                     }
223                 });
224         mSwipeDetector.setIsLongpressEnabled(false);
225         addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
226             @Override
227             public void onViewAttachedToWindow(View v) {
228                 startInputListening();
229             }
230 
231             @Override
232             public void onViewDetachedFromWindow(View v) {
233                 stopInputListening();
234             }
235         });
236     }
237 
getInteractionJankMonitorInstance()238     private InteractionJankMonitor getInteractionJankMonitorInstance() {
239         return InteractionJankMonitor.getInstance();
240     }
241 
setDefaultTimeoutMillis(long timeout)242     void setDefaultTimeoutMillis(long timeout) {
243         mDefaultTimeoutOfTimeoutHandler = timeout;
244     }
245 
hideScrollChip()246     public void hideScrollChip() {
247         mScrollChip.setVisibility(View.GONE);
248     }
249 
250     /**
251      * Called to display the scroll action chip when support is detected.
252      *
253      * @param packageName the owning package of the window to be captured
254      * @param onClick     the action to take when the chip is clicked.
255      */
showScrollChip(String packageName, Runnable onClick)256     public void showScrollChip(String packageName, Runnable onClick) {
257         if (DEBUG_SCROLL) {
258             Log.d(TAG, "Showing Scroll option");
259         }
260         mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
261         mScrollChip.setVisibility(VISIBLE);
262         mScrollChip.setOnClickListener((v) -> {
263             if (DEBUG_INPUT) {
264                 Log.d(TAG, "scroll chip tapped");
265             }
266             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
267                     packageName);
268             onClick.run();
269         });
270     }
271 
272     @Override // ViewTreeObserver.OnComputeInternalInsetsListener
onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo)273     public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
274         inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
275         inoutInfo.touchableRegion.set(getTouchRegion(true));
276     }
277 
getSwipeRegion()278     private Region getSwipeRegion() {
279         Region swipeRegion = new Region();
280 
281         final Rect tmpRect = new Rect();
282         mScreenshotPreview.getBoundsOnScreen(tmpRect);
283         tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
284                 (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
285         swipeRegion.op(tmpRect, Region.Op.UNION);
286         mActionsContainerBackground.getBoundsOnScreen(tmpRect);
287         tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
288                 (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
289         swipeRegion.op(tmpRect, Region.Op.UNION);
290         mDismissButton.getBoundsOnScreen(tmpRect);
291         swipeRegion.op(tmpRect, Region.Op.UNION);
292 
293         View messageDismiss = findViewById(R.id.message_dismiss_button);
294         if (messageDismiss != null) {
295             messageDismiss.getBoundsOnScreen(tmpRect);
296             swipeRegion.op(tmpRect, Region.Op.UNION);
297         }
298 
299         return swipeRegion;
300     }
301 
getTouchRegion(boolean includeScrim)302     private Region getTouchRegion(boolean includeScrim) {
303         Region touchRegion = getSwipeRegion();
304 
305         if (includeScrim && mScrollingScrim.getVisibility() == View.VISIBLE) {
306             final Rect tmpRect = new Rect();
307             mScrollingScrim.getBoundsOnScreen(tmpRect);
308             touchRegion.op(tmpRect, Region.Op.UNION);
309         }
310 
311         if (QuickStepContract.isGesturalMode(mNavMode)) {
312             final WindowManager wm = mContext.getSystemService(WindowManager.class);
313             final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
314             final Insets gestureInsets = windowMetrics.getWindowInsets().getInsets(
315                     WindowInsets.Type.systemGestures());
316             // Receive touches in gesture insets such that they don't cause TOUCH_OUTSIDE
317             Rect inset = new Rect(0, 0, gestureInsets.left, mDisplayMetrics.heightPixels);
318             touchRegion.op(inset, Region.Op.UNION);
319             inset.set(mDisplayMetrics.widthPixels - gestureInsets.right, 0,
320                     mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
321             touchRegion.op(inset, Region.Op.UNION);
322         }
323         return touchRegion;
324     }
325 
startInputListening()326     private void startInputListening() {
327         stopInputListening();
328         mInputMonitor = new InputMonitorCompat("Screenshot", mDefaultDisplay);
329         mInputEventReceiver = mInputMonitor.getInputReceiver(
330                 Looper.getMainLooper(), Choreographer.getInstance(), ev -> {
331                     if (ev instanceof MotionEvent) {
332                         MotionEvent event = (MotionEvent) ev;
333                         if (event.getActionMasked() == MotionEvent.ACTION_DOWN
334                                 && !getTouchRegion(false).contains(
335                                 (int) event.getRawX(), (int) event.getRawY())) {
336                             mCallbacks.onTouchOutside();
337                         }
338                     }
339                 });
340     }
341 
stopInputListening()342     void stopInputListening() {
343         if (mInputMonitor != null) {
344             mInputMonitor.dispose();
345             mInputMonitor = null;
346         }
347         if (mInputEventReceiver != null) {
348             mInputEventReceiver.dispose();
349             mInputEventReceiver = null;
350         }
351     }
352 
353     @Override // View
onFinishInflate()354     protected void onFinishInflate() {
355         super.onFinishInflate();
356         mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
357         mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
358         mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
359 
360         mScreenshotPreviewBorder = requireNonNull(
361                 findViewById(R.id.screenshot_preview_border));
362         mScreenshotPreview.setClipToOutline(true);
363         mScreenshotBadge = requireNonNull(findViewById(R.id.screenshot_badge));
364 
365         mActionsContainerBackground = requireNonNull(findViewById(
366                 R.id.actions_container_background));
367         mActionsContainer = requireNonNull(findViewById(R.id.actions_container));
368         mActionsView = requireNonNull(findViewById(R.id.screenshot_actions));
369         mDismissButton = requireNonNull(findViewById(R.id.screenshot_dismiss_button));
370         mScrollablePreview = requireNonNull(findViewById(R.id.screenshot_scrollable_preview));
371         mScreenshotFlash = requireNonNull(findViewById(R.id.screenshot_flash));
372         mShareChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_share_chip));
373         mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
374         mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
375 
376         int swipePaddingPx = (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, SWIPE_PADDING_DP);
377         TouchDelegate previewDelegate = new TouchDelegate(
378                 new Rect(swipePaddingPx, swipePaddingPx, swipePaddingPx, swipePaddingPx),
379                 mScreenshotPreview);
380         mScreenshotPreview.setTouchDelegate(previewDelegate);
381         TouchDelegate actionsDelegate = new TouchDelegate(
382                 new Rect(swipePaddingPx, swipePaddingPx, swipePaddingPx, swipePaddingPx),
383                 mActionsContainerBackground);
384         mActionsContainerBackground.setTouchDelegate(actionsDelegate);
385 
386         setFocusable(true);
387         mActionsContainer.setScrollX(0);
388 
389         mNavMode = getResources().getInteger(
390                 com.android.internal.R.integer.config_navBarInteractionMode);
391         mOrientationPortrait =
392                 getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
393         mDirectionLTR =
394                 getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
395 
396         // Get focus so that the key events go to the layout.
397         setFocusableInTouchMode(true);
398         requestFocus();
399 
400         mScreenshotStatic.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
401             @Override
402             public void onInteraction() {
403                 mCallbacks.onUserInteraction();
404             }
405 
406             @Override
407             public void onSwipeDismissInitiated(Animator animator) {
408                 if (DEBUG_DISMISS) {
409                     Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture");
410                 }
411                 mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
412                         mPackageName);
413             }
414 
415             @Override
416             public void onDismissComplete() {
417                 if (mInteractionJankMonitor.isInstrumenting(CUJ_TAKE_SCREENSHOT)) {
418                     mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
419                 }
420                 mCallbacks.onDismiss();
421             }
422         });
423     }
424 
getScreenshotPreview()425     View getScreenshotPreview() {
426         return mScreenshotPreview;
427     }
428 
429     /**
430      * Set up the logger and callback on dismissal.
431      *
432      * Note: must be called before any other (non-constructor) method or null pointer exceptions
433      * may occur.
434      */
init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, ActionIntentExecutor actionExecutor, FeatureFlags flags)435     void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks,
436             ActionIntentExecutor actionExecutor, FeatureFlags flags) {
437         mUiEventLogger = uiEventLogger;
438         mCallbacks = callbacks;
439         mActionExecutor = actionExecutor;
440         mFlags = flags;
441     }
442 
setScreenshot(Bitmap bitmap, Insets screenInsets)443     void setScreenshot(Bitmap bitmap, Insets screenInsets) {
444         mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
445     }
446 
setScreenshot(ScreenshotData screenshot)447     void setScreenshot(ScreenshotData screenshot) {
448         mScreenshotData = screenshot;
449         setScreenshot(screenshot.getBitmap(), screenshot.getInsets());
450         mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, screenshot.getBitmap(),
451                 screenshot.getInsets()));
452     }
453 
setPackageName(String packageName)454     void setPackageName(String packageName) {
455         mPackageName = packageName;
456     }
457 
setDefaultDisplay(int displayId)458     void setDefaultDisplay(int displayId) {
459         mDefaultDisplay = displayId;
460     }
461 
updateInsets(WindowInsets insets)462     void updateInsets(WindowInsets insets) {
463         int orientation = mContext.getResources().getConfiguration().orientation;
464         mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
465         FrameLayout.LayoutParams p =
466                 (FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams();
467         DisplayCutout cutout = insets.getDisplayCutout();
468         Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
469         if (cutout == null) {
470             p.setMargins(0, 0, 0, navBarInsets.bottom);
471         } else {
472             Insets waterfall = cutout.getWaterfallInsets();
473             if (mOrientationPortrait) {
474                 p.setMargins(
475                         waterfall.left,
476                         Math.max(cutout.getSafeInsetTop(), waterfall.top),
477                         waterfall.right,
478                         Math.max(cutout.getSafeInsetBottom(),
479                                 Math.max(navBarInsets.bottom, waterfall.bottom)));
480             } else {
481                 p.setMargins(
482                         Math.max(cutout.getSafeInsetLeft(), waterfall.left),
483                         waterfall.top,
484                         Math.max(cutout.getSafeInsetRight(), waterfall.right),
485                         Math.max(navBarInsets.bottom, waterfall.bottom));
486             }
487         }
488         mScreenshotStatic.setLayoutParams(p);
489         mScreenshotStatic.requestLayout();
490     }
491 
updateOrientation(WindowInsets insets)492     void updateOrientation(WindowInsets insets) {
493         int orientation = mContext.getResources().getConfiguration().orientation;
494         mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
495         updateInsets(insets);
496         ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
497         if (mOrientationPortrait) {
498             params.width = (int) mFixedSize;
499             params.height = LayoutParams.WRAP_CONTENT;
500             mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_START);
501         } else {
502             params.width = LayoutParams.WRAP_CONTENT;
503             params.height = (int) mFixedSize;
504             mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_END);
505         }
506 
507         mScreenshotPreview.setLayoutParams(params);
508     }
509 
createScreenshotDropInAnimation(Rect bounds, boolean showFlash)510     AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
511         if (DEBUG_ANIM) {
512             Log.d(TAG, "createAnim: bounds=" + bounds + " showFlash=" + showFlash);
513         }
514 
515         Rect targetPosition = new Rect();
516         mScreenshotPreview.getHitRect(targetPosition);
517 
518         // ratio of preview width, end vs. start size
519         float cornerScale =
520                 mFixedSize / (mOrientationPortrait ? bounds.width() : bounds.height());
521         final float currentScale = 1 / cornerScale;
522 
523         AnimatorSet dropInAnimation = new AnimatorSet();
524         ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
525         flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
526         flashInAnimator.setInterpolator(mFastOutSlowIn);
527         flashInAnimator.addUpdateListener(animation ->
528                 mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
529 
530         ValueAnimator flashOutAnimator = ValueAnimator.ofFloat(1, 0);
531         flashOutAnimator.setDuration(SCREENSHOT_FLASH_OUT_DURATION_MS);
532         flashOutAnimator.setInterpolator(mFastOutSlowIn);
533         flashOutAnimator.addUpdateListener(animation ->
534                 mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
535 
536         // animate from the current location, to the static preview location
537         final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
538         final PointF finalPos = new PointF(targetPosition.exactCenterX(),
539                 targetPosition.exactCenterY());
540 
541         // Shift to screen coordinates so that the animation runs on top of the entire screen,
542         // including e.g. bars covering the display cutout.
543         int[] locInScreen = mScreenshotPreview.getLocationOnScreen();
544         startPos.offset(targetPosition.left - locInScreen[0], targetPosition.top - locInScreen[1]);
545 
546         if (DEBUG_ANIM) {
547             Log.d(TAG, "toCorner: startPos=" + startPos);
548             Log.d(TAG, "toCorner: finalPos=" + finalPos);
549         }
550 
551         ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
552         toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
553 
554         toCorner.addListener(new AnimatorListenerAdapter() {
555             @Override
556             public void onAnimationStart(Animator animation) {
557                 mScreenshotPreview.setScaleX(currentScale);
558                 mScreenshotPreview.setScaleY(currentScale);
559                 mScreenshotPreview.setVisibility(View.VISIBLE);
560                 if (mAccessibilityManager.isEnabled()) {
561                     mDismissButton.setAlpha(0);
562                     mDismissButton.setVisibility(View.VISIBLE);
563                 }
564             }
565         });
566 
567         float xPositionPct =
568                 SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
569         float dismissPct =
570                 SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
571         float scalePct =
572                 SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
573         toCorner.addUpdateListener(animation -> {
574             float t = animation.getAnimatedFraction();
575             if (t < scalePct) {
576                 float scale = MathUtils.lerp(
577                         currentScale, 1, mFastOutSlowIn.getInterpolation(t / scalePct));
578                 mScreenshotPreview.setScaleX(scale);
579                 mScreenshotPreview.setScaleY(scale);
580             } else {
581                 mScreenshotPreview.setScaleX(1);
582                 mScreenshotPreview.setScaleY(1);
583             }
584 
585             if (t < xPositionPct) {
586                 float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
587                         mFastOutSlowIn.getInterpolation(t / xPositionPct));
588                 mScreenshotPreview.setX(xCenter - mScreenshotPreview.getWidth() / 2f);
589             } else {
590                 mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
591             }
592             float yCenter = MathUtils.lerp(
593                     startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t));
594             mScreenshotPreview.setY(yCenter - mScreenshotPreview.getHeight() / 2f);
595 
596             if (t >= dismissPct) {
597                 mDismissButton.setAlpha((t - dismissPct) / (1 - dismissPct));
598                 float currentX = mScreenshotPreview.getX();
599                 float currentY = mScreenshotPreview.getY();
600                 mDismissButton.setY(currentY - mDismissButton.getHeight() / 2f);
601                 if (mDirectionLTR) {
602                     mDismissButton.setX(currentX + mScreenshotPreview.getWidth()
603                             - mDismissButton.getWidth() / 2f);
604                 } else {
605                     mDismissButton.setX(currentX - mDismissButton.getWidth() / 2f);
606                 }
607             }
608         });
609 
610         mScreenshotFlash.setAlpha(0f);
611         mScreenshotFlash.setVisibility(View.VISIBLE);
612 
613         ValueAnimator borderFadeIn = ValueAnimator.ofFloat(0, 1);
614         borderFadeIn.setDuration(100);
615         borderFadeIn.addUpdateListener((animation) -> {
616             float borderAlpha = animation.getAnimatedFraction();
617             mScreenshotPreviewBorder.setAlpha(borderAlpha);
618             mScreenshotBadge.setAlpha(borderAlpha);
619         });
620 
621         if (showFlash) {
622             dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
623             dropInAnimation.play(flashOutAnimator).with(toCorner);
624         } else {
625             dropInAnimation.play(toCorner);
626         }
627         dropInAnimation.play(borderFadeIn).after(toCorner);
628 
629         dropInAnimation.addListener(new AnimatorListenerAdapter() {
630             @Override
631             public void onAnimationCancel(Animator animation) {
632                 mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
633             }
634 
635             @Override
636             public void onAnimationStart(Animator animation) {
637                 InteractionJankMonitor.Configuration.Builder builder =
638                         InteractionJankMonitor.Configuration.Builder.withView(
639                                         CUJ_TAKE_SCREENSHOT, mScreenshotPreview)
640                                 .setTag("DropIn");
641                 mInteractionJankMonitor.begin(builder);
642             }
643 
644             @Override
645             public void onAnimationEnd(Animator animation) {
646                 if (DEBUG_ANIM) {
647                     Log.d(TAG, "drop-in animation ended");
648                 }
649                 mDismissButton.setOnClickListener(view -> {
650                     if (DEBUG_INPUT) {
651                         Log.d(TAG, "dismiss button clicked");
652                     }
653                     mUiEventLogger.log(
654                             ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, 0, mPackageName);
655                     animateDismissal();
656                 });
657                 mDismissButton.setAlpha(1);
658                 float dismissOffset = mDismissButton.getWidth() / 2f;
659                 float finalDismissX = mDirectionLTR
660                         ? finalPos.x - dismissOffset + bounds.width() * cornerScale / 2f
661                         : finalPos.x - dismissOffset - bounds.width() * cornerScale / 2f;
662                 mDismissButton.setX(finalDismissX);
663                 mDismissButton.setY(
664                         finalPos.y - dismissOffset - bounds.height() * cornerScale / 2f);
665                 mScreenshotPreview.setScaleX(1);
666                 mScreenshotPreview.setScaleY(1);
667                 mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
668                 mScreenshotPreview.setY(finalPos.y - mScreenshotPreview.getHeight() / 2f);
669                 requestLayout();
670                 mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
671                 createScreenshotActionsShadeAnimation().start();
672             }
673         });
674 
675         return dropInAnimation;
676     }
677 
createScreenshotActionsShadeAnimation()678     ValueAnimator createScreenshotActionsShadeAnimation() {
679         // By default the activities won't be able to start immediately; override this to keep
680         // the same behavior as if started from a notification
681         try {
682             ActivityManager.getService().resumeAppSwitches();
683         } catch (RemoteException e) {
684         }
685 
686         ArrayList<OverlayActionChip> chips = new ArrayList<>();
687 
688         mShareChip.setContentDescription(mContext.getString(R.string.screenshot_share_description));
689         mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
690         mShareChip.setOnClickListener(v -> {
691             mShareChip.setIsPending(true);
692             mEditChip.setIsPending(false);
693             if (mQuickShareChip != null) {
694                 mQuickShareChip.setIsPending(false);
695             }
696             mPendingInteraction = PendingInteraction.SHARE;
697         });
698         chips.add(mShareChip);
699 
700         mEditChip.setContentDescription(
701                 mContext.getString(R.string.screenshot_edit_description));
702         mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit),
703                 true);
704         mEditChip.setOnClickListener(v -> {
705             mEditChip.setIsPending(true);
706             mShareChip.setIsPending(false);
707             if (mQuickShareChip != null) {
708                 mQuickShareChip.setIsPending(false);
709             }
710             mPendingInteraction = PendingInteraction.EDIT;
711         });
712         chips.add(mEditChip);
713 
714         mScreenshotPreview.setOnClickListener(v -> {
715             mShareChip.setIsPending(false);
716             mEditChip.setIsPending(false);
717             if (mQuickShareChip != null) {
718                 mQuickShareChip.setIsPending(false);
719             }
720             mPendingInteraction = PendingInteraction.PREVIEW;
721         });
722 
723         mScrollChip.setText(mContext.getString(R.string.screenshot_scroll_label));
724         mScrollChip.setIcon(Icon.createWithResource(mContext,
725                 R.drawable.ic_screenshot_scroll), true);
726         chips.add(mScrollChip);
727 
728         // remove the margin from the last chip so that it's correctly aligned with the end
729         LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
730                 mActionsView.getChildAt(0).getLayoutParams();
731         params.setMarginEnd(0);
732         mActionsView.getChildAt(0).setLayoutParams(params);
733 
734         ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
735         animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
736         float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
737                 / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
738         mActionsContainer.setAlpha(0f);
739         mActionsContainerBackground.setAlpha(0f);
740         mActionsContainer.setVisibility(View.VISIBLE);
741         mActionsContainerBackground.setVisibility(View.VISIBLE);
742 
743         animator.addListener(new AnimatorListenerAdapter() {
744             @Override
745             public void onAnimationCancel(Animator animation) {
746                 mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
747             }
748 
749             @Override
750             public void onAnimationEnd(Animator animation) {
751                 mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
752             }
753 
754             @Override
755             public void onAnimationStart(Animator animation) {
756                 InteractionJankMonitor.Configuration.Builder builder =
757                         InteractionJankMonitor.Configuration.Builder.withView(
758                                         CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
759                                 .setTag("Actions")
760                                 .setTimeout(mDefaultTimeoutOfTimeoutHandler);
761                 mInteractionJankMonitor.begin(builder);
762             }
763         });
764 
765         animator.addUpdateListener(animation -> {
766             float t = animation.getAnimatedFraction();
767             float containerAlpha = t < alphaFraction ? t / alphaFraction : 1;
768             mActionsContainer.setAlpha(containerAlpha);
769             mActionsContainerBackground.setAlpha(containerAlpha);
770             float containerScale = SCREENSHOT_ACTIONS_START_SCALE_X
771                     + (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
772             mActionsContainer.setScaleX(containerScale);
773             mActionsContainerBackground.setScaleX(containerScale);
774             for (OverlayActionChip chip : chips) {
775                 chip.setAlpha(t);
776                 chip.setScaleX(1 / containerScale); // invert to keep size of children constant
777             }
778             mActionsContainer.setScrollX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
779             mActionsContainer.setPivotX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
780             mActionsContainerBackground.setPivotX(
781                     mDirectionLTR ? 0 : mActionsContainerBackground.getWidth());
782         });
783         return animator;
784     }
785 
badgeScreenshot(Drawable badge)786     void badgeScreenshot(Drawable badge) {
787         mScreenshotBadge.setImageDrawable(badge);
788         mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
789     }
790 
setChipIntents(ScreenshotController.SavedImageData imageData)791     void setChipIntents(ScreenshotController.SavedImageData imageData) {
792         mShareChip.setOnClickListener(v -> {
793             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
794             if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
795                 prepareSharedTransition();
796 
797                 Intent shareIntent;
798                 if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && mScreenshotData != null
799                         && mScreenshotData.getContextUrl() != null) {
800                     shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithExtraText(
801                             imageData.uri, mScreenshotData.getContextUrl().toString());
802                 } else {
803                     shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithSubject(
804                             imageData.uri, imageData.subject);
805                 }
806                 mActionExecutor.launchIntentAsync(shareIntent,
807                         imageData.shareTransition.get().bundle,
808                         imageData.owner.getIdentifier(), false);
809             } else {
810                 startSharedTransition(imageData.shareTransition.get());
811             }
812         });
813         mEditChip.setOnClickListener(v -> {
814             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
815             if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
816                 prepareSharedTransition();
817                 mActionExecutor.launchIntentAsync(
818                         ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
819                         imageData.editTransition.get().bundle,
820                         imageData.owner.getIdentifier(), true);
821             } else {
822                 startSharedTransition(imageData.editTransition.get());
823             }
824         });
825         mScreenshotPreview.setOnClickListener(v -> {
826             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
827             if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
828                 prepareSharedTransition();
829                 mActionExecutor.launchIntentAsync(
830                         ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
831                         imageData.editTransition.get().bundle,
832                         imageData.owner.getIdentifier(), true);
833             } else {
834                 startSharedTransition(
835                         imageData.editTransition.get());
836             }
837         });
838         if (mQuickShareChip != null) {
839             if (imageData.quickShareAction != null) {
840                 mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
841                         () -> {
842                             mUiEventLogger.log(
843                                     ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0,
844                                     mPackageName);
845                             animateDismissal();
846                         });
847             } else {
848                 // hide chip and unset pending interaction if necessary, since we don't actually
849                 // have a useable quick share intent
850                 Log.wtf(TAG, "Showed quick share chip, but quick share intent was null");
851                 if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
852                     mPendingInteraction = null;
853                 }
854                 mQuickShareChip.setVisibility(GONE);
855             }
856         }
857 
858         if (mPendingInteraction != null) {
859             switch (mPendingInteraction) {
860                 case PREVIEW:
861                     mScreenshotPreview.callOnClick();
862                     break;
863                 case SHARE:
864                     mShareChip.callOnClick();
865                     break;
866                 case EDIT:
867                     mEditChip.callOnClick();
868                     break;
869                 case QUICK_SHARE:
870                     mQuickShareChip.callOnClick();
871                     break;
872             }
873         } else {
874             LayoutInflater inflater = LayoutInflater.from(mContext);
875 
876             for (Notification.Action smartAction : imageData.smartActions) {
877                 OverlayActionChip actionChip = (OverlayActionChip) inflater.inflate(
878                         R.layout.overlay_action_chip, mActionsView, false);
879                 actionChip.setText(smartAction.title);
880                 actionChip.setIcon(smartAction.getIcon(), false);
881                 actionChip.setPendingIntent(smartAction.actionIntent,
882                         () -> {
883                             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED,
884                                     0, mPackageName);
885                             animateDismissal();
886                         });
887                 actionChip.setAlpha(1);
888                 mActionsView.addView(actionChip, mActionsView.getChildCount() - 1);
889                 mSmartChips.add(actionChip);
890             }
891         }
892     }
893 
addQuickShareChip(Notification.Action quickShareAction)894     void addQuickShareChip(Notification.Action quickShareAction) {
895         if (mQuickShareChip != null) {
896             mSmartChips.remove(mQuickShareChip);
897             mActionsView.removeView(mQuickShareChip);
898         }
899         if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
900             mPendingInteraction = null;
901         }
902         if (mPendingInteraction == null) {
903             LayoutInflater inflater = LayoutInflater.from(mContext);
904             mQuickShareChip = (OverlayActionChip) inflater.inflate(
905                     R.layout.overlay_action_chip, mActionsView, false);
906             mQuickShareChip.setText(quickShareAction.title);
907             mQuickShareChip.setIcon(quickShareAction.getIcon(), false);
908             mQuickShareChip.setOnClickListener(v -> {
909                 mShareChip.setIsPending(false);
910                 mEditChip.setIsPending(false);
911                 mQuickShareChip.setIsPending(true);
912                 mPendingInteraction = PendingInteraction.QUICK_SHARE;
913             });
914             mQuickShareChip.setAlpha(1);
915             mActionsView.addView(mQuickShareChip);
916             mSmartChips.add(mQuickShareChip);
917         }
918     }
919 
scrollableAreaOnScreen(ScrollCaptureResponse response)920     private Rect scrollableAreaOnScreen(ScrollCaptureResponse response) {
921         Rect r = new Rect(response.getBoundsInWindow());
922         Rect windowInScreen = response.getWindowBounds();
923         r.offset(windowInScreen.left, windowInScreen.top);
924         r.intersect(new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
925         return r;
926     }
927 
startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd, ScrollCaptureController.LongScreenshot longScreenshot)928     void startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd,
929             ScrollCaptureController.LongScreenshot longScreenshot) {
930         mPendingSharedTransition = true;
931         AnimatorSet animSet = new AnimatorSet();
932 
933         ValueAnimator scrimAnim = ValueAnimator.ofFloat(0, 1);
934         scrimAnim.addUpdateListener(animation ->
935                 mScrollingScrim.setAlpha(1 - animation.getAnimatedFraction()));
936 
937         if (mShowScrollablePreview) {
938             mScrollablePreview.setImageBitmap(longScreenshot.toBitmap());
939             float startX = mScrollablePreview.getX();
940             float startY = mScrollablePreview.getY();
941             int[] locInScreen = mScrollablePreview.getLocationOnScreen();
942             destination.offset((int) startX - locInScreen[0], (int) startY - locInScreen[1]);
943             mScrollablePreview.setPivotX(0);
944             mScrollablePreview.setPivotY(0);
945             mScrollablePreview.setAlpha(1f);
946             float currentScale = mScrollablePreview.getWidth() / (float) longScreenshot.getWidth();
947             Matrix matrix = new Matrix();
948             matrix.setScale(currentScale, currentScale);
949             matrix.postTranslate(
950                     longScreenshot.getLeft() * currentScale,
951                     longScreenshot.getTop() * currentScale);
952             mScrollablePreview.setImageMatrix(matrix);
953             float destinationScale = destination.width() / (float) mScrollablePreview.getWidth();
954 
955             ValueAnimator previewAnim = ValueAnimator.ofFloat(0, 1);
956             previewAnim.addUpdateListener(animation -> {
957                 float t = animation.getAnimatedFraction();
958                 float currScale = MathUtils.lerp(1, destinationScale, t);
959                 mScrollablePreview.setScaleX(currScale);
960                 mScrollablePreview.setScaleY(currScale);
961                 mScrollablePreview.setX(MathUtils.lerp(startX, destination.left, t));
962                 mScrollablePreview.setY(MathUtils.lerp(startY, destination.top, t));
963             });
964             ValueAnimator previewFadeAnim = ValueAnimator.ofFloat(1, 0);
965             previewFadeAnim.addUpdateListener(animation ->
966                     mScrollablePreview.setAlpha(1 - animation.getAnimatedFraction()));
967             animSet.play(previewAnim).with(scrimAnim).before(previewFadeAnim);
968             previewAnim.addListener(new AnimatorListenerAdapter() {
969                 @Override
970                 public void onAnimationEnd(Animator animation) {
971                     super.onAnimationEnd(animation);
972                     onTransitionEnd.run();
973                 }
974             });
975         } else {
976             // if we switched orientations between the original screenshot and the long screenshot
977             // capture, just fade out the scrim instead of running the preview animation
978             animSet.play(scrimAnim);
979             animSet.addListener(new AnimatorListenerAdapter() {
980                 @Override
981                 public void onAnimationEnd(Animator animation) {
982                     super.onAnimationEnd(animation);
983                     onTransitionEnd.run();
984                 }
985             });
986         }
987         animSet.addListener(new AnimatorListenerAdapter() {
988             @Override
989             public void onAnimationEnd(Animator animation) {
990                 super.onAnimationEnd(animation);
991                 mCallbacks.onDismiss();
992             }
993         });
994         animSet.start();
995     }
996 
prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap, Bitmap newBitmap, boolean screenshotTakenInPortrait)997     void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap,
998             Bitmap newBitmap, boolean screenshotTakenInPortrait) {
999         mShowScrollablePreview = (screenshotTakenInPortrait == mOrientationPortrait);
1000 
1001         mScrollingScrim.setImageBitmap(newBitmap);
1002         mScrollingScrim.setVisibility(View.VISIBLE);
1003 
1004         if (mShowScrollablePreview) {
1005             Rect scrollableArea = scrollableAreaOnScreen(response);
1006 
1007             float scale = mFixedSize
1008                     / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
1009             ConstraintLayout.LayoutParams params =
1010                     (ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
1011 
1012             params.width = (int) (scale * scrollableArea.width());
1013             params.height = (int) (scale * scrollableArea.height());
1014             Matrix matrix = new Matrix();
1015             matrix.setScale(scale, scale);
1016             matrix.postTranslate(-scrollableArea.left * scale, -scrollableArea.top * scale);
1017 
1018             mScrollablePreview.setTranslationX(scale
1019                     * (mDirectionLTR ? scrollableArea.left : scrollableArea.right - getWidth()));
1020             mScrollablePreview.setTranslationY(scale * scrollableArea.top);
1021             mScrollablePreview.setImageMatrix(matrix);
1022             mScrollablePreview.setImageBitmap(screenBitmap);
1023             mScrollablePreview.setVisibility(View.VISIBLE);
1024         }
1025         mDismissButton.setVisibility(View.GONE);
1026         mActionsContainer.setVisibility(View.GONE);
1027         // set these invisible, but not gone, so that the views are laid out correctly
1028         mActionsContainerBackground.setVisibility(View.INVISIBLE);
1029         mScreenshotPreviewBorder.setVisibility(View.INVISIBLE);
1030         mScreenshotPreview.setVisibility(View.INVISIBLE);
1031         mScrollingScrim.setImageTintBlendMode(BlendMode.SRC_ATOP);
1032         ValueAnimator anim = ValueAnimator.ofFloat(0, .3f);
1033         anim.addUpdateListener(animation -> mScrollingScrim.setImageTintList(
1034                 ColorStateList.valueOf(Color.argb((float) animation.getAnimatedValue(), 0, 0, 0))));
1035         anim.setDuration(200);
1036         anim.start();
1037     }
1038 
restoreNonScrollingUi()1039     void restoreNonScrollingUi() {
1040         mScrollChip.setVisibility(View.GONE);
1041         mScrollablePreview.setVisibility(View.GONE);
1042         mScrollingScrim.setVisibility(View.GONE);
1043 
1044         if (mAccessibilityManager.isEnabled()) {
1045             mDismissButton.setVisibility(View.VISIBLE);
1046         }
1047         mActionsContainer.setVisibility(View.VISIBLE);
1048         mActionsContainerBackground.setVisibility(View.VISIBLE);
1049         mScreenshotPreviewBorder.setVisibility(View.VISIBLE);
1050         mScreenshotPreview.setVisibility(View.VISIBLE);
1051         // reset the timeout
1052         mCallbacks.onUserInteraction();
1053     }
1054 
isDismissing()1055     boolean isDismissing() {
1056         return mScreenshotStatic.isDismissing();
1057     }
1058 
isPendingSharedTransition()1059     boolean isPendingSharedTransition() {
1060         return mPendingSharedTransition;
1061     }
1062 
animateDismissal()1063     void animateDismissal() {
1064         mScreenshotStatic.dismiss();
1065     }
1066 
reset()1067     void reset() {
1068         if (DEBUG_UI) {
1069             Log.d(TAG, "reset screenshot view");
1070         }
1071         mScreenshotStatic.cancelDismissal();
1072         if (DEBUG_WINDOW) {
1073             Log.d(TAG, "removing OnComputeInternalInsetsListener");
1074         }
1075         // Make sure we clean up the view tree observer
1076         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
1077         // Clear any references to the bitmap
1078         mScreenshotPreview.setImageDrawable(null);
1079         mScreenshotPreview.setVisibility(View.INVISIBLE);
1080         mScreenshotPreview.setAlpha(1f);
1081         mScreenshotPreviewBorder.setAlpha(0);
1082         mScreenshotBadge.setAlpha(0f);
1083         mScreenshotBadge.setVisibility(View.GONE);
1084         mScreenshotBadge.setImageDrawable(null);
1085         mPendingSharedTransition = false;
1086         mActionsContainerBackground.setVisibility(View.INVISIBLE);
1087         mActionsContainer.setVisibility(View.GONE);
1088         mDismissButton.setVisibility(View.GONE);
1089         mScrollingScrim.setVisibility(View.GONE);
1090         mScrollablePreview.setVisibility(View.GONE);
1091         mScreenshotStatic.setTranslationX(0);
1092         mScreenshotPreview.setContentDescription(
1093                 mContext.getResources().getString(R.string.screenshot_preview_description));
1094         mScreenshotPreview.setOnClickListener(null);
1095         mShareChip.setOnClickListener(null);
1096         mScrollingScrim.setVisibility(View.GONE);
1097         mEditChip.setOnClickListener(null);
1098         mShareChip.setIsPending(false);
1099         mEditChip.setIsPending(false);
1100         mPendingInteraction = null;
1101         for (OverlayActionChip chip : mSmartChips) {
1102             mActionsView.removeView(chip);
1103         }
1104         mSmartChips.clear();
1105         mQuickShareChip = null;
1106         setAlpha(1);
1107         mScreenshotStatic.setAlpha(1);
1108         mScreenshotData = null;
1109     }
1110 
startSharedTransition(ActionTransition transition)1111     private void startSharedTransition(ActionTransition transition) {
1112         try {
1113             mPendingSharedTransition = true;
1114             transition.action.actionIntent.send();
1115 
1116             // fade out non-preview UI
1117             createScreenshotFadeDismissAnimation().start();
1118         } catch (PendingIntent.CanceledException e) {
1119             mPendingSharedTransition = false;
1120             if (transition.onCancelRunnable != null) {
1121                 transition.onCancelRunnable.run();
1122             }
1123             Log.e(TAG, "Intent cancelled", e);
1124         }
1125     }
1126 
prepareSharedTransition()1127     private void prepareSharedTransition() {
1128         mPendingSharedTransition = true;
1129         // fade out non-preview UI
1130         createScreenshotFadeDismissAnimation().start();
1131     }
1132 
createScreenshotFadeDismissAnimation()1133     ValueAnimator createScreenshotFadeDismissAnimation() {
1134         ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
1135         alphaAnim.addUpdateListener(animation -> {
1136             float alpha = 1 - animation.getAnimatedFraction();
1137             mDismissButton.setAlpha(alpha);
1138             mActionsContainerBackground.setAlpha(alpha);
1139             mActionsContainer.setAlpha(alpha);
1140             mScreenshotPreviewBorder.setAlpha(alpha);
1141             mScreenshotBadge.setAlpha(alpha);
1142         });
1143         alphaAnim.setDuration(600);
1144         return alphaAnim;
1145     }
1146 
1147     /**
1148      * Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
1149      */
createScreenDrawable(Resources res, Bitmap bitmap, Insets insets)1150     private static Drawable createScreenDrawable(Resources res, Bitmap bitmap, Insets insets) {
1151         int insettedWidth = bitmap.getWidth() - insets.left - insets.right;
1152         int insettedHeight = bitmap.getHeight() - insets.top - insets.bottom;
1153 
1154         BitmapDrawable bitmapDrawable = new BitmapDrawable(res, bitmap);
1155         if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
1156                 || bitmap.getHeight() == 0) {
1157             Log.e(TAG, "Can't create inset drawable, using 0 insets bitmap and insets create "
1158                     + "degenerate region: " + bitmap.getWidth() + "x" + bitmap.getHeight() + " "
1159                     + bitmapDrawable);
1160             return bitmapDrawable;
1161         }
1162 
1163         InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable,
1164                 -1f * insets.left / insettedWidth,
1165                 -1f * insets.top / insettedHeight,
1166                 -1f * insets.right / insettedWidth,
1167                 -1f * insets.bottom / insettedHeight);
1168 
1169         if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
1170             // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need
1171             // to fill in the background of the drawable.
1172             return new LayerDrawable(new Drawable[]{
1173                     new ColorDrawable(Color.BLACK), insetDrawable});
1174         } else {
1175             return insetDrawable;
1176         }
1177     }
1178 }
1179