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

Commit 55caa60c authored by Miranda Kephart's avatar Miranda Kephart Committed by Automerger Merge Worker
Browse files

Merge "Update quickshare intent rather than recreating" into udc-dev am: ef79a82f

parents 273df67e ef79a82f
Loading
Loading
Loading
Loading
+66 −47
Original line number Diff line number Diff line
@@ -137,7 +137,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
                // 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, mParams.owner);
                Notification.Action quickShare =
                        queryQuickShareAction(mScreenshotId, image, mParams.owner, null);
                if (quickShare != null) {
                    mQuickShareData.quickShareAction = quickShare;
                    mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
                }
            }

            // Call synchronously here since already on a background thread.
@@ -176,9 +181,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
                    smartActionsEnabled);
            mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
                    smartActionsEnabled);
            mImageData.quickShareAction = createQuickShareAction(mContext,
                    mQuickShareData.quickShareAction, uri);
            mImageData.subject = getSubjectString();
            mImageData.quickShareAction = createQuickShareAction(
                    mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
                    mParams.owner);
            mImageData.subject = getSubjectString(mImageTime);

            mParams.mActionsReadyListener.onActionsReady(mImageData);
            if (DEBUG_CALLBACK) {
@@ -251,7 +257,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
                    new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
                    new ClipData.Item(uri));
            sharingIntent.setClipData(clipdata);
            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString());
            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(mImageTime));
            sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                    .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

