Merge "Implement "shared element" SS->LSS transition in screenshots" into sc-dev

This commit is contained in:
Miranda Kephart
2021-06-21 12:13:24 +00:00
committed by Android (Google) Code Review
4 changed files with 139 additions and 47 deletions

View File

@@ -32,8 +32,6 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.text.TextUtils; import android.text.TextUtils;
import android.transition.Transition;
import android.transition.TransitionListenerAdapter;
import android.util.Log; import android.util.Log;
import android.view.ScrollCaptureResponse; import android.view.ScrollCaptureResponse;
import android.view.View; import android.view.View;
@@ -74,7 +72,7 @@ public class LongScreenshotActivity extends Activity {
private final Executor mUiExecutor; private final Executor mUiExecutor;
private final Executor mBackgroundExecutor; private final Executor mBackgroundExecutor;
private final ImageExporter mImageExporter; private final ImageExporter mImageExporter;
private final LongScreenshotHolder mLongScreenshotHolder; private final LongScreenshotData mLongScreenshotHolder;
private ImageView mPreview; private ImageView mPreview;
private ImageView mTransitionView; private ImageView mTransitionView;
@@ -103,7 +101,7 @@ public class LongScreenshotActivity extends Activity {
@Inject @Inject
public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter, public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
@Main Executor mainExecutor, @Background Executor bgExecutor, @Main Executor mainExecutor, @Background Executor bgExecutor,
LongScreenshotHolder longScreenshotHolder) { LongScreenshotData longScreenshotHolder) {
mUiEventLogger = uiEventLogger; mUiEventLogger = uiEventLogger;
mUiExecutor = mainExecutor; mUiExecutor = mainExecutor;
mBackgroundExecutor = bgExecutor; mBackgroundExecutor = bgExecutor;
@@ -116,7 +114,6 @@ public class LongScreenshotActivity extends Activity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate(savedInstanceState = " + savedInstanceState + ")"); Log.d(TAG, "onCreate(savedInstanceState = " + savedInstanceState + ")");
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
postponeEnterTransition();
setContentView(R.layout.long_screenshot); setContentView(R.layout.long_screenshot);
mPreview = requireViewById(R.id.preview); mPreview = requireViewById(R.id.preview);
@@ -210,13 +207,11 @@ public class LongScreenshotActivity extends Activity {
public boolean onPreDraw() { public boolean onPreDraw() {
mEnterTransitionView.getViewTreeObserver().removeOnPreDrawListener(this); mEnterTransitionView.getViewTreeObserver().removeOnPreDrawListener(this);
updateImageDimensions(); updateImageDimensions();
startPostponedEnterTransition(); mEnterTransitionView.post(() -> {
if (isActivityTransitionRunning()) { Rect dest = new Rect();
getWindow().getSharedElementEnterTransition().addListener( mEnterTransitionView.getBoundsOnScreen(dest);
new TransitionListenerAdapter() { mLongScreenshotHolder.takeTransitionDestinationCallback()
@Override .setTransitionDestination(dest, () -> {
public void onTransitionEnd(Transition transition) {
super.onTransitionEnd(transition);
mPreview.animate().alpha(1f); mPreview.animate().alpha(1f);
mCropView.setBoundaryPosition( mCropView.setBoundaryPosition(
CropView.CropBoundary.TOP, topFraction); CropView.CropBoundary.TOP, topFraction);
@@ -226,9 +221,8 @@ public class LongScreenshotActivity extends Activity {
mCropView.setVisibility(View.VISIBLE); mCropView.setVisibility(View.VISIBLE);
setButtonsEnabled(true); setButtonsEnabled(true);
mEnterTransitionView.setVisibility(View.GONE); mEnterTransitionView.setVisibility(View.GONE);
}
}); });
} });
return true; return true;
} }
}); });

View File

