Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 37a60b43 authored by Miranda Kephart's avatar Miranda Kephart
Browse files

Implement "shared element" SS->LSS transition in screenshots

Workaround for b/190806800; in order to get the transition from
screenshots to the long screenshots activity to look the way we
want, we fake the shared element transition by determining the
destination for the preview in long screenshots, sending that back,
and running the actual animation in screenshots.

Bug: 183197533
Test: manual; visual verification in portrait and landscape
Change-Id: I16d462789ec0fa9422cc1924e6df9649b89b9055
parent 0d5d58a1
Loading
Loading
Loading
Loading
+17 −23
Original line number Diff line number Diff line
@@ -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,13 +207,11 @@ 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);
                        mEnterTransitionView.post(() -> {
                            Rect dest = new Rect();
                            mEnterTransitionView.getBoundsOnScreen(dest);
                            mLongScreenshotHolder.takeTransitionDestinationCallback()
                                    .setTransitionDestination(dest, () -> {
                                        mPreview.animate().alpha(1f);
                                        mCropView.setBoundaryPosition(
                                                CropView.CropBoundary.TOP, topFraction);
@@ -226,9 +221,8 @@ public class LongScreenshotActivity extends Activity {
                                        mCropView.setVisibility(View.VISIBLE);
                                        setButtonsEnabled(true);
                                        mEnterTransitionView.setVisibility(View.GONE);
                                        }
                                    });
                        }
                        });
                        return true;
                    }
                });
+21 −4
Original line number Diff line number Diff line
@@ -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<ScrollCaptureController.LongScreenshot> mLongScreenshot;
    private final AtomicReference<ScreenshotController.TransitionDestination>
            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);
    }
}
+54 −17
Original line number Diff line number Diff line
@@ -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;
@@ -644,17 +655,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<ActivityOptions, ExitTransitionCoordinator> 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) {
@@ -879,8 +902,8 @@ public class ScreenshotController {
        return () -> {
            Pair<ActivityOptions, ExitTransitionCoordinator> 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();

@@ -966,7 +989,17 @@ public class ScreenshotController {
        return matchWithinTolerance;
    }

    private class ScreenshotExitTransitionCallbacks implements ExitTransitionCallbacks {
    private class ScreenshotExitTransitionCallbacksSupplier implements
            Supplier<ExitTransitionCallbacks> {
        final boolean mDismissOnHideSharedElements;

        ScreenshotExitTransitionCallbacksSupplier(boolean dismissOnHideSharedElements) {
            mDismissOnHideSharedElements = dismissOnHideSharedElements;
        }

        @Override
        public ExitTransitionCallbacks get() {
            return new ExitTransitionCallbacks() {
                @Override
                public boolean isReturnTransitionAllowed() {
                    return false;
@@ -974,11 +1007,15 @@ public class ScreenshotController {

                @Override
                public void hideSharedElements() {
                    if (mDismissOnHideSharedElements) {
                        finishDismiss();
                    }
                }

                @Override
                public void onFinish() {
                }
            };
        }
    }
}
+47 −3
Original line number Diff line number Diff line
@@ -759,7 +759,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());
@@ -770,14 +816,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();