Skip to content

Commit

Permalink
[DO NOT MERGE] Update quickshare intent rather than recreating
Browse files Browse the repository at this point in the history
Currently, we extract the quickshare intent and re-wrap it as a new
PendingIntent once we get the screenshot URI. This is insecure as
it leads to executing the original with SysUI's permissions, which
the app may not have. This change switches to using Intent.fillin
to add the URI, keeping the original PendingIntent and original
permission set.

Bug: 278720336
Test: manual (to test successful quickshare), atest
SaveImageInBackgroundTaskTest (to verify original pending intent
unchanged)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:02938e8ccae910d96578475a19dff0a5e746b03d)
Merged-In: Icad3d5f939fcfb894e2038948954bc2735dbe326
Change-Id: Icad3d5f939fcfb894e2038948954bc2735dbe326
  • Loading branch information
Miranda Kephart authored and thestinger committed Sep 6, 2023
1 parent a80971a commit 7e173b4
Show file tree
Hide file tree
Showing 4 changed files with 353 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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;
import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION;

import android.app.ActivityTaskManager;
import android.app.Notification;
Expand Down Expand Up @@ -141,7 +142,12 @@ protected Void doInBackground(Void... paramsUnused) {
// 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);
Notification.Action quickShare =
queryQuickShareAction(mScreenshotId, image, user, null);
if (quickShare != null) {
mQuickShareData.quickShareAction = quickShare;
mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
}
}

// Call synchronously here since already on a background thread.
Expand Down Expand Up @@ -180,8 +186,8 @@ protected Void doInBackground(Void... paramsUnused) {
smartActionsEnabled);
mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
smartActionsEnabled);
mImageData.quickShareAction = createQuickShareAction(mContext,
mQuickShareData.quickShareAction, uri);
mImageData.quickShareAction = createQuickShareAction(
mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image, user);
mImageData.subject = getSubjectString();

mParams.mActionsReadyListener.onActionsReady(mImageData);
Expand Down Expand Up @@ -423,75 +429,86 @@ private static void addIntentExtras(String screenshotId, Intent intent, String a
}

/**
* 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;
}
quickShare = quickShareWithUri;
}
// 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));

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);
addIntentExtras(screenshotId, wrappedIntent, actionType, true);
PendingIntent broadcastIntent =
PendingIntent.getBroadcast(mContext, mRandom.nextInt(), wrappedIntent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
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);
String subjectDate = DateFormat.getDateTimeInstance().format(new Date(imageTime));
String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
fillIn.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"}),
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 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);
// 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,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
return new Notification.Action.Builder(action.getIcon(), action.title,
broadcastIntent).setContextual(true).addExtras(extras).build();
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,
ScreenshotSmartActionType.QUICK_SHARE_ACTION,
true /* smartActionsEnabled */, user);
screenshotId, uri, image, mSmartActionsProvider, QUICK_SHARE_ACTION,
true, 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,
ScreenshotSmartActionType.QUICK_SHARE_ACTION);
screenshotId, quickShareActionsFuture, timeoutMs,
mSmartActionsProvider, QUICK_SHARE_ACTION);
if (!quickShareActions.isEmpty()) {
mQuickShareData.quickShareAction = quickShareActions.get(0);
mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
return quickShareActions.get(0);
}
return null;
}

private String getSubjectString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ interface TransitionDestination {
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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

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;

Expand Down Expand Up @@ -46,15 +47,17 @@ 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());
}
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);
}
Expand Down

0 comments on commit 7e173b4

Please sign in to comment.