@@ -23,16 +23,19 @@ import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
* LongScreenshotHolder holds on to 1 LongScreenshot reference to facilitate indirect in-process * LongScreenshotData holds on to 1 LongScreenshot reference and 1 TransitionDestination
* passing. * reference, to facilitate indirect in-process passing.
*/ */
@SysUISingleton @SysUISingleton
public class LongScreenshotHolder { public class LongScreenshotData {
private final AtomicReference<ScrollCaptureController.LongScreenshot> mLongScreenshot; private final AtomicReference<ScrollCaptureController.LongScreenshot> mLongScreenshot;
private final AtomicReference<ScreenshotController.TransitionDestination>
mTransitionDestinationCallback;
@Inject @Inject
public LongScreenshotHolder() { public LongScreenshotData() {
mLongScreenshot = new AtomicReference<>(); mLongScreenshot = new AtomicReference<>();
mTransitionDestinationCallback = new AtomicReference<>();
} }
/** /**
@@ -56,4 +59,18 @@ public class LongScreenshotHolder {
return mLongScreenshot.getAndSet(null); 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);
}
} }

View File

@@ -64,6 +64,7 @@ import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner; import android.view.IRemoteAnimationRunner;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget; import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse; import android.view.ScrollCaptureResponse;
import android.view.SurfaceControl; import android.view.SurfaceControl;
@@ -72,6 +73,7 @@ import android.view.ViewTreeObserver;
import android.view.Window; import android.view.Window;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.widget.Toast; import android.widget.Toast;
@@ -203,6 +205,15 @@ public class ScreenshotController {
void onActionsReady(ScreenshotController.QuickShareData quickShareData); 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 // These strings are used for communicating the action invoked to
// ScreenshotNotificationSmartActionsProvider. // ScreenshotNotificationSmartActionsProvider.
static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
@@ -241,7 +252,7 @@ public class ScreenshotController {
private final PhoneWindow mWindow; private final PhoneWindow mWindow;
private final DisplayManager mDisplayManager; private final DisplayManager mDisplayManager;
private final ScrollCaptureController mScrollCaptureController; private final ScrollCaptureController mScrollCaptureController;
private final LongScreenshotHolder mLongScreenshotHolder; private final LongScreenshotData mLongScreenshotHolder;
private ScreenshotView mScreenshotView; private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap; private Bitmap mScreenBitmap;
@@ -286,7 +297,7 @@ public class ScreenshotController {
ImageExporter imageExporter, ImageExporter imageExporter,
@Main Executor mainExecutor, @Main Executor mainExecutor,
ScrollCaptureController scrollCaptureController, ScrollCaptureController scrollCaptureController,
LongScreenshotHolder longScreenshotHolder) { LongScreenshotData longScreenshotHolder) {
mScreenshotSmartActions = screenshotSmartActions; mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController; mNotificationsController = screenshotNotificationsController;
mScrollCaptureClient = scrollCaptureClient; mScrollCaptureClient = scrollCaptureClient;
@@ -648,17 +659,29 @@ public class ScreenshotController {
} }
mLongScreenshotHolder.setLongScreenshot(longScreenshot); mLongScreenshotHolder.setLongScreenshot(longScreenshot);
mLongScreenshotHolder.setTransitionDestinationCallback(
(transitionDestination, onTransitionEnd) ->
mScreenshotView.startLongScreenshotTransition(
transitionDestination, onTransitionEnd,
longScreenshot));
final Intent intent = new Intent(mContext, LongScreenshotActivity.class); final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Pair<ActivityOptions, ExitTransitionCoordinator> transition = Pair<ActivityOptions, ExitTransitionCoordinator> transition =
ActivityOptions.startSharedElementAnimation( ActivityOptions.startSharedElementAnimation(mWindow,
mWindow, new ScreenshotExitTransitionCallbacks(), null, new ScreenshotExitTransitionCallbacksSupplier(false).get(),
Pair.create(mScreenshotView.getScrollablePreview(), null);
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
transition.second.startExit(); transition.second.startExit();
mContext.startActivity(intent, transition.first.toBundle()); 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); }, mMainExecutor);
}); });
} catch (CancellationException e) { } catch (CancellationException e) {
@@ -883,8 +906,8 @@ public class ScreenshotController {
return () -> { return () -> {
Pair<ActivityOptions, ExitTransitionCoordinator> transition = Pair<ActivityOptions, ExitTransitionCoordinator> transition =
ActivityOptions.startSharedElementAnimation( ActivityOptions.startSharedElementAnimation(
mWindow, new ScreenshotExitTransitionCallbacks(), null, mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(),
Pair.create(mScreenshotView.getScreenshotPreview(), null, Pair.create(mScreenshotView.getScreenshotPreview(),
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
transition.second.startExit(); transition.second.startExit();
@@ -970,7 +993,17 @@ public class ScreenshotController {
return matchWithinTolerance; 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 @Override
public boolean isReturnTransitionAllowed() { public boolean isReturnTransitionAllowed() {
return false; return false;
@@ -978,11 +1011,15 @@ public class ScreenshotController {
@Override @Override
public void hideSharedElements() { public void hideSharedElements() {
if (mDismissOnHideSharedElements) {
finishDismiss(); finishDismiss();
} }
}
@Override @Override
public void onFinish() { public void onFinish() {
} }
};
}
} }
} }

View File

@@ -764,7 +764,53 @@ public class ScreenshotView extends FrameLayout implements
return r; 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) { void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap) {
mScrollingScrim.setImageBitmap(screenBitmap);
mScrollingScrim.setVisibility(View.VISIBLE);
Rect scrollableArea = scrollableAreaOnScreen(response); Rect scrollableArea = scrollableAreaOnScreen(response);
float scale = mCornerSizeX float scale = mCornerSizeX
/ (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight()); / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
@@ -775,14 +821,12 @@ public class ScreenshotView extends FrameLayout implements
params.height = (int) (scale * scrollableArea.height()); params.height = (int) (scale * scrollableArea.height());
Matrix matrix = new Matrix(); Matrix matrix = new Matrix();
matrix.setScale(scale, scale); matrix.setScale(scale, scale);
matrix.postTranslate(0, -scrollableArea.top * scale); matrix.postTranslate(-scrollableArea.left * scale, -scrollableArea.top * scale);
mScrollablePreview.setTranslationX(scale * scrollableArea.left); mScrollablePreview.setTranslationX(scale * scrollableArea.left);
mScrollablePreview.setTranslationY(scale * scrollableArea.top); mScrollablePreview.setTranslationY(scale * scrollableArea.top);
mScrollablePreview.setImageMatrix(matrix); mScrollablePreview.setImageMatrix(matrix);
mScrollingScrim.setImageBitmap(screenBitmap);
mScrollingScrim.setVisibility(View.VISIBLE);
mScrollablePreview.setImageBitmap(screenBitmap); mScrollablePreview.setImageBitmap(screenBitmap);
mScrollablePreview.setVisibility(View.VISIBLE); mScrollablePreview.setVisibility(View.VISIBLE);
createScreenshotFadeDismissAnimation(true).start(); createScreenshotFadeDismissAnimation(true).start();