Add shared transitions for screenshot->markup

Bug: 170138961
Bug: 159419046
Test: manual

Change-Id: I3a9c7487cbc36a4a7183030d2e3aa432958f7e1a
This commit is contained in:
Miranda Kephart
2020-12-16 21:15:18 -05:00
parent 246e924066
commit 6b330ef7ee
6 changed files with 127 additions and 100 deletions

View File

@@ -182,7 +182,7 @@ public class ChooserActivity extends ResolverActivity implements
* To be used for shared element transition into this activity.
* @hide
*/
public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "chooser_preview_image_1";
public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "screenshot_preview_image";
private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";

View File

@@ -57,7 +57,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ShareTransition;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import java.io.File;
import java.io.IOException;
@@ -98,11 +98,11 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private final String mScreenshotId;
private final boolean mSmartActionsEnabled;
private final Random mRandom = new Random();
private final Supplier<ShareTransition> mSharedElementTransition;
private final Supplier<ActionTransition> mSharedElementTransition;
SaveImageInBackgroundTask(Context context, ScreenshotSmartActions screenshotSmartActions,
ScreenshotController.SaveImageInBackgroundData data,
Supplier<ShareTransition> sharedElementTransition) {
Supplier<ActionTransition> sharedElementTransition) {
mContext = context;
mScreenshotSmartActions = screenshotSmartActions;
mImageData = new ScreenshotController.SavedImageData();
@@ -239,7 +239,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mImageData.uri = uri;
mImageData.smartActions = smartActions;
mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri);
mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
mParams.mActionsReadyListener.onActionsReady(mImageData);
@@ -293,9 +293,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
* Assumes that the action intent is sent immediately after being supplied.
*/
@VisibleForTesting
Supplier<ShareTransition> createShareAction(Context context, Resources r, Uri uri) {
Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri) {
return () -> {
ShareTransition transition = mSharedElementTransition.get();
ActionTransition transition = mSharedElementTransition.get();
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
// order to do some common work like dismissing the keyguard and sending
@@ -348,52 +348,57 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Icon.createWithResource(r, R.drawable.ic_screenshot_share),
r.getString(com.android.internal.R.string.share), shareAction);
transition.shareAction = shareActionBuilder.build();
transition.action = shareActionBuilder.build();
return transition;
};
}
@VisibleForTesting
Notification.Action createEditAction(Context context, Resources r, Uri uri) {
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
// order to do some common work like dismissing the keyguard and sending
// closeSystemWindows
Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri) {
return () -> {
ActionTransition transition = mSharedElementTransition.get();
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
// order to do some common work like dismissing the keyguard and sending
// closeSystemWindows
// Create an edit intent, if a specific package is provided as the editor, then
// launch that directly
String editorPackage = context.getString(R.string.config_screenshotEditor);
Intent editIntent = new Intent(Intent.ACTION_EDIT);
if (!TextUtils.isEmpty(editorPackage)) {
editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
}
editIntent.setDataAndType(uri, "image/png");
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
// Create an edit intent, if a specific package is provided as the editor, then
// launch that directly
String editorPackage = context.getString(R.string.config_screenshotEditor);
Intent editIntent = new Intent(Intent.ACTION_EDIT);
if (!TextUtils.isEmpty(editorPackage)) {
editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
}
editIntent.setDataAndType(uri, "image/png");
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
editIntent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
context, 0, editIntent, PendingIntent.FLAG_IMMUTABLE,
transition.bundle, UserHandle.CURRENT);
// Make sure pending intents for the system user are still unique across users
// by setting the (otherwise unused) request code to the current user id.
int requestCode = mContext.getUserId();
// Make sure pending intents for the system user are still unique across users
// by setting the (otherwise unused) request code to the current user id.
int requestCode = mContext.getUserId();
// Create a edit action
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
new Intent(context, ActionProxyReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
mSmartActionsEnabled)
.setAction(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
UserHandle.SYSTEM);
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
// Create a edit action
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
new Intent(context, ActionProxyReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
mSmartActionsEnabled)
.setAction(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
UserHandle.SYSTEM);
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
return editActionBuilder.build();
transition.action = editActionBuilder.build();
return transition;
};
}
@VisibleForTesting

View File

