blob: 468b9b16addb0187da211b6f9e2a69881eb58292 [file] [log] [blame]
Miranda Kephartca6c5eb2019-11-14 12:44:11 -05001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.screenshot;
18
19import android.app.ActivityTaskManager;
20import android.app.Notification;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050021import android.app.PendingIntent;
22import android.content.ClipData;
23import android.content.ClipDescription;
24import android.content.ComponentName;
Jeff Sharkey04b4ba12019-12-15 22:42:42 -070025import android.content.ContentResolver;
26import android.content.ContentValues;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050027import android.content.Context;
28import android.content.Intent;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050029import android.content.res.Resources;
30import android.graphics.Bitmap;
Miranda Kephart9bbecf32019-12-06 10:54:58 -050031import android.graphics.drawable.Icon;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050032import android.media.ExifInterface;
33import android.net.Uri;
34import android.os.AsyncTask;
35import android.os.Build;
Satakshiaaf69532019-11-07 17:54:24 -080036import android.os.Bundle;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050037import android.os.Environment;
38import android.os.Handler;
39import android.os.ParcelFileDescriptor;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050040import android.os.RemoteException;
41import android.os.UserHandle;
42import android.os.UserManager;
43import android.provider.DeviceConfig;
44import android.provider.MediaStore;
Jeff Sharkey04b4ba12019-12-15 22:42:42 -070045import android.provider.MediaStore.MediaColumns;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050046import android.text.TextUtils;
Jeff Sharkey04b4ba12019-12-15 22:42:42 -070047import android.text.format.DateUtils;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050048import android.util.Slog;
49
Satakshiaaf69532019-11-07 17:54:24 -080050import com.android.internal.annotations.VisibleForTesting;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050051import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050052import com.android.systemui.R;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050053import com.android.systemui.SystemUIFactory;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050054
55import java.io.File;
56import java.io.IOException;
57import java.io.OutputStream;
58import java.text.DateFormat;
59import java.text.SimpleDateFormat;
60import java.time.Instant;
61import java.time.ZoneId;
62import java.time.ZoneOffset;
63import java.time.ZonedDateTime;
64import java.time.format.DateTimeFormatter;
Satakshiaaf69532019-11-07 17:54:24 -080065import java.util.ArrayList;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050066import java.util.Date;
67import java.util.List;
68import java.util.Objects;
Satakshiaaf69532019-11-07 17:54:24 -080069import java.util.Random;
70import java.util.UUID;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050071import java.util.concurrent.CompletableFuture;
72
73/**
74 * An AsyncTask that saves an image to the media store in the background.
75 */
76class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
77 private static final String TAG = "SaveImageInBackgroundTask";
78
79 private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
Satakshiaaf69532019-11-07 17:54:24 -080080 private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050081 private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
82
Miranda Kephart9bbecf32019-12-06 10:54:58 -050083 private final Context mContext;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050084 private final GlobalScreenshot.SaveImageInBackgroundData mParams;
Miranda Kephart7b2c3132020-03-27 09:54:14 -040085 private final GlobalScreenshot.SavedImageData mImageData;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050086 private final String mImageFileName;
87 private final long mImageTime;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050088 private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
Satakshiaaf69532019-11-07 17:54:24 -080089 private final String mScreenshotId;
90 private final boolean mSmartActionsEnabled;
91 private final Random mRandom = new Random();
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050092
Miranda Kephart9bbecf32019-12-06 10:54:58 -050093 SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
94 mContext = context;
Miranda Kephart7b2c3132020-03-27 09:54:14 -040095 mImageData = new GlobalScreenshot.SavedImageData();
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050096
97 // Prepare all the output metadata
98 mParams = data;
99 mImageTime = System.currentTimeMillis();
100 String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
101 mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
Satakshiaaf69532019-11-07 17:54:24 -0800102 mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID());
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500103
104 // Initialize screenshot notification smart actions provider.
Satakshiaaf69532019-11-07 17:54:24 -0800105 mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
Nikita Dubrovsky19b70d32020-01-29 15:53:15 -0800106 SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true);
Satakshiaaf69532019-11-07 17:54:24 -0800107 if (mSmartActionsEnabled) {
108 mSmartActionsProvider =
109 SystemUIFactory.getInstance()
110 .createScreenshotNotificationSmartActionsProvider(
111 context, THREAD_POOL_EXECUTOR, new Handler());
112 } else {
113 // If smart actions is not enabled use empty implementation.
114 mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
115 }
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500116 }
117
118 @Override
119 protected Void doInBackground(Void... paramsUnused) {
120 if (isCancelled()) {
121 return null;
122 }
Miranda Kephart40c99512020-06-04 11:27:35 -0400123 Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500124
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500125 ContentResolver resolver = mContext.getContentResolver();
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500126 Bitmap image = mParams.image;
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500127 Resources r = mContext.getResources();
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500128
129 try {
130 // Save the screenshot to the MediaStore
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700131 final ContentValues values = new ContentValues();
132 values.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
133 + File.separator + Environment.DIRECTORY_SCREENSHOTS);
134 values.put(MediaColumns.DISPLAY_NAME, mImageFileName);
135 values.put(MediaColumns.MIME_TYPE, "image/png");
136 values.put(MediaColumns.DATE_ADDED, mImageTime / 1000);
137 values.put(MediaColumns.DATE_MODIFIED, mImageTime / 1000);
138 values.put(MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
139 values.put(MediaColumns.IS_PENDING, 1);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500140
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700141 final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400142
Katsiaryna Naliuka2f8ae172020-04-22 14:14:27 +0200143 CompletableFuture<List<Notification.Action>> smartActionsFuture =
144 ScreenshotSmartActions.getSmartActionsFuture(
145 mScreenshotId, uri, image, mSmartActionsProvider,
Katsiaryna Naliuka104380d2020-05-14 14:01:29 +0200146 mSmartActionsEnabled, getUserHandle(mContext));
Katsiaryna Naliuka2f8ae172020-04-22 14:14:27 +0200147
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500148 try {
149 // First, write the actual data for our screenshot
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700150 try (OutputStream out = resolver.openOutputStream(uri)) {
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500151 if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
152 throw new IOException("Failed to compress");
153 }
154 }
155
156 // Next, write metadata to help index the screenshot
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700157 try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) {
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500158 final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
159
160 exif.setAttribute(ExifInterface.TAG_SOFTWARE,
161 "Android " + Build.DISPLAY);
162
163 exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH,
164 Integer.toString(image.getWidth()));
165 exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH,
166 Integer.toString(image.getHeight()));
167
168 final ZonedDateTime time = ZonedDateTime.ofInstant(
169 Instant.ofEpochMilli(mImageTime), ZoneId.systemDefault());
170 exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL,
171 DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(time));
172 exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
173 DateTimeFormatter.ofPattern("SSS").format(time));
174
175 if (Objects.equals(time.getOffset(), ZoneOffset.UTC)) {
176 exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00");
177 } else {
178 exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
179 DateTimeFormatter.ofPattern("XXX").format(time));
180 }
181
182 exif.saveAttributes();
183 }
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700184
185 // Everything went well above, publish it!
186 values.clear();
187 values.put(MediaColumns.IS_PENDING, 0);
188 values.putNull(MediaColumns.DATE_EXPIRES);
189 resolver.update(uri, values, null, null);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500190 } catch (Exception e) {
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700191 resolver.delete(uri, null);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500192 throw e;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500193 }
194
Miranda Kephart43c02962020-01-10 17:50:48 -0500195 List<Notification.Action> smartActions = new ArrayList<>();
196 if (mSmartActionsEnabled) {
197 int timeoutMs = DeviceConfig.getInt(
198 DeviceConfig.NAMESPACE_SYSTEMUI,
199 SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
200 1000);
201 smartActions.addAll(buildSmartActions(
202 ScreenshotSmartActions.getSmartActions(
Katsiaryna Naliuka2f8ae172020-04-22 14:14:27 +0200203 mScreenshotId, smartActionsFuture, timeoutMs,
Miranda Kephart43c02962020-01-10 17:50:48 -0500204 mSmartActionsProvider),
205 mContext));
206 }
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400207
208 mImageData.uri = uri;
209 mImageData.smartActions = smartActions;
210 mImageData.shareAction = createShareAction(mContext, mContext.getResources(), uri);
211 mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri);
212 mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
213
214 mParams.mActionsReadyListener.onActionsReady(mImageData);
Miranda Kephart3bf1ea32020-05-21 15:27:49 -0400215 mParams.finisher.accept(mImageData.uri);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500216 mParams.image = null;
217 mParams.errorMsgResId = 0;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500218 } catch (Exception e) {
219 // IOException/UnsupportedOperationException may be thrown if external storage is
220 // not mounted
221 Slog.e(TAG, "unable to save screenshot", e);
222 mParams.clearImage();
223 mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400224 mImageData.reset();
225 mParams.mActionsReadyListener.onActionsReady(mImageData);
Miranda Kephart3bf1ea32020-05-21 15:27:49 -0400226 mParams.finisher.accept(null);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500227 }
228
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500229 return null;
230 }
231
Miranda Kepharta4c79752020-04-24 15:23:58 -0400232 /**
Miranda Kephart3bf1ea32020-05-21 15:27:49 -0400233 * Update the listener run when the saving task completes. Used to avoid showing UI for the
234 * first screenshot when a second one is taken.
Miranda Kepharta4c79752020-04-24 15:23:58 -0400235 */
Miranda Kephart3bf1ea32020-05-21 15:27:49 -0400236 void setActionsReadyListener(GlobalScreenshot.ActionsReadyListener listener) {
237 mParams.mActionsReadyListener = listener;
Miranda Kepharta4c79752020-04-24 15:23:58 -0400238 }
239
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500240 @Override
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500241 protected void onCancelled(Void params) {
242 // If we are cancelled while the task is running in the background, we may get null
243 // params. The finisher is expected to always be called back, so just use the baked-in
244 // params from the ctor in any case.
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400245 mImageData.reset();
246 mParams.mActionsReadyListener.onActionsReady(mImageData);
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500247 mParams.finisher.accept(null);
248 mParams.clearImage();
249 }
250
Satakshiaaf69532019-11-07 17:54:24 -0800251 @VisibleForTesting
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400252 Notification.Action createShareAction(Context context, Resources r, Uri uri) {
Satakshiaaf69532019-11-07 17:54:24 -0800253 // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
254 // order to do some common work like dismissing the keyguard and sending
255 // closeSystemWindows
256
257 // Create a share intent, this will always go through the chooser activity first
258 // which should not trigger auto-enter PiP
259 String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
260 String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
261 Intent sharingIntent = new Intent(Intent.ACTION_SEND);
262 sharingIntent.setType("image/png");
263 sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
264 // Include URI in ClipData also, so that grantPermission picks it up.
265 // We don't use setData here because some apps interpret this as "to:".
266 ClipData clipdata = new ClipData(new ClipDescription("content",
267 new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
268 new ClipData.Item(uri));
269 sharingIntent.setClipData(clipdata);
270 sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
271 sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
272
273 // Make sure pending intents for the system user are still unique across users
274 // by setting the (otherwise unused) request code to the current user id.
275 int requestCode = context.getUserId();
276
277 PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
278 new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
279 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500280 Intent sharingChooserIntent =
281 Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender())
282 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
283 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Miranda Kephartd3d0aee2020-06-24 11:50:07 -0400284
285 // cancel current pending intent (if any) since clipData isn't used for matching
286 PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
287 sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
Satakshiaaf69532019-11-07 17:54:24 -0800288
289 // Create a share action for the notification
290 PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
291 new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
Matt Caseyea128192020-06-19 15:35:20 -0400292 .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent)
Satakshiaaf69532019-11-07 17:54:24 -0800293 .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true)
294 .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
295 .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
296 mSmartActionsEnabled)
Miranda Kephartffb18972020-02-12 19:39:40 -0500297 .setAction(Intent.ACTION_SEND)
298 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
Satakshiaaf69532019-11-07 17:54:24 -0800299 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500300
Satakshiaaf69532019-11-07 17:54:24 -0800301 Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500302 Icon.createWithResource(r, R.drawable.ic_screenshot_share),
Satakshiaaf69532019-11-07 17:54:24 -0800303 r.getString(com.android.internal.R.string.share), shareAction);
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400304
305 return shareActionBuilder.build();
306 }
307
308 @VisibleForTesting
309 Notification.Action createEditAction(Context context, Resources r, Uri uri) {
310 // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
311 // order to do some common work like dismissing the keyguard and sending
312 // closeSystemWindows
Satakshiaaf69532019-11-07 17:54:24 -0800313
314 // Create an edit intent, if a specific package is provided as the editor, then
315 // launch that directly
316 String editorPackage = context.getString(R.string.config_screenshotEditor);
317 Intent editIntent = new Intent(Intent.ACTION_EDIT);
318 if (!TextUtils.isEmpty(editorPackage)) {
319 editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
320 }
321 editIntent.setType("image/png");
322 editIntent.setData(uri);
323 editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
324 editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Matt Casey559ac1c2020-04-25 14:02:14 -0400325 editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
Satakshiaaf69532019-11-07 17:54:24 -0800326
Matt Caseyea128192020-06-19 15:35:20 -0400327 PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
328 editIntent, 0, null, UserHandle.CURRENT);
329
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400330 // Make sure pending intents for the system user are still unique across users
331 // by setting the (otherwise unused) request code to the current user id.
332 int requestCode = mContext.getUserId();
333
Satakshiaaf69532019-11-07 17:54:24 -0800334 // Create a edit action
Matt Caseyea128192020-06-19 15:35:20 -0400335 PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
Satakshiaaf69532019-11-07 17:54:24 -0800336 new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
Matt Caseyea128192020-06-19 15:35:20 -0400337 .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent)
Satakshiaaf69532019-11-07 17:54:24 -0800338 .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION,
339 editIntent.getComponent() != null)
340 .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
341 .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
342 mSmartActionsEnabled)
Miranda Kephartffb18972020-02-12 19:39:40 -0500343 .setAction(Intent.ACTION_EDIT)
344 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
Matt Caseyea128192020-06-19 15:35:20 -0400345 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Satakshiaaf69532019-11-07 17:54:24 -0800346 Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500347 Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
Satakshiaaf69532019-11-07 17:54:24 -0800348 r.getString(com.android.internal.R.string.screenshot_edit), editAction);
Satakshiaaf69532019-11-07 17:54:24 -0800349
Miranda Kephart7b2c3132020-03-27 09:54:14 -0400350 return editActionBuilder.build();
351 }
352
353 @VisibleForTesting
354 Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
355 // Make sure pending intents for the system user are still unique across users
356 // by setting the (otherwise unused) request code to the current user id.
357 int requestCode = mContext.getUserId();
358
359 // Create a delete action for the notification
360 PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
361 new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
362 .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
363 .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
364 .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
365 mSmartActionsEnabled)
366 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
367 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
368 Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
369 Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
370 r.getString(com.android.internal.R.string.delete), deleteAction);
371
372 return deleteActionBuilder.build();
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500373 }
374
375 private int getUserHandleOfForegroundApplication(Context context) {
376 // This logic matches
377 // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
378 try {
379 return ActivityTaskManager.getService().getLastResumedActivityUserId();
380 } catch (RemoteException e) {
381 Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e);
382 return context.getUserId();
Satakshiaaf69532019-11-07 17:54:24 -0800383 }
384 }
385
Katsiaryna Naliuka104380d2020-05-14 14:01:29 +0200386 private UserHandle getUserHandle(Context context) {
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500387 UserManager manager = UserManager.get(context);
Katsiaryna Naliuka104380d2020-05-14 14:01:29 +0200388 return manager.getUserInfo(getUserHandleOfForegroundApplication(context)).getUserHandle();
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500389 }
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500390
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500391 private List<Notification.Action> buildSmartActions(
392 List<Notification.Action> actions, Context context) {
393 List<Notification.Action> broadcastActions = new ArrayList<>();
394 for (Notification.Action action : actions) {
395 // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
396 // for logging smart actions.
397 Bundle extras = action.getExtras();
398 String actionType = extras.getString(
399 ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
400 ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
Miranda Kephartffb18972020-02-12 19:39:40 -0500401 Intent intent = new Intent(context, GlobalScreenshot.SmartActionsReceiver.class)
402 .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent)
403 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500404 addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
405 PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
406 mRandom.nextInt(),
407 intent,
408 PendingIntent.FLAG_CANCEL_CURRENT);
409 broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
410 broadcastIntent).setContextual(true).addExtras(extras).build());
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500411 }
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500412 return broadcastActions;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500413 }
414
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500415 private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
416 boolean smartActionsEnabled) {
417 intent
418 .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
419 .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
420 .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500421 }
Miranda Kephart9bbecf32019-12-06 10:54:58 -0500422
423
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500424}