diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 25ec1d74008e9..f571e417c6367 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -32,8 +32,6 @@ import android.net.Uri; 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; @@ -74,7 +72,7 @@ public class LongScreenshotActivity extends Activity { private final Executor mUiExecutor; private final Executor mBackgroundExecutor; private final ImageExporter mImageExporter; - private final LongScreenshotHolder mLongScreenshotHolder; + private final LongScreenshotData mLongScreenshotHolder; private ImageView mPreview; private ImageView mTransitionView; @@ -103,7 +101,7 @@ public class LongScreenshotActivity extends Activity { @Inject public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter, @Main Executor mainExecutor, @Background Executor bgExecutor, - LongScreenshotHolder longScreenshotHolder) { + LongScreenshotData longScreenshotHolder) { mUiEventLogger = uiEventLogger; mUiExecutor = mainExecutor; mBackgroundExecutor = bgExecutor; @@ -116,7 +114,6 @@ public class LongScreenshotActivity extends Activity { 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); @@ -210,25 +207,22 @@ public class LongScreenshotActivity extends Activity { 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.setBoundaryPosition( - CropView.CropBoundary.TOP, topFraction); - mCropView.setBoundaryPosition( - CropView.CropBoundary.BOTTOM, bottomFraction); - mCropView.animateEntrance(); - mCropView.setVisibility(View.VISIBLE); - setButtonsEnabled(true); - mEnterTransitionView.setVisibility(View.GONE); - } + mEnterTransitionView.post(() -> { + Rect dest = new Rect(); + mEnterTransitionView.getBoundsOnScreen(dest); + mLongScreenshotHolder.takeTransitionDestinationCallback() + .setTransitionDestination(dest, () -> { + mPreview.animate().alpha(1f); + mCropView.setBoundaryPosition( + CropView.CropBoundary.TOP, topFraction); + mCropView.setBoundaryPosition( + CropView.CropBoundary.BOTTOM, bottomFraction); + mCropView.animateEntrance(); + mCropView.setVisibility(View.VISIBLE); + setButtonsEnabled(true); + mEnterTransitionView.setVisibility(View.GONE); }); - } + }); return true; } }); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotHolder.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java similarity index 63% rename from packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotHolder.java rename to packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java index 39c6f07989885..f549faf2414aa 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotHolder.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java @@ -23,16 +23,19 @@ import java.util.concurrent.atomic.AtomicReference; import javax.inject.Inject; /** - * LongScreenshotHolder holds on to 1 LongScreenshot reference to facilitate indirect in-process - * passing. + * LongScreenshotData holds on to 1 LongScreenshot reference and 1 TransitionDestination + * reference, to facilitate indirect in-process passing. */ @SysUISingleton -public class LongScreenshotHolder { +public class LongScreenshotData { private final AtomicReference mLongScreenshot; + private final AtomicReference + mTransitionDestinationCallback; @Inject - public LongScreenshotHolder() { + public LongScreenshotData() { mLongScreenshot = new AtomicReference<>(); + mTransitionDestinationCallback = new AtomicReference<>(); } /** @@ -56,4 +59,18 @@ public class LongScreenshotHolder { return mLongScreenshot.getAndSet(null); } + /** + * Set the holder's TransitionDestination callback. + */ + public void setTransitionDestinationCallback( + ScreenshotController.TransitionDestination destination) { + mTransitionDestinationCallback.set(destination); + } + + /** + * Return the current TransitionDestination callback and clear. + */ + public ScreenshotController.TransitionDestination takeTransitionDestinationCallback() { + return mTransitionDestinationCallback.getAndSet(null); + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index aea8849adc244..2e138362aedb5 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -64,6 +64,7 @@ import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.ScrollCaptureResponse; import android.view.SurfaceControl; @@ -72,6 +73,7 @@ import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; @@ -203,6 +205,15 @@ public class ScreenshotController { void onActionsReady(ScreenshotController.QuickShareData quickShareData); } + interface TransitionDestination { + /** + * Allows the long screenshot activity to call back with a destination location (the bounds + * on screen of the destination for the transitioning view) and a Runnable to be run once + * the transition animation is complete. + */ + void setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd); + } + // These strings are used for communicating the action invoked to // ScreenshotNotificationSmartActionsProvider. static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; @@ -241,7 +252,7 @@ public class ScreenshotController { private final PhoneWindow mWindow; private final DisplayManager mDisplayManager; private final ScrollCaptureController mScrollCaptureController; - private final LongScreenshotHolder mLongScreenshotHolder; + private final LongScreenshotData mLongScreenshotHolder; private ScreenshotView mScreenshotView; private Bitmap mScreenBitmap; @@ -286,7 +297,7 @@ public class ScreenshotController { ImageExporter imageExporter, @Main Executor mainExecutor, ScrollCaptureController scrollCaptureController, - LongScreenshotHolder longScreenshotHolder) { + LongScreenshotData longScreenshotHolder) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; mScrollCaptureClient = scrollCaptureClient; @@ -648,17 +659,29 @@ public class ScreenshotController { } mLongScreenshotHolder.setLongScreenshot(longScreenshot); + mLongScreenshotHolder.setTransitionDestinationCallback( + (transitionDestination, onTransitionEnd) -> + mScreenshotView.startLongScreenshotTransition( + transitionDestination, onTransitionEnd, + longScreenshot)); final Intent intent = new Intent(mContext, LongScreenshotActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); Pair transition = - ActivityOptions.startSharedElementAnimation( - mWindow, new ScreenshotExitTransitionCallbacks(), null, - Pair.create(mScreenshotView.getScrollablePreview(), - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); + 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) { @@ -883,8 +906,8 @@ public class ScreenshotController { return () -> { Pair transition = ActivityOptions.startSharedElementAnimation( - mWindow, new ScreenshotExitTransitionCallbacks(), null, - Pair.create(mScreenshotView.getScreenshotPreview(), + mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(), + null, Pair.create(mScreenshotView.getScreenshotPreview(), ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); transition.second.startExit(); @@ -970,19 +993,33 @@ public class ScreenshotController { return matchWithinTolerance; } - private class ScreenshotExitTransitionCallbacks implements ExitTransitionCallbacks { - @Override - public boolean isReturnTransitionAllowed() { - return false; + private class ScreenshotExitTransitionCallbacksSupplier implements + Supplier { + final boolean mDismissOnHideSharedElements; + + ScreenshotExitTransitionCallbacksSupplier(boolean dismissOnHideSharedElements) { + mDismissOnHideSharedElements = dismissOnHideSharedElements; } @Override - public void hideSharedElements() { - finishDismiss(); - } + public ExitTransitionCallbacks get() { + return new ExitTransitionCallbacks() { + @Override + public boolean isReturnTransitionAllowed() { + return false; + } - @Override - public void onFinish() { + @Override + public void hideSharedElements() { + if (mDismissOnHideSharedElements) { + 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 0631739cf7604..6690465911707 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -764,7 +764,53 @@ public class ScreenshotView extends FrameLayout implements return r; } + void startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd, + ScrollCaptureController.LongScreenshot longScreenshot) { + mScrollablePreview.setImageBitmap(longScreenshot.toBitmap()); + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + float startX = mScrollablePreview.getX(); + float startY = mScrollablePreview.getY(); + int[] locInScreen = mScrollablePreview.getLocationOnScreen(); + destination.offset((int) startX - locInScreen[0], (int) startY - locInScreen[1]); + mScrollablePreview.setPivotX(0); + mScrollablePreview.setPivotY(0); + mScrollablePreview.setAlpha(1f); + float currentScale = mScrollablePreview.getWidth() / (float) longScreenshot.getWidth(); + Matrix matrix = new Matrix(); + matrix.setScale(currentScale, currentScale); + matrix.postTranslate( + longScreenshot.getLeft() * currentScale, longScreenshot.getTop() * currentScale); + mScrollablePreview.setImageMatrix(matrix); + float destinationScale = destination.width() / (float) mScrollablePreview.getWidth(); + anim.addUpdateListener(animation -> { + float t = animation.getAnimatedFraction(); + mScrollingScrim.setAlpha(1 - t); + float currScale = MathUtils.lerp(1, destinationScale, t); + mScrollablePreview.setScaleX(currScale); + mScrollablePreview.setScaleY(currScale); + mScrollablePreview.setX(MathUtils.lerp(startX, destination.left, t)); + mScrollablePreview.setY(MathUtils.lerp(startY, destination.top, t)); + }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + onTransitionEnd.run(); + mScrollablePreview.animate().alpha(0).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mCallbacks.onDismiss(); + } + }); + } + }); + anim.start(); + } + void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap) { + mScrollingScrim.setImageBitmap(screenBitmap); + mScrollingScrim.setVisibility(View.VISIBLE); Rect scrollableArea = scrollableAreaOnScreen(response); float scale = mCornerSizeX / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight()); @@ -775,14 +821,12 @@ public class ScreenshotView extends FrameLayout implements params.height = (int) (scale * scrollableArea.height()); Matrix matrix = new Matrix(); matrix.setScale(scale, scale); - matrix.postTranslate(0, -scrollableArea.top * scale); + matrix.postTranslate(-scrollableArea.left * scale, -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();