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

Commit 0482828c authored by Song Hu's avatar Song Hu Committed by Android (Google) Code Review
Browse files

Merge "Query and surface Quick Share chip before screenshot image...

Merge "Query and surface Quick Share chip before screenshot image compress/export. Update chip intent, once image URL is available after compress/export." into sc-dev
parents 116e4c41 e55e544f
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -81,6 +81,12 @@ public final class SystemUiDeviceConfigFlags {
    public static final String SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS =
            "screenshot_notification_smart_actions_timeout_ms";

    /**
     * (int) Timeout value in ms to get Quick Share actions for screenshot notification.
     */
    public static final String SCREENSHOT_NOTIFICATION_QUICK_SHARE_ACTIONS_TIMEOUT_MS =
            "screenshot_notification_quick_share_actions_timeout_ms";

    // Flags related to Smart Suggestions - these are read in SmartReplyConstants.

    /** (boolean) Whether to enable smart suggestions in notifications. */
+87 −2
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE;
import static com.android.systemui.screenshot.LogConfig.logTag;
import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION;
import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS;

import android.app.ActivityTaskManager;
import android.app.Notification;
@@ -74,6 +76,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
    private final ScreenshotSmartActions mScreenshotSmartActions;
    private final ScreenshotController.SaveImageInBackgroundData mParams;
    private final ScreenshotController.SavedImageData mImageData;
    private final ScreenshotController.QuickShareData mQuickShareData;

    private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
    private String mScreenshotId;
@@ -90,6 +93,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
        mContext = context;
        mScreenshotSmartActions = screenshotSmartActions;
        mImageData = new ScreenshotController.SavedImageData();
        mQuickShareData = new ScreenshotController.QuickShareData();
        mSharedElementTransition = sharedElementTransition;
        mImageExporter = exporter;

@@ -127,6 +131,13 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
        Bitmap image = mParams.image;
        mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, requestId);
        try {
            if (mSmartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
                // Since Quick Share target recommendation does not rely on image URL, it is
                // queried and surfaced before image compress/export. Action intent would not be
                // used, because it does not contain image URL.
                queryQuickShareAction(image, user);
            }

            // Call synchronously here since already on a background thread.
            ListenableFuture<ImageExporter.Result> future =
                    mImageExporter.export(Runnable::run, requestId, image);
@@ -136,7 +147,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {

            CompletableFuture<List<Notification.Action>> smartActionsFuture =
                    mScreenshotSmartActions.getSmartActionsFuture(
                            mScreenshotId, uri, image, mSmartActionsProvider,
                            mScreenshotId, uri, image, mSmartActionsProvider, REGULAR_SMART_ACTIONS,
                            mSmartActionsEnabled, user);

            List<Notification.Action> smartActions = new ArrayList<>();
@@ -148,7 +159,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
                smartActions.addAll(buildSmartActions(
                        mScreenshotSmartActions.getSmartActions(
                                mScreenshotId, smartActionsFuture, timeoutMs,
                                mSmartActionsProvider),
                                mSmartActionsProvider, REGULAR_SMART_ACTIONS),
                        mContext));
            }

@@ -157,6 +168,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
            mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
            mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
            mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
            mImageData.quickShareAction = createQuickShareAction(mContext,
                    mQuickShareData.quickShareAction, uri);

            mParams.mActionsReadyListener.onActionsReady(mImageData);
            if (DEBUG_CALLBACK) {
@@ -173,6 +186,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
            }
            mParams.clearImage();
            mImageData.reset();
            mQuickShareData.reset();
            mParams.mActionsReadyListener.onActionsReady(mImageData);
            if (DEBUG_CALLBACK) {
                Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)");
