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;