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