@@ -78,7 +78,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.R;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ShareTransition;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.util.DeviceConfigProxy;
import java.util.List;
@@ -111,17 +111,17 @@ public class ScreenshotController {
*/
static class SavedImageData {
public Uri uri;
public Supplier<ShareTransition> shareTransition;
public Notification.Action editAction;
public Supplier<ActionTransition> shareTransition;
public Supplier<ActionTransition> editTransition;
public Notification.Action deleteAction;
public List<Notification.Action> smartActions;
/**
* POD for shared element transition to share sheet.
* POD for shared element transition.
*/
static class ShareTransition {
static class ActionTransition {
public Bundle bundle;
public Notification.Action shareAction;
public Notification.Action action;
public Runnable onCancelRunnable;
}
@@ -131,7 +131,7 @@ public class ScreenshotController {
public void reset() {
uri = null;
shareTransition = null;
editAction = null;
editTransition = null;
deleteAction = null;
smartActions = null;
}
@@ -339,6 +339,10 @@ public class ScreenshotController {
}
}
boolean isPendingSharedTransition() {
return mScreenshotView.isPendingSharedTransition();
}
/**
* Update resources on configuration change. Reinflate for theme/color changes.
*/
@@ -462,7 +466,7 @@ public class ScreenshotController {
Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
+ "(dismissing=" + mScreenshotView.isDismissing() + ")");
}
mScreenshotView.reset();
reloadAssets();
}
mScreenBitmap = screenshot;
@@ -605,7 +609,7 @@ public class ScreenshotController {
}
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data,
getShareTransitionSupplier());
getActionTransitionSupplier());
mSaveInBgTask.execute();
}
@@ -659,7 +663,7 @@ public class ScreenshotController {
* Supplies the necessary bits for the shared element transition to share sheet.
* Note that once supplied, the action intent to share must be sent immediately after.
*/
private Supplier<ShareTransition> getShareTransitionSupplier() {
private Supplier<ActionTransition> getActionTransitionSupplier() {
return () -> {
ExitTransitionCallbacks cb = new ExitTransitionCallbacks() {
@Override
@@ -668,7 +672,8 @@ public class ScreenshotController {
}
@Override
public void onFinish() { }
public void onFinish() {
}
};
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
@@ -677,7 +682,7 @@ public class ScreenshotController {
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
transition.second.startExit();
ShareTransition supply = new ShareTransition();
ActionTransition supply = new ActionTransition();
supply.bundle = transition.first.toBundle();
supply.onCancelRunnable = () -> ActivityOptions.stopSharedElementAnimation(mWindow);
return supply;

View File

@@ -73,7 +73,7 @@ import android.widget.LinearLayout;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ShareTransition;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.shared.system.QuickStepContract;
import java.util.ArrayList;
@@ -105,7 +105,6 @@ public class ScreenshotView extends FrameLayout implements
private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350;
private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183;
private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
private static final long SCREENSHOT_DISMISS_SHARE_OFFSET_MS = 300; // delay after share clicked
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
private static final float ROUNDED_CORNER_RADIUS = .05f;
private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
@@ -140,7 +139,7 @@ public class ScreenshotView extends FrameLayout implements
private UiEventLogger mUiEventLogger;
private ScreenshotViewCallback mCallbacks;
private Animator mDismissAnimation;
private boolean mIgnoreDismiss;
private boolean mPendingSharedTransition;
private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
@@ -292,6 +291,10 @@ public class ScreenshotView extends FrameLayout implements
requestFocus();
}
View getScreenshotPreview() {
return mScreenshotPreview;
}
/**
* Set up the logger and callback on dismissal.
*
@@ -529,44 +532,22 @@ public class ScreenshotView extends FrameLayout implements
});
return animator;
}
protected View getScreenshotPreview() {
return mScreenshotPreview;
}
void setChipIntents(ScreenshotController.SavedImageData imageData) {
mShareChip.setOnClickListener(v -> {
ShareTransition transition = imageData.shareTransition.get();
try {
mIgnoreDismiss = true;
transition.shareAction.actionIntent.send();
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED);
// Ensures that we delay dismissing until transition has started.
postDelayed(() -> {
mIgnoreDismiss = false;
animateDismissal();
}, SCREENSHOT_DISMISS_SHARE_OFFSET_MS);
} catch (PendingIntent.CanceledException e) {
mIgnoreDismiss = false;
if (transition.onCancelRunnable != null) {
transition.onCancelRunnable.run();
}
Log.e(TAG, "Share intent cancelled", e);
}
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED);
startSharedTransition(
imageData.shareTransition.get());
});
mEditChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED);
startSharedTransition(
imageData.editTransition.get());
});
mEditChip.setPendingIntent(imageData.editAction.actionIntent,
() -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED);
animateDismissal();
});
mScreenshotPreview.setOnClickListener(v -> {
try {
imageData.editAction.actionIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "PendingIntent was cancelled", e);
}
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
animateDismissal();
startSharedTransition(
imageData.editTransition.get());
});
if (mPendingInteraction != null) {
@@ -605,12 +586,16 @@ public class ScreenshotView extends FrameLayout implements
return (mDismissAnimation != null && mDismissAnimation.isRunning());
}
boolean isPendingSharedTransition() {
return mPendingSharedTransition;
}
void animateDismissal() {
animateDismissal(createScreenshotDismissAnimation());
animateDismissal(createScreenshotTranslateDismissAnimation());
}
private void animateDismissal(Animator dismissAnimation) {
if (mIgnoreDismiss) {
if (mPendingSharedTransition) {
return;
}
if (DEBUG_WINDOW) {
@@ -665,6 +650,7 @@ public class ScreenshotView extends FrameLayout implements
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
// Clear any references to the bitmap
mScreenshotPreview.setImageDrawable(null);
mPendingSharedTransition = false;
mActionsContainerBackground.setVisibility(View.GONE);
mActionsContainer.setVisibility(View.GONE);
mBackgroundProtection.setAlpha(0f);
@@ -692,7 +678,23 @@ public class ScreenshotView extends FrameLayout implements
mScreenshotSelectorView.stop();
}
private AnimatorSet createScreenshotDismissAnimation() {
private void startSharedTransition(ActionTransition transition) {
try {
mPendingSharedTransition = true;
transition.action.actionIntent.send();
// fade out non-preview UI
createScreenshotFadeDismissAnimation().start();
} catch (PendingIntent.CanceledException e) {
mPendingSharedTransition = false;
if (transition.onCancelRunnable != null) {
transition.onCancelRunnable.run();
}
Log.e(TAG, "Intent cancelled", e);
}
}
private AnimatorSet createScreenshotTranslateDismissAnimation() {
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
@@ -719,6 +721,19 @@ public class ScreenshotView extends FrameLayout implements
return animSet;
}
private ValueAnimator createScreenshotFadeDismissAnimation() {
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.addUpdateListener(animation -> {
float alpha = 1 - animation.getAnimatedFraction();
mDismissButton.setAlpha(alpha);
mActionsContainerBackground.setAlpha(alpha);
mActionsContainer.setAlpha(alpha);
mBackgroundProtection.setAlpha(alpha);
});
alphaAnim.setDuration(600);
return alphaAnim;
}
/**
* Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
*/

View File

@@ -72,7 +72,9 @@ public class TakeScreenshotService extends Service {
if (DEBUG_DISMISS) {
Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
}
mScreenshot.dismissScreenshot(false);
if (!mScreenshot.isPendingSharedTransition()) {
mScreenshot.dismissScreenshot(false);
}
}
}
};

View File

@@ -43,7 +43,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ShareTransition;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import org.junit.Before;
import org.junit.Test;
@@ -177,10 +177,10 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
data.mActionsReadyListener = null;
SaveImageInBackgroundTask task =
new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data,
ShareTransition::new);
ActionTransition::new);
Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
Uri.parse("Screenshot_123.png")).get().shareAction;
Uri.parse("Screenshot_123.png")).get().action;
Intent intent = shareAction.actionIntent.getIntent();
assertNotNull(intent);
@@ -205,10 +205,10 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
data.mActionsReadyListener = null;
SaveImageInBackgroundTask task =
new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data,
ShareTransition::new);
ActionTransition::new);
Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
Uri.parse("Screenshot_123.png"));
Uri.parse("Screenshot_123.png")).get().action;
Intent intent = editAction.actionIntent.getIntent();
assertNotNull(intent);
@@ -233,7 +233,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
data.mActionsReadyListener = null;
SaveImageInBackgroundTask task =
new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data,
ShareTransition::new);
ActionTransition::new);
Notification.Action deleteAction = task.createDeleteAction(mContext,
mContext.getResources(),