@@ -417,60 +423,73 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
    }

    /**
     * Populate image uri into intent of Quick Share action.
     * Wrap the quickshare intent and populate the fillin intent with the URI
     */
    @VisibleForTesting
    private Notification.Action createQuickShareAction(Context context, Notification.Action action,
            Uri uri) {
        if (action == null) {
    Notification.Action createQuickShareAction(
            Notification.Action quickShare, String screenshotId, Uri uri, long imageTime,
            Bitmap image, UserHandle user) {
        if (quickShare == null) {
            return null;
        } else if (quickShare.actionIntent.isImmutable()) {
            Notification.Action quickShareWithUri =
                    queryQuickShareAction(screenshotId, image, user, uri);
            if (quickShareWithUri == null
                    || !quickShareWithUri.title.toString().contentEquals(quickShare.title)) {
                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);
            quickShare = quickShareWithUri;
        }

        // Proxy smart actions through {@link SmartActionsReceiver} for logging smart actions.
        Bundle extras = action.getExtras();
        Intent wrappedIntent = new Intent(mContext, SmartActionsReceiver.class)
                .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
                .putExtra(ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
                        createFillInIntent(uri, imageTime))
                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        Bundle extras = quickShare.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);
        // We only query for quick share actions when smart actions are enabled, so we can assert
        // that it's true here.
        addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
        PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
                mRandom.nextInt(),
                intent,
        addIntentExtras(screenshotId, wrappedIntent, actionType, true /* smartActionsEnabled */);
        PendingIntent broadcastIntent =
                PendingIntent.getBroadcast(mContext, mRandom.nextInt(), wrappedIntent,
                        PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        return new Notification.Action.Builder(action.getIcon(), action.title,
                broadcastIntent).setContextual(true).addExtras(extras).build();
        return new Notification.Action.Builder(quickShare.getIcon(), quickShare.title,
                broadcastIntent)
                .setContextual(true)
                .addExtras(extras)
                .build();
    }

    private Intent createFillInIntent(Uri uri, long imageTime) {
        Intent fillIn = new Intent();
        fillIn.setType("image/png");
        fillIn.putExtra(Intent.EXTRA_STREAM, uri);
        fillIn.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(imageTime));
        // 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));
        fillIn.setClipData(clipData);
        fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        return fillIn;
    }

    /**
     * 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)}
     * #createQuickShareAction(Notification.Action, String, Uri, long, Bitmap, UserHandle)}
     */
    private void queryQuickShareAction(Bitmap image, UserHandle user) {

    @VisibleForTesting
    Notification.Action queryQuickShareAction(
            String screenshotId, Bitmap image, UserHandle user, Uri uri) {
        CompletableFuture<List<Notification.Action>> quickShareActionsFuture =
                mScreenshotSmartActions.getSmartActionsFuture(
                        mScreenshotId, null, image, mSmartActionsProvider,
                        screenshotId, uri, image, mSmartActionsProvider,
                        ScreenshotSmartActionType.QUICK_SHARE_ACTION,
                        true /* smartActionsEnabled */, user);
        int timeoutMs = DeviceConfig.getInt(
@@ -479,17 +498,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
                500);
        List<Notification.Action> quickShareActions =
                mScreenshotSmartActions.getSmartActions(
                        mScreenshotId, quickShareActionsFuture, timeoutMs,
                        screenshotId, quickShareActionsFuture, timeoutMs,
                        mSmartActionsProvider,
                        ScreenshotSmartActionType.QUICK_SHARE_ACTION);
        if (!quickShareActions.isEmpty()) {
            mQuickShareData.quickShareAction = quickShareActions.get(0);
            mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
            return quickShareActions.get(0);
        }
        return null;
    }

    private String getSubjectString() {
        String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
    private static String getSubjectString(long imageTime) {
        String subjectDate = DateFormat.getDateTimeInstance().format(new Date(imageTime));
        return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -246,6 +246,7 @@ public class ScreenshotController {
    static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
    static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition";
    static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
    static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin";

    static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
    static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
+1 −4
Original line number Diff line number Diff line
@@ -30,7 +30,6 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.shared.system.ActivityManagerWrapper;

@@ -61,7 +60,6 @@ public class ScreenshotSmartActions {
                screenshotNotificationSmartActionsProviderProvider;
    }

    @VisibleForTesting
    CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
            String screenshotId, Uri screenshotUri, Bitmap image,
            ScreenshotNotificationSmartActionsProvider smartActionsProvider,
@@ -112,7 +110,6 @@ public class ScreenshotSmartActions {
        return smartActionsFuture;
    }

    @VisibleForTesting
    List<Notification.Action> getSmartActions(String screenshotId,
            CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
            ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+5 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.screenshot;

import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT_FILLIN;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;

@@ -46,7 +47,9 @@ public class SmartActionsReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
        PendingIntent pendingIntent =
                intent.getParcelableExtra(EXTRA_ACTION_INTENT, PendingIntent.class);
        Intent fillIn = intent.getParcelableExtra(EXTRA_ACTION_INTENT_FILLIN, Intent.class);
        String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
        if (DEBUG_ACTIONS) {
            Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
@@ -54,7 +57,7 @@ public class SmartActionsReceiver extends BroadcastReceiver {
        ActivityOptions opts = ActivityOptions.makeBasic();

        try {
            pendingIntent.send(context, 0, null, null, null, null, opts.toBundle());
            pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle());
        } catch (PendingIntent.CanceledException e) {
            Log.e(TAG, "Pending intent canceled", e);
        }
+283 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.screenshot

import android.app.Notification
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Icon
import android.net.Uri
import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.screenshot.ScreenshotController.SaveImageInBackgroundData
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import java.util.concurrent.CompletableFuture
import java.util.function.Supplier
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test

@SmallTest
class SaveImageInBackgroundTaskTest : SysuiTestCase() {
    private val imageExporter = mock<ImageExporter>()
    private val smartActions = mock<ScreenshotSmartActions>()
    private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>()
    private val saveImageData = SaveImageInBackgroundData()
    private val sharedTransitionSupplier =
        mock<Supplier<ScreenshotController.SavedImageData.ActionTransition>>()
    private val testScreenshotId: String = "testScreenshotId"
    private val testBitmap = mock<Bitmap>()
    private val testUser = UserHandle.getUserHandleForUid(0)
    private val testIcon = mock<Icon>()
    private val testImageTime = 1234.toLong()
    private val flags = FakeFeatureFlags()

    private val smartActionsUriFuture = mock<CompletableFuture<List<Notification.Action>>>()
    private val smartActionsFuture = mock<CompletableFuture<List<Notification.Action>>>()

    private val testUri: Uri = Uri.parse("testUri")
    private val intent =
        Intent(Intent.ACTION_SEND)
            .setComponent(
                ComponentName.unflattenFromString(
                    "com.google.android.test/com.google.android.test.TestActivity"
                )
            )
    private val immutablePendingIntent =
        PendingIntent.getBroadcast(
            mContext,
            0,
            intent,
            PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
    private val mutablePendingIntent =
        PendingIntent.getBroadcast(
            mContext,
            0,
            intent,
            PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
        )

    private val saveImageTask =
        SaveImageInBackgroundTask(
            mContext,
            flags,
            imageExporter,
            smartActions,
            saveImageData,
            sharedTransitionSupplier,
            smartActionsProvider,
        )

    @Before
    fun setup() {
        whenever(
                smartActions.getSmartActionsFuture(
                    eq(testScreenshotId),
                    any(Uri::class.java),
                    eq(testBitmap),
                    eq(smartActionsProvider),
                    any(ScreenshotSmartActionType::class.java),
                    any(Boolean::class.java),
                    eq(testUser)
                )
            )
            .thenReturn(smartActionsUriFuture)
        whenever(
                smartActions.getSmartActionsFuture(
                    eq(testScreenshotId),
                    eq(null),
                    eq(testBitmap),
                    eq(smartActionsProvider),
                    any(ScreenshotSmartActionType::class.java),
                    any(Boolean::class.java),
                    eq(testUser)
                )
            )
            .thenReturn(smartActionsFuture)
    }

    @Test
    fun testQueryQuickShare_noAction() {
        whenever(
                smartActions.getSmartActions(
                    eq(testScreenshotId),
                    eq(smartActionsFuture),
                    any(Int::class.java),
                    eq(smartActionsProvider),
                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
                )
            )
            .thenReturn(ArrayList<Notification.Action>())

        val quickShareAction =
            saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)

        assertNull(quickShareAction)
    }

    @Test
    fun testQueryQuickShare_withActions() {
        val actions = ArrayList<Notification.Action>()
        actions.add(constructAction("Action One", mutablePendingIntent))
        actions.add(constructAction("Action Two", mutablePendingIntent))
        whenever(
                smartActions.getSmartActions(
                    eq(testScreenshotId),
                    eq(smartActionsUriFuture),
                    any(Int::class.java),
                    eq(smartActionsProvider),
                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
                )
            )
            .thenReturn(actions)

        val quickShareAction =
            saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)!!

        assertEquals("Action One", quickShareAction.title)
        assertEquals(mutablePendingIntent, quickShareAction.actionIntent)
    }

    @Test
    fun testCreateQuickShareAction_originalWasNull_returnsNull() {
        val quickShareAction =
            saveImageTask.createQuickShareAction(
                null,
                testScreenshotId,
                testUri,
                testImageTime,
                testBitmap,
                testUser
            )

        assertNull(quickShareAction)
    }

    @Test
    fun testCreateQuickShareAction_immutableIntentDifferentAction_returnsNull() {
        val actions = ArrayList<Notification.Action>()
        actions.add(constructAction("New Test Action", immutablePendingIntent))
        whenever(
                smartActions.getSmartActions(
                    eq(testScreenshotId),
                    eq(smartActionsUriFuture),
                    any(Int::class.java),
                    eq(smartActionsProvider),
                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
                )
            )
            .thenReturn(actions)
        val origAction = constructAction("Old Test Action", immutablePendingIntent)

        val quickShareAction =
            saveImageTask.createQuickShareAction(
                origAction,
                testScreenshotId,
                testUri,
                testImageTime,
                testBitmap,
                testUser,
            )

        assertNull(quickShareAction)
    }

    @Test
    fun testCreateQuickShareAction_mutableIntent_returnsSafeIntent() {
        val actions = ArrayList<Notification.Action>()
        val action = constructAction("Action One", mutablePendingIntent)
        actions.add(action)
        whenever(
                smartActions.getSmartActions(
                    eq(testScreenshotId),
                    eq(smartActionsUriFuture),
                    any(Int::class.java),
                    eq(smartActionsProvider),
                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
                )
            )
            .thenReturn(actions)

        val quickShareAction =
            saveImageTask.createQuickShareAction(
                constructAction("Test Action", mutablePendingIntent),
                testScreenshotId,
                testUri,
                testImageTime,
                testBitmap,
                testUser
            )
        val quickSharePendingIntent =
            quickShareAction.actionIntent.intent.extras!!.getParcelable(
                ScreenshotController.EXTRA_ACTION_INTENT,
                PendingIntent::class.java
            )

        assertEquals("Test Action", quickShareAction.title)
        assertEquals(mutablePendingIntent, quickSharePendingIntent)
    }

    @Test
    fun testCreateQuickShareAction_immutableIntent_returnsSafeIntent() {
        val actions = ArrayList<Notification.Action>()
        val action = constructAction("Test Action", immutablePendingIntent)
        actions.add(action)
        whenever(
                smartActions.getSmartActions(
                    eq(testScreenshotId),
                    eq(smartActionsUriFuture),
                    any(Int::class.java),
                    eq(smartActionsProvider),
                    eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
                )
            )
            .thenReturn(actions)

        val quickShareAction =
            saveImageTask.createQuickShareAction(
                constructAction("Test Action", immutablePendingIntent),
                testScreenshotId,
                testUri,
                testImageTime,
                testBitmap,
                testUser,
            )!!

        assertEquals("Test Action", quickShareAction.title)
        assertEquals(
            immutablePendingIntent,
            quickShareAction.actionIntent.intent.extras!!.getParcelable(
                ScreenshotController.EXTRA_ACTION_INTENT,
                PendingIntent::class.java
            )
        )
    }

    private fun constructAction(title: String, intent: PendingIntent): Notification.Action {
        return Notification.Action.Builder(testIcon, title, intent).build()
    }
}