Capture a new scrim for scrolling screenshots
The capture process for scrolling screenshots is disorienting (since the app itself is actually scrolled on the display), so we add a scrim on top. Previously we just used the original bitmap, but this looks odd if the user scrolled between the original capture and tapping the scroll button. This change captures a new screenshot when the scroll button is tapped, and shades/dims it to indicate to the user that the scrolling process is happening. Bug: 183197533 Test: manual; visual verification Change-Id: Ief2111ecc93f90638c6b2abb8a8120b83e805c07
This commit is contained in:
@@ -19,12 +19,15 @@
|
||||
android:id="@+id/global_screenshot_frame"
|
||||
android:theme="@style/Screenshot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAccessibility="no">
|
||||
<ImageView
|
||||
android:id="@+id/screenshot_scrolling_scrim"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"/>
|
||||
android:visibility="gone"
|
||||
android:clickable="true"
|
||||
android:importantForAccessibility="no"/>
|
||||
<ImageView
|
||||
android:id="@+id/global_screenshot_actions_background"
|
||||
android:layout_height="@dimen/screenshot_bg_protection_height"
|
||||
|
||||
@@ -486,9 +486,24 @@ public class ScreenshotController {
|
||||
private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) {
|
||||
// copy the input Rect, since SurfaceControl.screenshot can mutate it
|
||||
Rect screenRect = new Rect(crop);
|
||||
Bitmap screenshot = captureScreenshot(crop);
|
||||
|
||||
if (screenshot == null) {
|
||||
Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
|
||||
mNotificationsController.notifyScreenshotError(
|
||||
R.string.screenshot_failed_to_capture_text);
|
||||
if (mCurrentRequestCallback != null) {
|
||||
mCurrentRequestCallback.reportError();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
|
||||
}
|
||||
|
||||
private Bitmap captureScreenshot(Rect crop) {
|
||||
int width = crop.width();
|
||||
int height = crop.height();
|
||||
|
||||
Bitmap screenshot = null;
|
||||
final Display display = getDefaultDisplay();
|
||||
final DisplayAddress address = display.getAddress();
|
||||
@@ -509,18 +524,7 @@ public class ScreenshotController {
|
||||
SurfaceControl.captureDisplay(captureArgs);
|
||||
screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
|
||||
}
|
||||
|
||||
if (screenshot == null) {
|
||||
Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
|
||||
mNotificationsController.notifyScreenshotError(
|
||||
R.string.screenshot_failed_to_capture_text);
|
||||
if (mCurrentRequestCallback != null) {
|
||||
mCurrentRequestCallback.reportError();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
|
||||
@@ -644,45 +648,56 @@ public class ScreenshotController {
|
||||
|
||||
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
|
||||
mScreenshotView.showScrollChip(/* onClick */ () -> {
|
||||
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap);
|
||||
// Clear the reference to prevent close() in dismissScreenshot
|
||||
mLastScrollCaptureResponse = null;
|
||||
final ListenableFuture<ScrollCaptureController.LongScreenshot> future =
|
||||
mScrollCaptureController.run(response);
|
||||
future.addListener(() -> {
|
||||
ScrollCaptureController.LongScreenshot longScreenshot;
|
||||
try {
|
||||
longScreenshot = future.get();
|
||||
} catch (CancellationException | InterruptedException | ExecutionException e) {
|
||||
Log.e(TAG, "Exception", e);
|
||||
return;
|
||||
}
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
getDefaultDisplay().getRealMetrics(displayMetrics);
|
||||
Bitmap newScreenshot = captureScreenshot(
|
||||
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
|
||||
|
||||
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
|
||||
mLongScreenshotHolder.setTransitionDestinationCallback(
|
||||
(transitionDestination, onTransitionEnd) ->
|
||||
mScreenshotView.startLongScreenshotTransition(
|
||||
transitionDestination, onTransitionEnd,
|
||||
longScreenshot));
|
||||
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot);
|
||||
// delay starting scroll capture to make sure the scrim is up before the app moves
|
||||
mScreenshotView.post(() -> {
|
||||
// Clear the reference to prevent close() in dismissScreenshot
|
||||
mLastScrollCaptureResponse = null;
|
||||
final ListenableFuture<ScrollCaptureController.LongScreenshot> future =
|
||||
mScrollCaptureController.run(response);
|
||||
future.addListener(() -> {
|
||||
ScrollCaptureController.LongScreenshot longScreenshot;
|
||||
try {
|
||||
longScreenshot = future.get();
|
||||
} catch (CancellationException
|
||||
| InterruptedException
|
||||
| ExecutionException e) {
|
||||
Log.e(TAG, "Exception", e);
|
||||
return;
|
||||
}
|
||||
|
||||
final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
|
||||
mLongScreenshotHolder.setTransitionDestinationCallback(
|
||||
(transitionDestination, onTransitionEnd) ->
|
||||
mScreenshotView.startLongScreenshotTransition(
|
||||
transitionDestination, onTransitionEnd,
|
||||
longScreenshot));
|
||||
|
||||
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
|
||||
ActivityOptions.startSharedElementAnimation(mWindow,
|
||||
new ScreenshotExitTransitionCallbacksSupplier(false).get(),
|
||||
null);
|
||||
transition.second.startExit();
|
||||
mContext.startActivity(intent, transition.first.toBundle());
|
||||
RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
|
||||
SCREENSHOT_REMOTE_RUNNER, 0, 0);
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error overriding screenshot app transition", e);
|
||||
}
|
||||
}, mMainExecutor);
|
||||
final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
|
||||
intent.setFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
|
||||
ActivityOptions.startSharedElementAnimation(mWindow,
|
||||
new ScreenshotExitTransitionCallbacksSupplier(false).get(),
|
||||
null);
|
||||
transition.second.startExit();
|
||||
mContext.startActivity(intent, transition.first.toBundle());
|
||||
RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
|
||||
SCREENSHOT_REMOTE_RUNNER, 0, 0);
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error overriding screenshot app transition", e);
|
||||
}
|
||||
}, mMainExecutor);
|
||||
});
|
||||
});
|
||||
} catch (CancellationException e) {
|
||||
// Ignore
|
||||
|
||||
@@ -36,8 +36,10 @@ import android.app.ActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BlendMode;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Matrix;
|
||||
@@ -257,10 +259,10 @@ public class ScreenshotView extends FrameLayout implements
|
||||
@Override // ViewTreeObserver.OnComputeInternalInsetsListener
|
||||
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
|
||||
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
|
||||
inoutInfo.touchableRegion.set(getTouchRegion());
|
||||
inoutInfo.touchableRegion.set(getTouchRegion(true));
|
||||
}
|
||||
|
||||
private Region getTouchRegion() {
|
||||
private Region getTouchRegion(boolean includeScrim) {
|
||||
Region touchRegion = new Region();
|
||||
|
||||
final Rect tmpRect = new Rect();
|
||||
@@ -273,6 +275,11 @@ public class ScreenshotView extends FrameLayout implements
|
||||
mDismissButton.getBoundsOnScreen(tmpRect);
|
||||
touchRegion.op(tmpRect, Region.Op.UNION);
|
||||
|
||||
if (includeScrim && mScrollingScrim.getVisibility() == View.VISIBLE) {
|
||||
mScrollingScrim.getBoundsOnScreen(tmpRect);
|
||||
touchRegion.op(tmpRect, Region.Op.UNION);
|
||||
}
|
||||
|
||||
if (QuickStepContract.isGesturalMode(mNavMode)) {
|
||||
final WindowManager wm = mContext.getSystemService(WindowManager.class);
|
||||
final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
|
||||
@@ -296,7 +303,7 @@ public class ScreenshotView extends FrameLayout implements
|
||||
if (ev instanceof MotionEvent) {
|
||||
MotionEvent event = (MotionEvent) ev;
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN
|
||||
&& !getTouchRegion().contains(
|
||||
&& !getTouchRegion(false).contains(
|
||||
(int) event.getRawX(), (int) event.getRawY())) {
|
||||
mCallbacks.onTouchOutside();
|
||||
}
|
||||
@@ -313,6 +320,10 @@ public class ScreenshotView extends FrameLayout implements
|
||||
|
||||
@Override // ViewGroup
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
// scrolling scrim should not be swipeable; return early if we're on the scrim
|
||||
if (!getTouchRegion(false).contains((int) ev.getRawX(), (int) ev.getRawY())) {
|
||||
return false;
|
||||
}
|
||||
// always pass through the down event so the swipe handler knows the initial state
|
||||
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
mSwipeDismissHandler.onTouch(this, ev);
|
||||
@@ -374,10 +385,6 @@ public class ScreenshotView extends FrameLayout implements
|
||||
return mScreenshotPreview;
|
||||
}
|
||||
|
||||
View getScrollablePreview() {
|
||||
return mScrollablePreview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the logger and callback on dismissal.
|
||||
*
|
||||
@@ -808,8 +815,9 @@ public class ScreenshotView extends FrameLayout implements
|
||||
anim.start();
|
||||
}
|
||||
|
||||
void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap) {
|
||||
mScrollingScrim.setImageBitmap(screenBitmap);
|
||||
void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap,
|
||||
Bitmap newBitmap) {
|
||||
mScrollingScrim.setImageBitmap(newBitmap);
|
||||
mScrollingScrim.setVisibility(View.VISIBLE);
|
||||
Rect scrollableArea = scrollableAreaOnScreen(response);
|
||||
float scale = mCornerSizeX
|
||||
@@ -829,7 +837,19 @@ public class ScreenshotView extends FrameLayout implements
|
||||
|
||||
mScrollablePreview.setImageBitmap(screenBitmap);
|
||||
mScrollablePreview.setVisibility(View.VISIBLE);
|
||||
createScreenshotFadeDismissAnimation(true).start();
|
||||
mDismissButton.setVisibility(View.GONE);
|
||||
mActionsContainer.setVisibility(View.GONE);
|
||||
mBackgroundProtection.setVisibility(View.GONE);
|
||||
// set these invisible, but not gone, so that the views are laid out correctly
|
||||
mActionsContainerBackground.setVisibility(View.INVISIBLE);
|
||||
mScreenshotPreviewBorder.setVisibility(View.INVISIBLE);
|
||||
mScreenshotPreview.setVisibility(View.INVISIBLE);
|
||||
mScrollingScrim.setImageTintBlendMode(BlendMode.SRC_ATOP);
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(0, .3f);
|
||||
anim.addUpdateListener(animation -> mScrollingScrim.setImageTintList(
|
||||
ColorStateList.valueOf(Color.argb((float) animation.getAnimatedValue(), 0, 0, 0))));
|
||||
anim.setDuration(200);
|
||||
anim.start();
|
||||
}
|
||||
|
||||
boolean isDismissing() {
|
||||
@@ -910,6 +930,7 @@ public class ScreenshotView extends FrameLayout implements
|
||||
mContext.getResources().getString(R.string.screenshot_preview_description));
|
||||
mScreenshotPreview.setOnClickListener(null);
|
||||
mShareChip.setOnClickListener(null);
|
||||
mScrollingScrim.setVisibility(View.GONE);
|
||||
mEditChip.setOnClickListener(null);
|
||||
mShareChip.setIsPending(false);
|
||||
mEditChip.setIsPending(false);
|
||||
@@ -932,7 +953,7 @@ public class ScreenshotView extends FrameLayout implements
|
||||
transition.action.actionIntent.send();
|
||||
|
||||
// fade out non-preview UI
|
||||
createScreenshotFadeDismissAnimation(false).start();
|
||||
createScreenshotFadeDismissAnimation().start();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
mPendingSharedTransition = false;
|
||||
if (transition.onCancelRunnable != null) {
|
||||
@@ -970,7 +991,7 @@ public class ScreenshotView extends FrameLayout implements
|
||||
return animSet;
|
||||
}
|
||||
|
||||
ValueAnimator createScreenshotFadeDismissAnimation(boolean fadePreview) {
|
||||
ValueAnimator createScreenshotFadeDismissAnimation() {
|
||||
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
|
||||
alphaAnim.addUpdateListener(animation -> {
|
||||
float alpha = 1 - animation.getAnimatedFraction();
|
||||
@@ -979,9 +1000,6 @@ public class ScreenshotView extends FrameLayout implements
|
||||
mActionsContainer.setAlpha(alpha);
|
||||
mBackgroundProtection.setAlpha(alpha);
|
||||
mScreenshotPreviewBorder.setAlpha(alpha);
|
||||
if (fadePreview) {
|
||||
mScreenshotPreview.setAlpha(alpha);
|
||||
}
|
||||
});
|
||||
alphaAnim.setDuration(600);
|
||||
return alphaAnim;
|
||||
|
||||
Reference in New Issue
Block a user