@@ -197,6 +211,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
        // params. The finisher is expected to always be called back, so just use the baked-in
        // params from the ctor in any case.
        mImageData.reset();
        mQuickShareData.reset();
        mParams.mActionsReadyListener.onActionsReady(mImageData);
        if (DEBUG_CALLBACK) {
            Log.d(TAG, "onCancelled, calling (Consumer<Uri>) finisher.accept(null)");
@@ -389,4 +404,74 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
                .putExtra(ScreenshotController.EXTRA_ID, screenshotId)
                .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
    }

    /**
     * Populate image uri into intent of Quick Share action.
     */
    @VisibleForTesting
    private Notification.Action createQuickShareAction(Context context, Notification.Action action,
            Uri uri) {
        if (action == null) {
            return null;
        }
        // Populate image URI into Quick Share chip intent
        Intent sharingIntent = action.actionIntent.getIntent();
        sharingIntent.setType("image/png");
        sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
        String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
        String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
        sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
        // Include URI in ClipData also, so that grantPermission picks it up.
        // We don't use setData here because some apps interpret this as "to:".
        ClipData clipdata = new ClipData(new ClipDescription("content",
                new String[]{"image/png"}),
                new ClipData.Item(uri));
        sharingIntent.setClipData(clipdata);
        sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        PendingIntent updatedPendingIntent = PendingIntent.getActivity(
                context, 0, sharingIntent,
                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);

        // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
        // for logging smart actions.
        Bundle extras = action.getExtras();
        String actionType = extras.getString(
                ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
                ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
        Intent intent = new Intent(context, SmartActionsReceiver.class)
                .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent)
                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
        PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
                mRandom.nextInt(),
                intent,
                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        return new Notification.Action.Builder(action.getIcon(), action.title,
                broadcastIntent).setContextual(true).addExtras(extras).build();
    }

    /**
     * Query and surface Quick Share chip if it is available. Action intent would not be used,
     * because it does not contain image URL which would be populated in {@link
     * #createQuickShareAction(Context, Notification.Action, Uri)}
     */
    private void queryQuickShareAction(Bitmap image, UserHandle user) {
        CompletableFuture<List<Notification.Action>> quickShareActionsFuture =
                mScreenshotSmartActions.getSmartActionsFuture(
                        mScreenshotId, null, image, mSmartActionsProvider,
                        QUICK_SHARE_ACTION,
                        mSmartActionsEnabled, user);
        int timeoutMs = DeviceConfig.getInt(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_QUICK_SHARE_ACTIONS_TIMEOUT_MS,
                500);
        List<Notification.Action> quickShareActions =
                mScreenshotSmartActions.getSmartActions(
                        mScreenshotId, quickShareActionsFuture, timeoutMs,
                        mSmartActionsProvider, QUICK_SHARE_ACTION);
        if (!quickShareActions.isEmpty()) {
            mQuickShareData.quickShareAction = quickShareActions.get(0);
            mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
        }
    }
}
+66 −16
Original line number Diff line number Diff line
@@ -114,6 +114,7 @@ public class ScreenshotController {
        public Bitmap image;
        public Consumer<Uri> finisher;
        public ScreenshotController.ActionsReadyListener mActionsReadyListener;
        public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener;

        void clearImage() {
            image = null;
@@ -129,6 +130,7 @@ public class ScreenshotController {
        public Supplier<ActionTransition> editTransition;
        public Notification.Action deleteAction;
        public List<Notification.Action> smartActions;
        public Notification.Action quickShareAction;

        /**
         * POD for shared element transition.
@@ -148,6 +150,21 @@ public class ScreenshotController {
            editTransition = null;
            deleteAction = null;
            smartActions = null;
            quickShareAction = null;
        }
    }

    /**
     * Structure returned by the QueryQuickShareInBackgroundTask
     */
    static class QuickShareData {
        public Notification.Action quickShareAction;

        /**
         * Used to reset the return data on error
         */
        public void reset() {
            quickShareAction = null;
        }
    }

@@ -155,6 +172,10 @@ public class ScreenshotController {
        void onActionsReady(ScreenshotController.SavedImageData imageData);
    }

    interface QuickShareActionReadyListener {
        void onActionsReady(ScreenshotController.QuickShareData quickShareData);
    }

    // These strings are used for communicating the action invoked to
    // ScreenshotNotificationSmartActionsProvider.
    static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
@@ -519,7 +540,8 @@ public class ScreenshotController {
        mScreenBitmap.setHasAlpha(false);
        mScreenBitmap.prepareToDraw();

        saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady);
        saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady,
                this::showUiOnQuickShareActionReady);

        // The window is focusable by default
        setWindowFocusable(true);
@@ -677,7 +699,8 @@ public class ScreenshotController {
                        mScreenshotHandler.post(() -> Toast.makeText(mContext,
                                R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
                    }
        });
                },
                null);
    }

    /**
@@ -718,12 +741,15 @@ public class ScreenshotController {
     * Creates a new worker thread and saves the screenshot to the media store.
     */
    private void saveScreenshotInWorkerThread(Consumer<Uri> finisher,
            @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener) {
            @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener,
            @Nullable ScreenshotController.QuickShareActionReadyListener
                    quickShareActionsReadyListener) {
        ScreenshotController.SaveImageInBackgroundData
                data = new ScreenshotController.SaveImageInBackgroundData();
        data.image = mScreenBitmap;
        data.finisher = finisher;
        data.mActionsReadyListener = actionsReadyListener;
        data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;

        if (mSaveInBgTask != null) {
            // just log success/failure for the pre-existing screenshot
@@ -785,6 +811,30 @@ public class ScreenshotController {
        }
    }

    /**
     * Sets up the action shade and its entrance animation, once we get the Quick Share action data.
     */
    private void showUiOnQuickShareActionReady(ScreenshotController.QuickShareData quickShareData) {
        if (DEBUG_UI) {
            Log.d(TAG, "Showing UI for Quick Share action");
        }
        if (quickShareData.quickShareAction != null) {
            mScreenshotHandler.post(() -> {
                if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
                    mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            super.onAnimationEnd(animation);
                            mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
                        }
                    });
                } else {
                    mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
                }
            });
        }
    }

    /**
     * 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.
+18 −12
Original line number Diff line number Diff line
@@ -60,11 +60,14 @@ public class ScreenshotSmartActions {
    CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
            String screenshotId, Uri screenshotUri, Bitmap image,
            ScreenshotNotificationSmartActionsProvider smartActionsProvider,
            ScreenshotSmartActionType actionType,
            boolean smartActionsEnabled, UserHandle userHandle) {
        if (DEBUG_ACTIONS) {
            Log.d(TAG, String.format("getSmartActionsFuture id=%s, uri=%s, provider=%s, "
                            + "smartActionsEnabled=%b, userHandle=%s", screenshotId, screenshotUri,
                    smartActionsProvider.getClass(), smartActionsEnabled, userHandle));
            Log.d(TAG, String.format(
                    "getSmartActionsFuture id=%s, uri=%s, provider=%s, actionType=%s, "
                            + "smartActionsEnabled=%b, userHandle=%s",
                    screenshotId, screenshotUri, smartActionsProvider.getClass(), actionType,
                    smartActionsEnabled, userHandle));
        }
        if (!smartActionsEnabled) {
            if (DEBUG_ACTIONS) {
@@ -89,7 +92,7 @@ public class ScreenshotSmartActions {
                            ? runningTask.topActivity
                            : new ComponentName("", "");
            smartActionsFuture = smartActionsProvider.getActions(screenshotId, screenshotUri, image,
                    componentName, ScreenshotSmartActionType.REGULAR_SMART_ACTIONS, userHandle);
                    componentName, actionType, userHandle);
        } catch (Throwable e) {
            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
            smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
@@ -107,19 +110,21 @@ public class ScreenshotSmartActions {
    @VisibleForTesting
    List<Notification.Action> getSmartActions(String screenshotId,
            CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
            ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
            ScreenshotNotificationSmartActionsProvider smartActionsProvider,
            ScreenshotSmartActionType actionType) {
        long startTimeMs = SystemClock.uptimeMillis();
        if (DEBUG_ACTIONS) {
            Log.d(TAG, String.format("getSmartActions id=%s, timeoutMs=%d, provider=%s",
                    screenshotId, timeoutMs, smartActionsProvider.getClass()));
            Log.d(TAG,
                    String.format("getSmartActions id=%s, timeoutMs=%d, actionType=%s, provider=%s",
                            screenshotId, timeoutMs, actionType, smartActionsProvider.getClass()));
        }
        try {
            List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
                    TimeUnit.MILLISECONDS);
            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
            if (DEBUG_ACTIONS) {
                Log.d(TAG, String.format("Got %d smart actions. Wait time: %d ms",
                        actions.size(), waitTimeMs));
                Log.d(TAG, String.format("Got %d smart actions. Wait time: %d ms, actionType=%s",
                        actions.size(), waitTimeMs, actionType));
            }
            notifyScreenshotOp(screenshotId, smartActionsProvider,
                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
@@ -129,8 +134,9 @@ public class ScreenshotSmartActions {
        } catch (Throwable e) {
            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
            if (DEBUG_ACTIONS) {
                Log.e(TAG, String.format("Error getting smart actions. Wait time: %d ms",
                        waitTimeMs), e);
                Log.e(TAG, String.format(
                        "Error getting smart actions. Wait time: %d ms, actionType=%s",
                        waitTimeMs, actionType), e);
            }
            ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
                    (e instanceof TimeoutException)
@@ -165,7 +171,7 @@ public class ScreenshotSmartActions {
                    SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
                            context, THREAD_POOL_EXECUTOR, new Handler());
            if (DEBUG_ACTIONS) {
                Log.e(TAG, String.format("%s notifyAction: %s id=%s, isSmartAction=%b",
                Log.d(TAG, String.format("%s notifyAction: %s id=%s, isSmartAction=%b",
                        provider.getClass(), action, screenshotId, isSmartAction));
            }
            provider.notifyAction(screenshotId, action, isSmartAction);
+42 −1
Original line number Diff line number Diff line
@@ -137,6 +137,7 @@ public class ScreenshotView extends FrameLayout implements
    private ScreenshotActionChip mShareChip;
    private ScreenshotActionChip mEditChip;
    private ScreenshotActionChip mScrollChip;
    private ScreenshotActionChip mQuickShareChip;

    private UiEventLogger mUiEventLogger;
    private ScreenshotViewCallback mCallbacks;
@@ -151,7 +152,8 @@ public class ScreenshotView extends FrameLayout implements
    private enum PendingInteraction {
        PREVIEW,
        EDIT,
        SHARE
        SHARE,
        QUICK_SHARE
    }

    public ScreenshotView(Context context) {
@@ -505,6 +507,9 @@ public class ScreenshotView extends FrameLayout implements
        mShareChip.setOnClickListener(v -> {
            mShareChip.setIsPending(true);
            mEditChip.setIsPending(false);
            if (mQuickShareChip != null) {
                mQuickShareChip.setIsPending(false);
            }
            mPendingInteraction = PendingInteraction.SHARE;
        });
        chips.add(mShareChip);
@@ -514,6 +519,9 @@ public class ScreenshotView extends FrameLayout implements
        mEditChip.setOnClickListener(v -> {
            mEditChip.setIsPending(true);
            mShareChip.setIsPending(false);
            if (mQuickShareChip != null) {
                mQuickShareChip.setIsPending(false);
            }
            mPendingInteraction = PendingInteraction.EDIT;
        });
        chips.add(mEditChip);
@@ -521,6 +529,9 @@ public class ScreenshotView extends FrameLayout implements
        mScreenshotPreview.setOnClickListener(v -> {
            mShareChip.setIsPending(false);
            mEditChip.setIsPending(false);
            if (mQuickShareChip != null) {
                mQuickShareChip.setIsPending(false);
            }
            mPendingInteraction = PendingInteraction.PREVIEW;
        });

@@ -582,6 +593,13 @@ public class ScreenshotView extends FrameLayout implements
            startSharedTransition(
                    imageData.editTransition.get());
        });
        if (mQuickShareChip != null) {
            mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
                    () -> {
                        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED);
                        animateDismissal();
                    });
        }

        if (mPendingInteraction != null) {
            switch (mPendingInteraction) {
@@ -594,6 +612,9 @@ public class ScreenshotView extends FrameLayout implements
                case EDIT:
                    mEditChip.callOnClick();
                    break;
                case QUICK_SHARE:
                    mQuickShareChip.callOnClick();
                    break;
            }
        } else {
            LayoutInflater inflater = LayoutInflater.from(mContext);
@@ -615,6 +636,25 @@ public class ScreenshotView extends FrameLayout implements
        }
    }

    void addQuickShareChip(Notification.Action quickShareAction) {
        if (mPendingInteraction == null) {
            LayoutInflater inflater = LayoutInflater.from(mContext);
            mQuickShareChip = (ScreenshotActionChip) inflater.inflate(
                    R.layout.global_screenshot_action_chip, mActionsView, false);
            mQuickShareChip.setText(quickShareAction.title);
            mQuickShareChip.setIcon(quickShareAction.getIcon(), false);
            mQuickShareChip.setOnClickListener(v -> {
                mShareChip.setIsPending(false);
                mEditChip.setIsPending(false);
                mQuickShareChip.setIsPending(true);
                mPendingInteraction = PendingInteraction.QUICK_SHARE;
            });
            mQuickShareChip.setAlpha(1);
            mActionsView.addView(mQuickShareChip);
            mSmartChips.add(mQuickShareChip);
        }
    }

    boolean isDismissing() {
        return (mDismissAnimation != null && mDismissAnimation.isRunning());
    }
@@ -700,6 +740,7 @@ public class ScreenshotView extends FrameLayout implements
            mActionsView.removeView(chip);
        }
        mSmartChips.clear();
        mQuickShareChip = null;
        setAlpha(1);
        mDismissButton.setTranslationY(0);
        mActionsContainer.setTranslationY(0);
Loading