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