Merge "Implement "shared element" SS->LSS transition in screenshots" into sc-dev am: 070852ed44

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15005300

Change-Id: I997b2b89fefbe2aea3f8818ed7cd01df4cb2a515
This commit is contained in:
Miranda Kephart
2021-06-21 12:21:24 +00:00
committed by Automerger Merge Worker
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.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;
}
});

View File

@@ -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);
}
}

View File

@@ -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<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) {
@@ -883,8 +906,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();
@@ -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<ExitTransitionCallbacks> {
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() {
}
};
}
}
}

View File

@@ -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();