blob: d2268e12c6620cfad697a48da317b15d0847c594 [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;
21import android.app.NotificationManager;
22import android.app.PendingIntent;
23import android.content.ClipData;
24import android.content.ClipDescription;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.pm.UserInfo;
29import android.content.res.Resources;
30import android.graphics.Bitmap;
31import android.graphics.Canvas;
32import android.graphics.ColorMatrix;
33import android.graphics.ColorMatrixColorFilter;
34import android.graphics.Matrix;
35import android.graphics.Paint;
36import android.graphics.Picture;
37import android.media.ExifInterface;
38import android.net.Uri;
39import android.os.AsyncTask;
40import android.os.Build;
Satakshiaaf69532019-11-07 17:54:24 -080041import android.os.Bundle;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050042import android.os.Environment;
43import android.os.Handler;
44import android.os.ParcelFileDescriptor;
45import android.os.Process;
46import android.os.RemoteException;
47import android.os.UserHandle;
48import android.os.UserManager;
49import android.provider.DeviceConfig;
50import android.provider.MediaStore;
51import android.text.TextUtils;
52import android.util.Slog;
53
Satakshiaaf69532019-11-07 17:54:24 -080054import com.android.internal.annotations.VisibleForTesting;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050055import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
56import com.android.internal.messages.nano.SystemMessageProto;
57import com.android.systemui.R;
58import com.android.systemui.SystemUI;
59import com.android.systemui.SystemUIFactory;
60import com.android.systemui.util.NotificationChannels;
61
62import libcore.io.IoUtils;
63
64import java.io.File;
65import java.io.IOException;
66import java.io.OutputStream;
67import java.text.DateFormat;
68import java.text.SimpleDateFormat;
69import java.time.Instant;
70import java.time.ZoneId;
71import java.time.ZoneOffset;
72import java.time.ZonedDateTime;
73import java.time.format.DateTimeFormatter;
Satakshiaaf69532019-11-07 17:54:24 -080074import java.util.ArrayList;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050075import java.util.Date;
76import java.util.List;
77import java.util.Objects;
Satakshiaaf69532019-11-07 17:54:24 -080078import java.util.Random;
79import java.util.UUID;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050080import java.util.concurrent.CompletableFuture;
81
82/**
83 * An AsyncTask that saves an image to the media store in the background.
84 */
85class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
86 private static final String TAG = "SaveImageInBackgroundTask";
87
88 private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
Satakshiaaf69532019-11-07 17:54:24 -080089 private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
Miranda Kephartca6c5eb2019-11-14 12:44:11 -050090 private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
91
92 private final GlobalScreenshot.SaveImageInBackgroundData mParams;
93 private final NotificationManager mNotificationManager;
94 private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
95 private final String mImageFileName;
96 private final long mImageTime;
97 private final Notification.BigPictureStyle mNotificationStyle;
98 private final int mImageWidth;
99 private final int mImageHeight;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500100 private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
Satakshiaaf69532019-11-07 17:54:24 -0800101 private final String mScreenshotId;
102 private final boolean mSmartActionsEnabled;
103 private final Random mRandom = new Random();
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500104
105 SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data,
106 NotificationManager nManager) {
107 Resources r = context.getResources();
108
109 // Prepare all the output metadata
110 mParams = data;
111 mImageTime = System.currentTimeMillis();
112 String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
113 mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
Satakshiaaf69532019-11-07 17:54:24 -0800114 mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID());
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500115
116 // Initialize screenshot notification smart actions provider.
Satakshiaaf69532019-11-07 17:54:24 -0800117 mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
118 SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false);
119 if (mSmartActionsEnabled) {
120 mSmartActionsProvider =
121 SystemUIFactory.getInstance()
122 .createScreenshotNotificationSmartActionsProvider(
123 context, THREAD_POOL_EXECUTOR, new Handler());
124 } else {
125 // If smart actions is not enabled use empty implementation.
126 mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
127 }
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500128
129 // Create the large notification icon
130 mImageWidth = data.image.getWidth();
131 mImageHeight = data.image.getHeight();
132 int iconSize = data.iconSize;
133 int previewWidth = data.previewWidth;
134 int previewHeight = data.previewheight;
135
136 Paint paint = new Paint();
137 ColorMatrix desat = new ColorMatrix();
138 desat.setSaturation(0.25f);
139 paint.setColorFilter(new ColorMatrixColorFilter(desat));
140 Matrix matrix = new Matrix();
141 int overlayColor = 0x40FFFFFF;
142
143 matrix.setTranslate((previewWidth - mImageWidth) / 2,
144 (previewHeight - mImageHeight) / 2);
145 Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight,
146 matrix, paint, overlayColor);
147
148 // Note, we can't use the preview for the small icon, since it is non-square
149 float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
150 matrix.setScale(scale, scale);
151 matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
152 (iconSize - (scale * mImageHeight)) / 2);
153 Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint,
154 overlayColor);
155
156 mNotificationManager = nManager;
157 final long now = System.currentTimeMillis();
158
159 // Setup the notification
160 mNotificationStyle = new Notification.BigPictureStyle()
161 .bigPicture(picture.createAshmemBitmap());
162
163 // The public notification will show similar info but with the actual screenshot omitted
164 mPublicNotificationBuilder =
165 new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP)
166 .setContentTitle(r.getString(R.string.screenshot_saving_title))
167 .setSmallIcon(R.drawable.stat_notify_image)
168 .setCategory(Notification.CATEGORY_PROGRESS)
169 .setWhen(now)
170 .setShowWhen(true)
171 .setColor(r.getColor(
172 com.android.internal.R.color.system_notification_accent_color));
173 SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder, true);
174
175 mNotificationBuilder = new Notification.Builder(context,
176 NotificationChannels.SCREENSHOTS_HEADSUP)
177 .setContentTitle(r.getString(R.string.screenshot_saving_title))
178 .setSmallIcon(R.drawable.stat_notify_image)
179 .setWhen(now)
180 .setShowWhen(true)
181 .setColor(r.getColor(
182 com.android.internal.R.color.system_notification_accent_color))
183 .setStyle(mNotificationStyle)
184 .setPublicVersion(mPublicNotificationBuilder.build());
185 mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
186 SystemUI.overrideNotificationAppName(context, mNotificationBuilder, true);
187
188 mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
189 mNotificationBuilder.build());
190
191 /**
192 * NOTE: The following code prepares the notification builder for updating the
193 * notification after the screenshot has been written to disk.
194 */
195
196 // On the tablet, the large icon makes the notification appear as if it is clickable
197 // (and on small devices, the large icon is not shown) so defer showing the large icon
198 // until we compose the final post-save notification below.
199 mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
200 // But we still don't set it for the expanded view, allowing the smallIcon to show here.
201 mNotificationStyle.bigLargeIcon((Bitmap) null);
202 }
203
204 private int getUserHandleOfForegroundApplication(Context context) {
205 // This logic matches
206 // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
207 try {
208 return ActivityTaskManager.getService().getLastResumedActivityUserId();
209 } catch (RemoteException e) {
210 Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e);
211 return context.getUserId();
212 }
213 }
214
215 private boolean isManagedProfile(Context context) {
216 UserManager manager = UserManager.get(context);
217 UserInfo info = manager.getUserInfo(getUserHandleOfForegroundApplication(context));
218 return info.isManagedProfile();
219 }
220
Satakshiaaf69532019-11-07 17:54:24 -0800221 private List<Notification.Action> buildSmartActions(
222 List<Notification.Action> actions, Context context) {
223 List<Notification.Action> broadcastActions = new ArrayList<>();
224 for (Notification.Action action : actions) {
225 // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
226 // for logging smart actions.
227 Bundle extras = action.getExtras();
228 String actionType = extras.getString(
229 ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
230 ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
231 Intent intent = new Intent(context,
232 GlobalScreenshot.SmartActionsReceiver.class).putExtra(
233 GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
234 addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
235 PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
236 mRandom.nextInt(),
237 intent,
238 PendingIntent.FLAG_CANCEL_CURRENT);
239 broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
240 broadcastIntent).setContextual(true).addExtras(extras).build());
241 }
242 return broadcastActions;
243 }
244
245 private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
246 boolean smartActionsEnabled) {
247 intent
248 .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
249 .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
250 .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
251 }
252
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500253 /**
254 * Generates a new hardware bitmap with specified values, copying the content from the
255 * passed in bitmap.
256 */
257 private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
258 Paint paint, int color) {
259 Picture picture = new Picture();
260 Canvas canvas = picture.beginRecording(width, height);
261 canvas.drawColor(color);
262 canvas.drawBitmap(bitmap, matrix, paint);
263 picture.endRecording();
264 return Bitmap.createBitmap(picture);
265 }
266
267 @Override
268 protected Void doInBackground(Void... paramsUnused) {
269 if (isCancelled()) {
270 return null;
271 }
272
273 // By default, AsyncTask sets the worker thread to have background thread priority,
274 // so bump it back up so that we save a little quicker.
275 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
276
277 Context context = mParams.context;
278 Bitmap image = mParams.image;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500279 Resources r = context.getResources();
280
281 try {
Satakshiaaf69532019-11-07 17:54:24 -0800282 CompletableFuture<List<Notification.Action>> smartActionsFuture =
283 GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image,
284 mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
285
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500286 // Save the screenshot to the MediaStore
287 final MediaStore.PendingParams params = new MediaStore.PendingParams(
288 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
289 params.setRelativePath(Environment.DIRECTORY_PICTURES + File.separator
290 + Environment.DIRECTORY_SCREENSHOTS);
291
292 final Uri uri = MediaStore.createPending(context, params);
293 final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
294 try {
295 // First, write the actual data for our screenshot
296 try (OutputStream out = session.openOutputStream()) {
297 if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
298 throw new IOException("Failed to compress");
299 }
300 }
301
302 // Next, write metadata to help index the screenshot
303 try (ParcelFileDescriptor pfd = session.open()) {
304 final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
305
306 exif.setAttribute(ExifInterface.TAG_SOFTWARE,
307 "Android " + Build.DISPLAY);
308
309 exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH,
310 Integer.toString(image.getWidth()));
311 exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH,
312 Integer.toString(image.getHeight()));
313
314 final ZonedDateTime time = ZonedDateTime.ofInstant(
315 Instant.ofEpochMilli(mImageTime), ZoneId.systemDefault());
316 exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL,
317 DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(time));
318 exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
319 DateTimeFormatter.ofPattern("SSS").format(time));
320
321 if (Objects.equals(time.getOffset(), ZoneOffset.UTC)) {
322 exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00");
323 } else {
324 exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
325 DateTimeFormatter.ofPattern("XXX").format(time));
326 }
327
328 exif.saveAttributes();
329 }
330 session.publish();
331 } catch (Exception e) {
332 session.abandon();
333 throw e;
334 } finally {
335 IoUtils.closeQuietly(session);
336 }
337
Satakshiaaf69532019-11-07 17:54:24 -0800338 populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500339
340 mParams.imageUri = uri;
341 mParams.image = null;
342 mParams.errorMsgResId = 0;
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500343 } catch (Exception e) {
344 // IOException/UnsupportedOperationException may be thrown if external storage is
345 // not mounted
346 Slog.e(TAG, "unable to save screenshot", e);
347 mParams.clearImage();
348 mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
349 }
350
351 // Recycle the bitmap data
352 if (image != null) {
353 image.recycle();
354 }
355
356 return null;
357 }
358
Satakshiaaf69532019-11-07 17:54:24 -0800359 @VisibleForTesting
360 void populateNotificationActions(Context context, Resources r, Uri uri,
361 CompletableFuture<List<Notification.Action>> smartActionsFuture,
362 Notification.Builder notificationBuilder) {
363 // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
364 // order to do some common work like dismissing the keyguard and sending
365 // closeSystemWindows
366
367 // Create a share intent, this will always go through the chooser activity first
368 // which should not trigger auto-enter PiP
369 String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
370 String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
371 Intent sharingIntent = new Intent(Intent.ACTION_SEND);
372 sharingIntent.setType("image/png");
373 sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
374 // Include URI in ClipData also, so that grantPermission picks it up.
375 // We don't use setData here because some apps interpret this as "to:".
376 ClipData clipdata = new ClipData(new ClipDescription("content",
377 new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
378 new ClipData.Item(uri));
379 sharingIntent.setClipData(clipdata);
380 sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
381 sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
382
383 // Make sure pending intents for the system user are still unique across users
384 // by setting the (otherwise unused) request code to the current user id.
385 int requestCode = context.getUserId();
386
387 PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
388 new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
389 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
390 Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
391 chooserAction.getIntentSender())
392 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
393 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
394
395 // Create a share action for the notification
396 PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
397 new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
398 .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, sharingChooserIntent)
399 .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true)
400 .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
401 .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
402 mSmartActionsEnabled)
403 .setAction(Intent.ACTION_SEND),
404 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
405 Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
406 R.drawable.ic_screenshot_share,
407 r.getString(com.android.internal.R.string.share), shareAction);
408 notificationBuilder.addAction(shareActionBuilder.build());
409
410 // Create an edit intent, if a specific package is provided as the editor, then
411 // launch that directly
412 String editorPackage = context.getString(R.string.config_screenshotEditor);
413 Intent editIntent = new Intent(Intent.ACTION_EDIT);
414 if (!TextUtils.isEmpty(editorPackage)) {
415 editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
416 }
417 editIntent.setType("image/png");
418 editIntent.setData(uri);
419 editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
420 editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
421
422 // Create a edit action
423 PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
424 new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
425 .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, editIntent)
426 .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION,
427 editIntent.getComponent() != null)
428 .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
429 .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
430 mSmartActionsEnabled)
431 .setAction(Intent.ACTION_EDIT),
432 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
433 Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
434 R.drawable.ic_screenshot_edit,
435 r.getString(com.android.internal.R.string.screenshot_edit), editAction);
436 notificationBuilder.addAction(editActionBuilder.build());
Miranda Kephart0148b282019-10-31 15:36:21 -0400437 if (mParams.mActionsReadyListener != null) {
438 mParams.mActionsReadyListener.onActionsReady(shareAction, editAction);
Satakshiaaf69532019-11-07 17:54:24 -0800439 }
440
441 // Create a delete action for the notification
442 PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
443 new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
444 .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
445 .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
446 .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
447 mSmartActionsEnabled),
448 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
449 Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
450 R.drawable.ic_screenshot_delete,
451 r.getString(com.android.internal.R.string.delete), deleteAction);
452 notificationBuilder.addAction(deleteActionBuilder.build());
453
454 if (mSmartActionsEnabled) {
455 int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
456 SystemUiDeviceConfigFlags
457 .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
458 1000);
459 List<Notification.Action> smartActions = buildSmartActions(
460 GlobalScreenshot.getSmartActions(mScreenshotId, smartActionsFuture,
461 timeoutMs, mSmartActionsProvider), context);
462 for (Notification.Action action : smartActions) {
463 notificationBuilder.addAction(action);
464 }
465 }
466 }
467
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500468 @Override
469 protected void onPostExecute(Void params) {
470 if (mParams.errorMsgResId != 0) {
471 // Show a message that we've failed to save the image to disk
472 GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager,
473 mParams.errorMsgResId);
474 } else {
Miranda Kephart0148b282019-10-31 15:36:21 -0400475 if (mParams.mActionsReadyListener != null) {
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500476 // Cancel the "saving screenshot" notification
477 mNotificationManager.cancel(
478 SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
479 } else {
480 // Show the final notification to indicate screenshot saved
481 Context context = mParams.context;
482 Resources r = context.getResources();
483
484 // Create the intent to show the screenshot in gallery
485 Intent launchIntent = new Intent(Intent.ACTION_VIEW);
486 launchIntent.setDataAndType(mParams.imageUri, "image/png");
487 launchIntent.setFlags(
488 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
489
490 final long now = System.currentTimeMillis();
491
492 // Update the text and the icon for the existing notification
493 mPublicNotificationBuilder
494 .setContentTitle(r.getString(R.string.screenshot_saved_title))
495 .setContentText(r.getString(R.string.screenshot_saved_text))
496 .setContentIntent(
497 PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
498 .setWhen(now)
499 .setAutoCancel(true)
500 .setColor(context.getColor(
501 com.android.internal.R.color.system_notification_accent_color));
502 mNotificationBuilder
503 .setContentTitle(r.getString(R.string.screenshot_saved_title))
504 .setContentText(r.getString(R.string.screenshot_saved_text))
505 .setContentIntent(PendingIntent.getActivity(mParams.context, 0,
506 launchIntent, 0))
507 .setWhen(now)
508 .setAutoCancel(true)
509 .setColor(context.getColor(
510 com.android.internal.R.color.system_notification_accent_color))
511 .setPublicVersion(mPublicNotificationBuilder.build())
512 .setFlag(Notification.FLAG_NO_CLEAR, false);
513
514 mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
515 mNotificationBuilder.build());
516 }
517 }
Aran Inkeb565fc2019-11-15 15:52:31 -0500518 mParams.finisher.accept(mParams.imageUri);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500519 mParams.clearContext();
520 }
521
522 @Override
523 protected void onCancelled(Void params) {
524 // If we are cancelled while the task is running in the background, we may get null
525 // params. The finisher is expected to always be called back, so just use the baked-in
526 // params from the ctor in any case.
Aran Inkeb565fc2019-11-15 15:52:31 -0500527 mParams.finisher.accept(null);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500528 mParams.clearImage();
529 mParams.clearContext();
530
531 // Cancel the posted notification
532 mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
533 }
534}