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

Commit 0aaf18c6 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Screenshot to share sheet shared element transition."

parents ed766173 92dfd673
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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";
@@ -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;
@@ -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();

@@ -413,6 +442,8 @@ public class ChooserActivity extends ResolverActivity implements
                }
                mHideParentOnFail = false;
            }
            mRemoveSharedElements = true;
            startPostponedEnterTransition();
        }

        private void collapseParentView() {
@@ -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
@@ -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();
@@ -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) {
+69 −59
Original line number Diff line number Diff line
@@ -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;
@@ -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.
@@ -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;
@@ -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
@@ -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);

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

@@ -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,
@@ -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
@@ -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);
+50 −3
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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;

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

@@ -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.
     */
+28 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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;
@@ -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,
                () -> {
@@ -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");
        }
+8 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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);
@@ -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"));
@@ -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(),