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