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