From b8b2f06d05641639c5f77dbac2886e26b2a94afd Mon Sep 17 00:00:00 2001 From: Zak Cohen Date: Fri, 15 May 2020 14:19:08 -0700 Subject: [PATCH] Screenshots - respect insets of bitmap when passed in. Explicitly sets the size of the animated screenshot preview to the size of the bounds for the animation provided. Adds an option to not show the flash animation when using a provided image to show visually consistency between the originating view and the screen shot UI. Bug: 154524544 Test: locally on fullscreen, partial and overview screenshots Change-Id: I3d0b48fd44d93182472f939114025bf67bdf8e71 --- .../SystemUI/res/layout/global_screenshot.xml | 5 +- .../systemui/screenshot/GlobalScreenshot.java | 144 ++++++++++++++---- 2 files changed, 117 insertions(+), 32 deletions(-) diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index fce4610b934bc..6a235218b32ff 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -30,11 +30,10 @@ android:id="@+id/global_screenshot_animated_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center" + android:layout_gravity="top|start" android:visibility="gone" android:elevation="@dimen/screenshot_preview_elevation" - android:background="@drawable/screenshot_rounded_corners" - android:adjustViewBounds="true"/> + android:background="@drawable/screenshot_rounded_corners" /> finisher, Rect screenRect) { + private void takeScreenshot(Bitmap screenshot, Consumer finisher, Rect screenRect, + Insets screenInsets, boolean showFlash) { dismissScreenshot("new screenshot requested", true); mScreenBitmap = screenshot; @@ -482,7 +490,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mDismissAnimation.cancel(); } // Start the post-screenshot animation - startAnimation(finisher, mScreenBitmap.getWidth(), mScreenBitmap.getHeight(), screenRect); + startAnimation(finisher, screenRect, screenInsets, showFlash); } void takeScreenshot(Consumer finisher, Runnable onComplete) { @@ -498,9 +506,15 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer finisher, Runnable onComplete) { // TODO: use task Id, userId, topComponent for smart handler - // TODO: use visibleInsets for animation + mOnCompleteRunnable = onComplete; - takeScreenshot(screenshot, finisher, screenshotScreenBounds); + if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { + takeScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); + } else { + takeScreenshot(screenshot, finisher, + new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, + true); + } } /** @@ -621,8 +635,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } // Clear any references to the bitmap - mScreenshotPreview.setImageBitmap(null); - mScreenshotAnimatedView.setImageBitmap(null); + mScreenshotPreview.setImageDrawable(null); + mScreenshotAnimatedView.setImageDrawable(null); + mScreenshotAnimatedView.setVisibility(View.GONE); mActionsContainerBackground.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); @@ -688,8 +703,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset /** * Starts the animation after taking the screenshot */ - private void startAnimation( - final Consumer finisher, int bitmapWidth, int bitmapHeight, Rect screenRect) { + private void startAnimation(final Consumer finisher, Rect screenRect, Insets screenInsets, + boolean showFlash) { + // If power save is on, show a toast so there is some visual indication that a // screenshot has been taken. PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -701,9 +717,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (!mScreenshotLayout.isAttachedToWindow()) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); } - mScreenshotAnimatedView.setImageBitmap(mScreenBitmap); - mScreenshotPreview.setImageBitmap(mScreenBitmap); + mScreenshotAnimatedView.setImageDrawable( + createScreenDrawable(mScreenBitmap, screenInsets)); + setAnimatedViewSize(screenRect.width(), screenRect.height()); + // Show when the animation starts + mScreenshotAnimatedView.setVisibility(View.GONE); + mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); // make static preview invisible (from gone) so we can query its location on screen mScreenshotPreview.setVisibility(View.INVISIBLE); @@ -711,14 +731,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); mScreenshotAnimation = - createScreenshotDropInAnimation(bitmapWidth, bitmapHeight, screenRect); + createScreenshotDropInAnimation(screenRect, showFlash); saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { - @Override - void onActionsReady(SavedImageData imageData) { - showUiOnActionsReady(imageData); - } - }); + @Override + void onActionsReady(SavedImageData imageData) { + showUiOnActionsReady(imageData); + } + }); // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); @@ -730,20 +750,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } - private AnimatorSet createScreenshotDropInAnimation( - int bitmapWidth, int bitmapHeight, Rect bounds) { + private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) { Rect previewBounds = new Rect(); mScreenshotPreview.getBoundsOnScreen(previewBounds); - float cornerScale = mCornerSizeX / (mOrientationPortrait ? bitmapWidth : bitmapHeight); - float currentScale = bounds.height() / (float) bitmapHeight; + float cornerScale = + mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height()); + final float currentScale = 1f; mScreenshotAnimatedView.setScaleX(currentScale); mScreenshotAnimatedView.setScaleY(currentScale); - mScreenshotAnimatedView.setPivotX(0); - mScreenshotAnimatedView.setPivotY(0); - AnimatorSet dropInAnimation = new AnimatorSet(); ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1); flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS); @@ -785,13 +802,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (t < xPositionPct) { float xCenter = MathUtils.lerp(startPos.x, finalPos.x, mFastOutSlowIn.getInterpolation(t / xPositionPct)); - mScreenshotAnimatedView.setX(xCenter - bitmapWidth * currentScaleX / 2f); + mScreenshotAnimatedView.setX(xCenter - bounds.width() * currentScaleX / 2f); } else { - mScreenshotAnimatedView.setX(finalPos.x - bitmapWidth * currentScaleX / 2f); + mScreenshotAnimatedView.setX(finalPos.x - bounds.width() * currentScaleX / 2f); } float yCenter = MathUtils.lerp( startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t)); - mScreenshotAnimatedView.setY(yCenter - bitmapHeight * currentScaleY / 2f); + mScreenshotAnimatedView.setY(yCenter - bounds.height() * currentScaleY / 2f); }); toCorner.addListener(new AnimatorListenerAdapter() { @@ -805,8 +822,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotFlash.setAlpha(0f); mScreenshotFlash.setVisibility(View.VISIBLE); - dropInAnimation.play(flashOutAnimator).after(flashInAnimator); - dropInAnimation.play(flashOutAnimator).with(toCorner); + if (showFlash) { + dropInAnimation.play(flashOutAnimator).after(flashInAnimator); + dropInAnimation.play(flashOutAnimator).with(toCorner); + } else { + dropInAnimation.play(toCorner); + } dropInAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -972,6 +993,71 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return animSet; } + private void setAnimatedViewSize(int width, int height) { + ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams(); + layoutParams.width = width; + layoutParams.height = height; + mScreenshotAnimatedView.setLayoutParams(layoutParams); + } + + /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ + private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) { + int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right; + int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom; + + if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0 + || bitmap.getHeight() == 0) { + Log.e(TAG, String.format( + "Provided bitmap and insets create degenerate region: %dx%d %s", + bitmap.getWidth(), bitmap.getHeight(), bitmapInsets)); + return false; + } + + float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight; + float boundsAspect = ((float) screenBounds.width()) / screenBounds.height(); + + boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f; + if (!matchWithinTolerance) { + Log.d(TAG, String.format("aspectRatiosMatch: don't match bitmap: %f, bounds: %f", + insettedBitmapAspect, boundsAspect)); + } + + return matchWithinTolerance; + } + + /** + * Create a drawable using the size of the bitmap and insets as the fractional inset parameters. + */ + private Drawable createScreenDrawable(Bitmap bitmap, Insets insets) { + int insettedWidth = bitmap.getWidth() - insets.left - insets.right; + int insettedHeight = bitmap.getHeight() - insets.top - insets.bottom; + + BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap); + if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0 + || bitmap.getHeight() == 0) { + Log.e(TAG, String.format( + "Can't create insetted drawable, using 0 insets " + + "bitmap and insets create degenerate region: %dx%d %s", + bitmap.getWidth(), bitmap.getHeight(), insets)); + return bitmapDrawable; + } + + InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable, + -1f * insets.left / insettedWidth, + -1f * insets.top / insettedHeight, + -1f * insets.right / insettedWidth, + -1f * insets.bottom / insettedHeight); + + if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) { + // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need + // to fill in the background of the drawable. + return new LayerDrawable(new Drawable[] { + new ColorDrawable(Color.BLACK), insetDrawable}); + } else { + return insetDrawable; + } + } + /** * Receiver to proxy the share or edit intent, used to clean up the notification and send * appropriate signals to the system (ie. to dismiss the keyguard if necessary).