Loading core/java/com/android/internal/app/ChooserActivity.java +48 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.SharedElementCallback; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionManager; import android.app.prediction.AppPredictor; Loading Loading @@ -176,6 +177,13 @@ public class ChooserActivity extends ResolverActivity implements public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP"; /** * Transition name for the first image preview. * To be used for shared element transition into this activity. * @hide */ public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "chooser_preview_image_1"; private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions"; private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label"; Loading Loading @@ -307,6 +315,8 @@ public class ChooserActivity extends ResolverActivity implements @VisibleForTesting protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter; private boolean mRemoveSharedElements = false; private class ContentPreviewCoordinator { private static final int IMAGE_FADE_IN_MILLIS = 150; private static final int IMAGE_LOAD_TIMEOUT = 1; Loading Loading @@ -370,10 +380,29 @@ public class ChooserActivity extends ResolverActivity implements if (task.mExtraCount > 0) { imageView.setExtraImageCount(task.mExtraCount); } setupPreDrawForSharedElementTransition(imageView); } } }; private void setupPreDrawForSharedElementTransition(View v) { v.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { v.getViewTreeObserver().removeOnPreDrawListener(this); if (!mRemoveSharedElements && isActivityTransitionRunning()) { // Disable the window animations as it interferes with the // transition animation. getWindow().setWindowAnimations(0); } startPostponedEnterTransition(); return true; } }); } ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) { super(); Loading Loading @@ -413,6 +442,8 @@ public class ChooserActivity extends ResolverActivity implements } mHideParentOnFail = false; } mRemoveSharedElements = true; startPostponedEnterTransition(); } private void collapseParentView() { Loading Loading @@ -794,6 +825,19 @@ public class ChooserActivity extends ResolverActivity implements ); mDirectShareShortcutInfoCache = new HashMap<>(); mChooserTargetComponentNameCache = new HashMap<>(); setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { if (mRemoveSharedElements) { names.remove(FIRST_IMAGE_PREVIEW_TRANSITION_NAME); sharedElements.remove(FIRST_IMAGE_PREVIEW_TRANSITION_NAME); } super.onMapSharedElements(names, sharedElements); mRemoveSharedElements = false; } }); postponeEnterTransition(); } @Override Loading Loading @@ -1337,6 +1381,8 @@ public class ChooserActivity extends ResolverActivity implements String action = targetIntent.getAction(); if (Intent.ACTION_SEND.equals(action)) { Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); imagePreview.findViewById(R.id.content_preview_image_1_large) .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0); } else { ContentResolver resolver = getContentResolver(); Loading @@ -1356,6 +1402,8 @@ public class ChooserActivity extends ResolverActivity implements return contentPreviewLayout; } imagePreview.findViewById(R.id.content_preview_image_1_large) .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0); if (imageUris.size() == 2) { Loading packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +69 −59 Original line number Diff line number Diff line Loading @@ -57,6 +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 java.io.File; import java.io.IOException; Loading @@ -75,6 +76,7 @@ import java.util.Objects; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; /** * An AsyncTask that saves an image to the media store in the background. Loading @@ -96,12 +98,15 @@ 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; SaveImageInBackgroundTask(Context context, ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data) { ScreenshotController.SaveImageInBackgroundData data, Supplier<ShareTransition> sharedElementTransition) { mContext = context; mScreenshotSmartActions = screenshotSmartActions; mImageData = new ScreenshotController.SavedImageData(); mSharedElementTransition = sharedElementTransition; // Prepare all the output metadata mParams = data; Loading Loading @@ -136,7 +141,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { ContentResolver resolver = mContext.getContentResolver(); Bitmap image = mParams.image; Resources r = mContext.getResources(); try { // Save the screenshot to the MediaStore Loading Loading @@ -234,7 +238,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageData.uri = uri; mImageData.smartActions = smartActions; mImageData.shareAction = createShareAction(mContext, mContext.getResources(), uri); mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri); mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri); mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); Loading Loading @@ -285,8 +289,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mParams.clearImage(); } /** * Assumes that the action intent is sent immediately after being supplied. */ @VisibleForTesting Notification.Action createShareAction(Context context, Resources r, Uri uri) { Supplier<ShareTransition> createShareAction(Context context, Resources r, Uri uri) { return () -> { ShareTransition 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 Loading @@ -311,8 +321,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null) Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Loading @@ -320,7 +329,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { PendingIntent pendingIntent = PendingIntent.getActivityAsUser( context, 0, sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT); transition.bundle, UserHandle.CURRENT); // Create a share action for the notification PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, Loading @@ -339,7 +348,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Icon.createWithResource(r, R.drawable.ic_screenshot_share), r.getString(com.android.internal.R.string.share), shareAction); return shareActionBuilder.build(); transition.shareAction = shareActionBuilder.build(); return transition; }; } @VisibleForTesting Loading @@ -355,8 +366,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { if (!TextUtils.isEmpty(editorPackage)) { editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); } editIntent.setType("image/png"); editIntent.setData(uri); 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); Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +50 −3 Original line number Diff line number Diff line Loading @@ -34,6 +34,9 @@ import static java.util.Objects.requireNonNull; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.ExitTransitionCoordinator; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.Notification; import android.content.ComponentName; import android.content.Context; Loading @@ -46,6 +49,7 @@ import android.hardware.display.DisplayManager; import android.media.MediaActionSound; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; Loading @@ -54,6 +58,7 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.view.Display; import android.view.KeyEvent; import android.view.LayoutInflater; Loading @@ -67,15 +72,18 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.android.internal.app.ChooserActivity; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 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.util.DeviceConfigProxy; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; import javax.inject.Inject; Loading Loading @@ -103,17 +111,26 @@ public class ScreenshotController { */ static class SavedImageData { public Uri uri; public Notification.Action shareAction; public Supplier<ShareTransition> shareTransition; public Notification.Action editAction; public Notification.Action deleteAction; public List<Notification.Action> smartActions; /** * POD for shared element transition to share sheet. */ static class ShareTransition { public Bundle bundle; public Notification.Action shareAction; public Runnable onCancelRunnable; } /** * Used to reset the return data on error */ public void reset() { uri = null; shareAction = null; shareTransition = null; editAction = null; deleteAction = null; smartActions = null; Loading Loading @@ -587,7 +604,8 @@ public class ScreenshotController { mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, getShareTransitionSupplier()); mSaveInBgTask.execute(); } Loading Loading @@ -637,6 +655,35 @@ 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() { return () -> { ExitTransitionCallbacks cb = new ExitTransitionCallbacks() { @Override public boolean isReturnTransitionAllowed() { return false; } @Override public void onFinish() { } }; Pair<ActivityOptions, ExitTransitionCoordinator> transition = ActivityOptions.startSharedElementAnimation(mWindow, cb, null, Pair.create(mScreenshotView.getScreenshotPreview(), ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); transition.second.startExit(); ShareTransition supply = new ShareTransition(); supply.bundle = transition.first.toBundle(); supply.onCancelRunnable = () -> ActivityOptions.stopSharedElementAnimation(mWindow); return supply; }; } /** * Logs success/failure of the screenshot saving task, and shows an error if it failed. */ Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +28 −4 Original line number Diff line number Diff line Loading @@ -73,6 +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.shared.system.QuickStepContract; import java.util.ArrayList; Loading Loading @@ -104,6 +105,7 @@ 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 Loading Loading @@ -138,6 +140,7 @@ public class ScreenshotView extends FrameLayout implements private UiEventLogger mUiEventLogger; private ScreenshotViewCallback mCallbacks; private Animator mDismissAnimation; private boolean mIgnoreDismiss; private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>(); private PendingInteraction mPendingInteraction; Loading Loading @@ -526,12 +529,30 @@ public class ScreenshotView extends FrameLayout implements }); return animator; } protected View getScreenshotPreview() { return mScreenshotPreview; } void setChipIntents(ScreenshotController.SavedImageData imageData) { mShareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> { 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); } }); mEditChip.setPendingIntent(imageData.editAction.actionIntent, () -> { Loading Loading @@ -589,6 +610,9 @@ public class ScreenshotView extends FrameLayout implements } private void animateDismissal(Animator dismissAnimation) { if (mIgnoreDismiss) { return; } if (DEBUG_WINDOW) { Log.d(TAG, "removing OnComputeInternalInsetsListener"); } Loading packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +8 −4 Original line number Diff line number Diff line Loading @@ -43,6 +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 org.junit.Before; import org.junit.Test; Loading Loading @@ -175,10 +176,11 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, ShareTransition::new); Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); Uri.parse("Screenshot_123.png")).get().shareAction; Intent intent = shareAction.actionIntent.getIntent(); assertNotNull(intent); Loading @@ -202,7 +204,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, ShareTransition::new); Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); Loading @@ -229,7 +232,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, ShareTransition::new); Notification.Action deleteAction = task.createDeleteAction(mContext, mContext.getResources(), Loading Loading
core/java/com/android/internal/app/ChooserActivity.java +48 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.SharedElementCallback; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionManager; import android.app.prediction.AppPredictor; Loading Loading @@ -176,6 +177,13 @@ public class ChooserActivity extends ResolverActivity implements public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP"; /** * Transition name for the first image preview. * To be used for shared element transition into this activity. * @hide */ public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "chooser_preview_image_1"; private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions"; private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label"; Loading Loading @@ -307,6 +315,8 @@ public class ChooserActivity extends ResolverActivity implements @VisibleForTesting protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter; private boolean mRemoveSharedElements = false; private class ContentPreviewCoordinator { private static final int IMAGE_FADE_IN_MILLIS = 150; private static final int IMAGE_LOAD_TIMEOUT = 1; Loading Loading @@ -370,10 +380,29 @@ public class ChooserActivity extends ResolverActivity implements if (task.mExtraCount > 0) { imageView.setExtraImageCount(task.mExtraCount); } setupPreDrawForSharedElementTransition(imageView); } } }; private void setupPreDrawForSharedElementTransition(View v) { v.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { v.getViewTreeObserver().removeOnPreDrawListener(this); if (!mRemoveSharedElements && isActivityTransitionRunning()) { // Disable the window animations as it interferes with the // transition animation. getWindow().setWindowAnimations(0); } startPostponedEnterTransition(); return true; } }); } ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) { super(); Loading Loading @@ -413,6 +442,8 @@ public class ChooserActivity extends ResolverActivity implements } mHideParentOnFail = false; } mRemoveSharedElements = true; startPostponedEnterTransition(); } private void collapseParentView() { Loading Loading @@ -794,6 +825,19 @@ public class ChooserActivity extends ResolverActivity implements ); mDirectShareShortcutInfoCache = new HashMap<>(); mChooserTargetComponentNameCache = new HashMap<>(); setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { if (mRemoveSharedElements) { names.remove(FIRST_IMAGE_PREVIEW_TRANSITION_NAME); sharedElements.remove(FIRST_IMAGE_PREVIEW_TRANSITION_NAME); } super.onMapSharedElements(names, sharedElements); mRemoveSharedElements = false; } }); postponeEnterTransition(); } @Override Loading Loading @@ -1337,6 +1381,8 @@ public class ChooserActivity extends ResolverActivity implements String action = targetIntent.getAction(); if (Intent.ACTION_SEND.equals(action)) { Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); imagePreview.findViewById(R.id.content_preview_image_1_large) .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0); } else { ContentResolver resolver = getContentResolver(); Loading @@ -1356,6 +1402,8 @@ public class ChooserActivity extends ResolverActivity implements return contentPreviewLayout; } imagePreview.findViewById(R.id.content_preview_image_1_large) .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0); if (imageUris.size() == 2) { Loading
packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +69 −59 Original line number Diff line number Diff line Loading @@ -57,6 +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 java.io.File; import java.io.IOException; Loading @@ -75,6 +76,7 @@ import java.util.Objects; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; /** * An AsyncTask that saves an image to the media store in the background. Loading @@ -96,12 +98,15 @@ 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; SaveImageInBackgroundTask(Context context, ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data) { ScreenshotController.SaveImageInBackgroundData data, Supplier<ShareTransition> sharedElementTransition) { mContext = context; mScreenshotSmartActions = screenshotSmartActions; mImageData = new ScreenshotController.SavedImageData(); mSharedElementTransition = sharedElementTransition; // Prepare all the output metadata mParams = data; Loading Loading @@ -136,7 +141,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { ContentResolver resolver = mContext.getContentResolver(); Bitmap image = mParams.image; Resources r = mContext.getResources(); try { // Save the screenshot to the MediaStore Loading Loading @@ -234,7 +238,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageData.uri = uri; mImageData.smartActions = smartActions; mImageData.shareAction = createShareAction(mContext, mContext.getResources(), uri); mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri); mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri); mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); Loading Loading @@ -285,8 +289,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mParams.clearImage(); } /** * Assumes that the action intent is sent immediately after being supplied. */ @VisibleForTesting Notification.Action createShareAction(Context context, Resources r, Uri uri) { Supplier<ShareTransition> createShareAction(Context context, Resources r, Uri uri) { return () -> { ShareTransition 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 Loading @@ -311,8 +321,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null) Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Loading @@ -320,7 +329,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { PendingIntent pendingIntent = PendingIntent.getActivityAsUser( context, 0, sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT); transition.bundle, UserHandle.CURRENT); // Create a share action for the notification PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, Loading @@ -339,7 +348,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Icon.createWithResource(r, R.drawable.ic_screenshot_share), r.getString(com.android.internal.R.string.share), shareAction); return shareActionBuilder.build(); transition.shareAction = shareActionBuilder.build(); return transition; }; } @VisibleForTesting Loading @@ -355,8 +366,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { if (!TextUtils.isEmpty(editorPackage)) { editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); } editIntent.setType("image/png"); editIntent.setData(uri); 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); Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +50 −3 Original line number Diff line number Diff line Loading @@ -34,6 +34,9 @@ import static java.util.Objects.requireNonNull; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.ExitTransitionCoordinator; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.Notification; import android.content.ComponentName; import android.content.Context; Loading @@ -46,6 +49,7 @@ import android.hardware.display.DisplayManager; import android.media.MediaActionSound; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; Loading @@ -54,6 +58,7 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.view.Display; import android.view.KeyEvent; import android.view.LayoutInflater; Loading @@ -67,15 +72,18 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.android.internal.app.ChooserActivity; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 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.util.DeviceConfigProxy; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; import javax.inject.Inject; Loading Loading @@ -103,17 +111,26 @@ public class ScreenshotController { */ static class SavedImageData { public Uri uri; public Notification.Action shareAction; public Supplier<ShareTransition> shareTransition; public Notification.Action editAction; public Notification.Action deleteAction; public List<Notification.Action> smartActions; /** * POD for shared element transition to share sheet. */ static class ShareTransition { public Bundle bundle; public Notification.Action shareAction; public Runnable onCancelRunnable; } /** * Used to reset the return data on error */ public void reset() { uri = null; shareAction = null; shareTransition = null; editAction = null; deleteAction = null; smartActions = null; Loading Loading @@ -587,7 +604,8 @@ public class ScreenshotController { mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, getShareTransitionSupplier()); mSaveInBgTask.execute(); } Loading Loading @@ -637,6 +655,35 @@ 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() { return () -> { ExitTransitionCallbacks cb = new ExitTransitionCallbacks() { @Override public boolean isReturnTransitionAllowed() { return false; } @Override public void onFinish() { } }; Pair<ActivityOptions, ExitTransitionCoordinator> transition = ActivityOptions.startSharedElementAnimation(mWindow, cb, null, Pair.create(mScreenshotView.getScreenshotPreview(), ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); transition.second.startExit(); ShareTransition supply = new ShareTransition(); supply.bundle = transition.first.toBundle(); supply.onCancelRunnable = () -> ActivityOptions.stopSharedElementAnimation(mWindow); return supply; }; } /** * Logs success/failure of the screenshot saving task, and shows an error if it failed. */ Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +28 −4 Original line number Diff line number Diff line Loading @@ -73,6 +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.shared.system.QuickStepContract; import java.util.ArrayList; Loading Loading @@ -104,6 +105,7 @@ 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 Loading Loading @@ -138,6 +140,7 @@ public class ScreenshotView extends FrameLayout implements private UiEventLogger mUiEventLogger; private ScreenshotViewCallback mCallbacks; private Animator mDismissAnimation; private boolean mIgnoreDismiss; private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>(); private PendingInteraction mPendingInteraction; Loading Loading @@ -526,12 +529,30 @@ public class ScreenshotView extends FrameLayout implements }); return animator; } protected View getScreenshotPreview() { return mScreenshotPreview; } void setChipIntents(ScreenshotController.SavedImageData imageData) { mShareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> { 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); } }); mEditChip.setPendingIntent(imageData.editAction.actionIntent, () -> { Loading Loading @@ -589,6 +610,9 @@ public class ScreenshotView extends FrameLayout implements } private void animateDismissal(Animator dismissAnimation) { if (mIgnoreDismiss) { return; } if (DEBUG_WINDOW) { Log.d(TAG, "removing OnComputeInternalInsetsListener"); } Loading
packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +8 −4 Original line number Diff line number Diff line Loading @@ -43,6 +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 org.junit.Before; import org.junit.Test; Loading Loading @@ -175,10 +176,11 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, ShareTransition::new); Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); Uri.parse("Screenshot_123.png")).get().shareAction; Intent intent = shareAction.actionIntent.getIntent(); assertNotNull(intent); Loading @@ -202,7 +204,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, ShareTransition::new); Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); Loading @@ -229,7 +232,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data, ShareTransition::new); Notification.Action deleteAction = task.createDeleteAction(mContext, mContext.getResources(), Loading