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