Screenshot to long screenshot shared transition
Test: manual
Bug: 183197533
Change-Id: I4bad5ba3db6d3808584bc1cccb5e3319789ff706
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 5bd181c..93bd581 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -21,6 +21,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
+ android:id="@+id/screenshot_scrolling_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"/>
+ <ImageView
android:id="@+id/global_screenshot_actions_background"
android:layout_height="@dimen/screenshot_bg_protection_height"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml
index 60dbaf9..d1f6cee 100644
--- a/packages/SystemUI/res/layout/global_screenshot_static.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_static.xml
@@ -121,4 +121,12 @@
android:layout_margin="@dimen/screenshot_dismiss_button_margin"
android:src="@drawable/screenshot_cancel"/>
</FrameLayout>
+ <ImageView
+ android:id="@+id/screenshot_scrollable_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="matrix"
+ app:layout_constraintStart_toStartOf="@id/global_screenshot_preview"
+ app:layout_constraintTop_toTopOf="@id/global_screenshot_preview"
+ android:elevation="@dimen/screenshot_preview_elevation"/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 8e30aec..0c6baec 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -62,6 +62,7 @@
android:paddingHorizontal="48dp"
android:paddingTop="8dp"
android:paddingBottom="42dp"
+ android:alpha="0"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/save"
@@ -72,6 +73,16 @@
tools:minHeight="100dp"
tools:minWidth="100dp" />
+ <ImageView
+ android:id="@+id/enter_transition"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="matrix"
+ app:layout_constraintTop_toTopOf="@id/preview"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:transitionName="screenshot_preview_image"/>
+
<com.android.systemui.screenshot.CropView
android:id="@+id/crop_view"
android:layout_width="0px"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 30c9b44..d5b4032 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
+import android.graphics.Matrix;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
@@ -31,9 +32,12 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.transition.Transition;
+import android.transition.TransitionListenerAdapter;
import android.util.Log;
import android.view.ScrollCaptureResponse;
import android.view.View;
+import android.view.ViewTreeObserver;
import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -74,6 +78,7 @@
private ImageView mPreview;
private ImageView mTransitionView;
+ private ImageView mEnterTransitionView;
private View mSave;
private View mEdit;
private View mShare;
@@ -111,7 +116,7 @@
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate(savedInstanceState = " + savedInstanceState + ")");
super.onCreate(savedInstanceState);
-
+ postponeEnterTransition();
setContentView(R.layout.long_screenshot);
mPreview = requireViewById(R.id.preview);
@@ -122,6 +127,7 @@
mMagnifierView = requireViewById(R.id.magnifier);
mCropView.setCropInteractionListener(mMagnifierView);
mTransitionView = requireViewById(R.id.transition);
+ mEnterTransitionView = requireViewById(R.id.enter_transition);
mSave.setOnClickListener(this::onClicked);
mEdit.setOnClickListener(this::onClicked);
@@ -184,8 +190,8 @@
private void onLongScreenshotReceived(LongScreenshot longScreenshot) {
Log.d(TAG, "onLongScreenshotReceived(longScreenshot=" + longScreenshot + ")");
mLongScreenshot = longScreenshot;
- mPreview.setImageDrawable(mLongScreenshot.getDrawable());
- updateImageDimensions();
+ Drawable drawable = mLongScreenshot.getDrawable();
+ mPreview.setImageDrawable(drawable);
mCropView.setVisibility(View.VISIBLE);
mMagnifierView.setDrawable(mLongScreenshot.getDrawable(),
mLongScreenshot.getWidth(), mLongScreenshot.getHeight());
@@ -196,9 +202,35 @@
float bottomFraction = Math.min(1f,
1 - (mLongScreenshot.getBottom() - mLongScreenshot.getPageHeight())
/ (float) mLongScreenshot.getHeight());
- mCropView.animateBoundaryTo(CropView.CropBoundary.TOP, topFraction);
- mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, bottomFraction);
- setButtonsEnabled(true);
+
+ mEnterTransitionView.setImageDrawable(drawable);
+
+ mEnterTransitionView.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mEnterTransitionView.getViewTreeObserver().removeOnPreDrawListener(this);
+ updateImageDimensions();
+ startPostponedEnterTransition();
+ if (isActivityTransitionRunning()) {
+ getWindow().getSharedElementEnterTransition().addListener(
+ new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ super.onTransitionEnd(transition);
+ mPreview.animate().alpha(1f);
+ mCropView.animateBoundaryTo(
+ CropView.CropBoundary.TOP, topFraction);
+ mCropView.animateBoundaryTo(
+ CropView.CropBoundary.BOTTOM, bottomFraction);
+ setButtonsEnabled(true);
+ mEnterTransitionView.setVisibility(View.GONE);
+ }
+ });
+ }
+ return true;
+ }
+ });
// Immediately export to temp image file for saved state
mCacheSaveFuture = mImageExporter.exportAsTempFile(mBackgroundExecutor,
@@ -412,22 +444,26 @@
// The image width and height on screen
int imageHeight = previewHeight;
int imageWidth = previewWidth;
+ float scale;
+ int extraPadding = 0;
if (imageRatio > viewRatio) {
// Image is full width and height is constrained, compute extra padding to inform
// CropView
imageHeight = (int) (previewHeight * viewRatio / imageRatio);
- int extraPadding = (previewHeight - imageHeight) / 2;
+ extraPadding = (previewHeight - imageHeight) / 2;
mCropView.setExtraPadding(extraPadding + mPreview.getPaddingTop(),
extraPadding + mPreview.getPaddingBottom());
imageTop += (previewHeight - imageHeight) / 2;
mCropView.setExtraPadding(extraPadding, extraPadding);
mCropView.setImageWidth(previewWidth);
+ scale = previewWidth / (float) mPreview.getDrawable().getIntrinsicWidth();
} else {
imageWidth = (int) (previewWidth * imageRatio / viewRatio);
imageLeft += (previewWidth - imageWidth) / 2;
// Image is full height
mCropView.setExtraPadding(mPreview.getPaddingTop(), mPreview.getPaddingBottom());
mCropView.setImageWidth((int) (previewHeight * imageRatio));
+ scale = previewHeight / (float) mPreview.getDrawable().getIntrinsicHeight();
}
// Update transition view's position and scale.
@@ -439,5 +475,20 @@
params.width = boundaries.width();
params.height = boundaries.height();
mTransitionView.setLayoutParams(params);
+
+ ConstraintLayout.LayoutParams enterTransitionParams =
+ (ConstraintLayout.LayoutParams) mEnterTransitionView.getLayoutParams();
+ float topFraction = Math.max(0,
+ -mLongScreenshot.getTop() / (float) mLongScreenshot.getHeight());
+ enterTransitionParams.width = (int) (scale * drawable.getIntrinsicWidth());
+ enterTransitionParams.height = (int) (scale * mLongScreenshot.getPageHeight());
+ mEnterTransitionView.setLayoutParams(enterTransitionParams);
+
+ Matrix matrix = new Matrix();
+ matrix.setScale(scale, scale);
+ matrix.postTranslate(0, -scale * drawable.getIntrinsicHeight() * topFraction);
+ mEnterTransitionView.setImageMatrix(matrix);
+ mEnterTransitionView.setTranslationY(
+ topFraction * previewHeight + mPreview.getPaddingTop() + extraPadding);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 1f9221c..fa4daf2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -603,7 +603,7 @@
// No connection means that the target window wasn't found
// or that it cannot support scroll capture.
Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " ["
- + mLastScrollCaptureResponse.getWindowTitle() + "]");
+ + mLastScrollCaptureResponse.getWindowTitle() + "]");
return;
}
Log.d(TAG, "ScrollCapture: connected to window ["
@@ -611,6 +611,7 @@
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 =
@@ -628,9 +629,14 @@
final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivity(intent);
- dismissScreenshot(false);
+ Pair<ActivityOptions, ExitTransitionCoordinator> transition =
+ ActivityOptions.startSharedElementAnimation(
+ mWindow, new ScreenshotExitTransitionCallbacks(), null,
+ Pair.create(mScreenshotView.getScrollablePreview(),
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
+ transition.second.startExit();
+ mContext.startActivity(intent, transition.first.toBundle());
}, mMainExecutor);
});
} catch (CancellationException e) {
@@ -654,7 +660,8 @@
}
@Override
- public void onWindowDetached() { }
+ public void onWindowDetached() {
+ }
});
}
@@ -852,24 +859,9 @@
*/
private Supplier<ActionTransition> getActionTransitionSupplier() {
return () -> {
- ExitTransitionCallbacks cb = new ExitTransitionCallbacks() {
- @Override
- public boolean isReturnTransitionAllowed() {
- return false;
- }
-
- @Override
- public void hideSharedElements() {
- finishDismiss();
- }
-
- @Override
- public void onFinish() {
- }
- };
-
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
- ActivityOptions.startSharedElementAnimation(mWindow, cb, null,
+ ActivityOptions.startSharedElementAnimation(
+ mWindow, new ScreenshotExitTransitionCallbacks(), null,
Pair.create(mScreenshotView.getScreenshotPreview(),
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
transition.second.startExit();
@@ -955,4 +947,20 @@
}
return matchWithinTolerance;
}
+
+ private class ScreenshotExitTransitionCallbacks implements ExitTransitionCallbacks {
+ @Override
+ public boolean isReturnTransitionAllowed() {
+ return false;
+ }
+
+ @Override
+ public void hideSharedElements() {
+ finishDismiss();
+ }
+
+ @Override
+ public void onFinish() {
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index facebee..3e7601e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -40,6 +40,7 @@
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Insets;
+import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
@@ -57,6 +58,7 @@
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.ScrollCaptureResponse;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
@@ -71,6 +73,8 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
+import androidx.constraintlayout.widget.ConstraintLayout;
+
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
@@ -125,9 +129,11 @@
private boolean mDirectionLTR;
private ScreenshotSelectorView mScreenshotSelectorView;
+ private ImageView mScrollingScrim;
private View mScreenshotStatic;
private ImageView mScreenshotPreview;
private View mScreenshotPreviewBorder;
+ private ImageView mScrollablePreview;
private ImageView mScreenshotFlash;
private ImageView mActionsContainerBackground;
private HorizontalScrollView mActionsContainer;
@@ -262,6 +268,7 @@
@Override // View
protected void onFinishInflate() {
+ mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
mScreenshotStatic = requireNonNull(findViewById(R.id.global_screenshot_static));
mScreenshotPreview = requireNonNull(findViewById(R.id.global_screenshot_preview));
mScreenshotPreviewBorder = requireNonNull(
@@ -275,6 +282,7 @@
mBackgroundProtection = requireNonNull(
findViewById(R.id.global_screenshot_actions_background));
mDismissButton = requireNonNull(findViewById(R.id.global_screenshot_dismiss_button));
+ mScrollablePreview = requireNonNull(findViewById(R.id.screenshot_scrollable_preview));
mScreenshotFlash = requireNonNull(findViewById(R.id.global_screenshot_flash));
mScreenshotSelectorView = requireNonNull(findViewById(R.id.global_screenshot_selector));
mShareChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_share_chip));
@@ -323,6 +331,10 @@
return mScreenshotPreview;
}
+ View getScrollablePreview() {
+ return mScrollablePreview;
+ }
+
/**
* Set up the logger and callback on dismissal.
*
@@ -672,6 +684,38 @@
}
}
+ private Rect scrollableAreaOnScreen(ScrollCaptureResponse response) {
+ Rect r = new Rect(response.getBoundsInWindow());
+ Rect windowInScreen = response.getWindowBounds();
+ r.offset(windowInScreen.left, windowInScreen.top);
+ r.intersect(new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
+ return r;
+ }
+
+ void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap) {
+ Rect scrollableArea = scrollableAreaOnScreen(response);
+ float scale = mCornerSizeX
+ / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
+ ConstraintLayout.LayoutParams params =
+ (ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
+
+ params.width = (int) (scale * scrollableArea.width());
+ params.height = (int) (scale * scrollableArea.height());
+ Matrix matrix = new Matrix();
+ matrix.setScale(scale, scale);
+ matrix.postTranslate(0, -scrollableArea.top * scale);
+
+ mScrollablePreview.setTranslationX(scale * scrollableArea.left);
+ mScrollablePreview.setTranslationY(scale * scrollableArea.top);
+ mScrollablePreview.setImageMatrix(matrix);
+
+ mScrollingScrim.setImageBitmap(screenBitmap);
+ mScrollingScrim.setVisibility(View.VISIBLE);
+ mScrollablePreview.setImageBitmap(screenBitmap);
+ mScrollablePreview.setVisibility(View.VISIBLE);
+ createScreenshotFadeDismissAnimation(true).start();
+ }
+
boolean isDismissing() {
return (mDismissAnimation != null && mDismissAnimation.isRunning());
}
@@ -772,7 +816,7 @@
transition.action.actionIntent.send();
// fade out non-preview UI
- createScreenshotFadeDismissAnimation().start();
+ createScreenshotFadeDismissAnimation(false).start();
} catch (PendingIntent.CanceledException e) {
mPendingSharedTransition = false;
if (transition.onCancelRunnable != null) {
@@ -810,7 +854,7 @@
return animSet;
}
- private ValueAnimator createScreenshotFadeDismissAnimation() {
+ ValueAnimator createScreenshotFadeDismissAnimation(boolean fadePreview) {
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.addUpdateListener(animation -> {
float alpha = 1 - animation.getAnimatedFraction();
@@ -818,6 +862,10 @@
mActionsContainerBackground.setAlpha(alpha);
mActionsContainer.setAlpha(alpha);
mBackgroundProtection.setAlpha(alpha);
+ mScreenshotPreviewBorder.setAlpha(alpha);
+ if (fadePreview) {
+ mScreenshotPreview.setAlpha(alpha);
+ }
});
alphaAnim.setDuration(600);
return alphaAnim;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index 2863074..94e3149 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -405,6 +405,10 @@
return new Rect(mWindowBounds);
}
+ public Rect getBoundsInWindow() {
+ return new Rect(mBoundsInWindow);
+ }
+
@Override
public int getMaxTiles() {
return mMaxTiles;