Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.systemui.screenshot; |
| 18 | |
| 19 | import android.app.ActivityTaskManager; |
| 20 | import android.app.Notification; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 21 | import android.app.PendingIntent; |
| 22 | import android.content.ClipData; |
| 23 | import android.content.ClipDescription; |
| 24 | import android.content.ComponentName; |
Jeff Sharkey | 04b4ba1 | 2019-12-15 22:42:42 -0700 | [diff] [blame] | 25 | import android.content.ContentResolver; |
| 26 | import android.content.ContentValues; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 27 | import android.content.Context; |
| 28 | import android.content.Intent; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 29 | import android.content.res.Resources; |
| 30 | import android.graphics.Bitmap; |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 31 | import android.graphics.drawable.Icon; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 32 | import android.media.ExifInterface; |
| 33 | import android.net.Uri; |
| 34 | import android.os.AsyncTask; |
| 35 | import android.os.Build; |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 36 | import android.os.Bundle; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 37 | import android.os.Environment; |
| 38 | import android.os.Handler; |
| 39 | import android.os.ParcelFileDescriptor; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 40 | import android.os.RemoteException; |
| 41 | import android.os.UserHandle; |
| 42 | import android.os.UserManager; |
| 43 | import android.provider.DeviceConfig; |
| 44 | import android.provider.MediaStore; |
Jeff Sharkey | 04b4ba1 | 2019-12-15 22:42:42 -0700 | [diff] [blame] | 45 | import android.provider.MediaStore.MediaColumns; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 46 | import android.text.TextUtils; |
Jeff Sharkey | 04b4ba1 | 2019-12-15 22:42:42 -0700 | [diff] [blame] | 47 | import android.text.format.DateUtils; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 48 | import android.util.Slog; |
| 49 | |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 50 | import com.android.internal.annotations.VisibleForTesting; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 51 | import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 52 | import com.android.systemui.R; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 53 | import com.android.systemui.SystemUIFactory; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 54 | |
| 55 | import java.io.File; |
| 56 | import java.io.IOException; |
| 57 | import java.io.OutputStream; |
| 58 | import java.text.DateFormat; |
| 59 | import java.text.SimpleDateFormat; |
| 60 | import java.time.Instant; |
| 61 | import java.time.ZoneId; |
| 62 | import java.time.ZoneOffset; |
| 63 | import java.time.ZonedDateTime; |
| 64 | import java.time.format.DateTimeFormatter; |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 65 | import java.util.ArrayList; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 66 | import java.util.Date; |
| 67 | import java.util.List; |
| 68 | import java.util.Objects; |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 69 | import java.util.Random; |
| 70 | import java.util.UUID; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 71 | import java.util.concurrent.CompletableFuture; |
| 72 | |
| 73 | /** |
| 74 | * An AsyncTask that saves an image to the media store in the background. |
| 75 | */ |
| 76 | class 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"; |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 80 | private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s"; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 81 | private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"; |
| 82 | |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 83 | private final Context mContext; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 84 | private final GlobalScreenshot.SaveImageInBackgroundData mParams; |
Miranda Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 85 | private final GlobalScreenshot.SavedImageData mImageData; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 86 | private final String mImageFileName; |
| 87 | private final long mImageTime; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 88 | private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 89 | private final String mScreenshotId; |
| 90 | private final boolean mSmartActionsEnabled; |
| 91 | private final Random mRandom = new Random(); |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 92 | |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 93 | SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) { |
| 94 | mContext = context; |
Miranda Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 95 | mImageData = new GlobalScreenshot.SavedImageData(); |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 96 | |
| 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); |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 102 | mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID()); |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 103 | |
| 104 | // Initialize screenshot notification smart actions provider. |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 105 | mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, |
Nikita Dubrovsky | 19b70d3 | 2020-01-29 15:53:15 -0800 | [diff] [blame] | 106 | SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true); |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 107 | 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 Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 116 | } |
| 117 | |
| 118 | @Override |
| 119 | protected Void doInBackground(Void... paramsUnused) { |
| 120 | if (isCancelled()) { |
| 121 | return null; |
| 122 | } |
Miranda Kephart | 40c9951 | 2020-06-04 11:27:35 -0400 | [diff] [blame] | 123 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 124 | |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 125 | ContentResolver resolver = mContext.getContentResolver(); |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 126 | Bitmap image = mParams.image; |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 127 | Resources r = mContext.getResources(); |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 128 | |
| 129 | try { |
| 130 | // Save the screenshot to the MediaStore |
Jeff Sharkey | 04b4ba1 | 2019-12-15 22:42:42 -0700 | [diff] [blame] | 131 | 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 Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 140 | |
Jeff Sharkey | 04b4ba1 | 2019-12-15 22:42:42 -0700 | [diff] [blame] | 141 | final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); |
Miranda Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 142 | |
Katsiaryna Naliuka | 2f8ae17 | 2020-04-22 14:14:27 +0200 | [diff] [blame] | 143 | CompletableFuture<List<Notification.Action>> smartActionsFuture = |
| 144 | ScreenshotSmartActions.getSmartActionsFuture( |
| 145 | mScreenshotId, uri, image, mSmartActionsProvider, |
Katsiaryna Naliuka | 104380d | 2020-05-14 14:01:29 +0200 | [diff] [blame] | 146 | mSmartActionsEnabled, getUserHandle(mContext)); |
Katsiaryna Naliuka | 2f8ae17 | 2020-04-22 14:14:27 +0200 | [diff] [blame] | 147 | |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 148 | try { |
| 149 | // First, write the actual data for our screenshot |
Jeff Sharkey | 04b4ba1 | 2019-12-15 22:42:42 -0700 | [diff] [blame] | 150 | try (OutputStream out = resolver.openOutputStream(uri)) { |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 151 | 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 Sharkey | 04b4ba1 | 2019-12-15 22:42:42 -0700 | [diff] [blame] | 157 | try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) { |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 158 | 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 Sharkey | 04b4ba1 | 2019-12-15 22:42:42 -0700 | [diff] [blame] | 184 | |
| 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 Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 190 | } catch (Exception e) { |
Jeff Sharkey | 04b4ba1 | 2019-12-15 22:42:42 -0700 | [diff] [blame] | 191 | resolver.delete(uri, null); |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 192 | throw e; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 193 | } |
| 194 | |
Miranda Kephart | 43c0296 | 2020-01-10 17:50:48 -0500 | [diff] [blame] | 195 | 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 Naliuka | 2f8ae17 | 2020-04-22 14:14:27 +0200 | [diff] [blame] | 203 | mScreenshotId, smartActionsFuture, timeoutMs, |
Miranda Kephart | 43c0296 | 2020-01-10 17:50:48 -0500 | [diff] [blame] | 204 | mSmartActionsProvider), |
| 205 | mContext)); |
| 206 | } |
Miranda Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 207 | |
| 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 Kephart | 3bf1ea3 | 2020-05-21 15:27:49 -0400 | [diff] [blame] | 215 | mParams.finisher.accept(mImageData.uri); |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 216 | mParams.image = null; |
| 217 | mParams.errorMsgResId = 0; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 218 | } 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 Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 224 | mImageData.reset(); |
| 225 | mParams.mActionsReadyListener.onActionsReady(mImageData); |
Miranda Kephart | 3bf1ea3 | 2020-05-21 15:27:49 -0400 | [diff] [blame] | 226 | mParams.finisher.accept(null); |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 227 | } |
| 228 | |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 229 | return null; |
| 230 | } |
| 231 | |
Miranda Kephart | a4c7975 | 2020-04-24 15:23:58 -0400 | [diff] [blame] | 232 | /** |
Miranda Kephart | 3bf1ea3 | 2020-05-21 15:27:49 -0400 | [diff] [blame] | 233 | * 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 Kephart | a4c7975 | 2020-04-24 15:23:58 -0400 | [diff] [blame] | 235 | */ |
Miranda Kephart | 3bf1ea3 | 2020-05-21 15:27:49 -0400 | [diff] [blame] | 236 | void setActionsReadyListener(GlobalScreenshot.ActionsReadyListener listener) { |
| 237 | mParams.mActionsReadyListener = listener; |
Miranda Kephart | a4c7975 | 2020-04-24 15:23:58 -0400 | [diff] [blame] | 238 | } |
| 239 | |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 240 | @Override |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 241 | 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 Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 245 | mImageData.reset(); |
| 246 | mParams.mActionsReadyListener.onActionsReady(mImageData); |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 247 | mParams.finisher.accept(null); |
| 248 | mParams.clearImage(); |
| 249 | } |
| 250 | |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 251 | @VisibleForTesting |
Miranda Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 252 | Notification.Action createShareAction(Context context, Resources r, Uri uri) { |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 253 | // 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 Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 280 | 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 Kephart | d3d0aee | 2020-06-24 11:50:07 -0400 | [diff] [blame] | 284 | |
| 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); |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 288 | |
| 289 | // Create a share action for the notification |
| 290 | PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, |
| 291 | new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) |
Matt Casey | ea12819 | 2020-06-19 15:35:20 -0400 | [diff] [blame] | 292 | .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent) |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 293 | .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true) |
| 294 | .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) |
| 295 | .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, |
| 296 | mSmartActionsEnabled) |
Miranda Kephart | ffb1897 | 2020-02-12 19:39:40 -0500 | [diff] [blame] | 297 | .setAction(Intent.ACTION_SEND) |
| 298 | .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 299 | PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM); |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 300 | |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 301 | Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 302 | Icon.createWithResource(r, R.drawable.ic_screenshot_share), |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 303 | r.getString(com.android.internal.R.string.share), shareAction); |
Miranda Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 304 | |
| 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 |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 313 | |
| 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 Casey | 559ac1c | 2020-04-25 14:02:14 -0400 | [diff] [blame] | 325 | editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 326 | |
Matt Casey | ea12819 | 2020-06-19 15:35:20 -0400 | [diff] [blame] | 327 | PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0, |
| 328 | editIntent, 0, null, UserHandle.CURRENT); |
| 329 | |
Miranda Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 330 | // 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 | |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 334 | // Create a edit action |
Matt Casey | ea12819 | 2020-06-19 15:35:20 -0400 | [diff] [blame] | 335 | PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 336 | new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) |
Matt Casey | ea12819 | 2020-06-19 15:35:20 -0400 | [diff] [blame] | 337 | .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent) |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 338 | .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 Kephart | ffb1897 | 2020-02-12 19:39:40 -0500 | [diff] [blame] | 343 | .setAction(Intent.ACTION_EDIT) |
| 344 | .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), |
Matt Casey | ea12819 | 2020-06-19 15:35:20 -0400 | [diff] [blame] | 345 | PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM); |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 346 | Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 347 | Icon.createWithResource(r, R.drawable.ic_screenshot_edit), |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 348 | r.getString(com.android.internal.R.string.screenshot_edit), editAction); |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 349 | |
Miranda Kephart | 7b2c313 | 2020-03-27 09:54:14 -0400 | [diff] [blame] | 350 | 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 Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 373 | } |
| 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(); |
Satakshi | aaf6953 | 2019-11-07 17:54:24 -0800 | [diff] [blame] | 383 | } |
| 384 | } |
| 385 | |
Katsiaryna Naliuka | 104380d | 2020-05-14 14:01:29 +0200 | [diff] [blame] | 386 | private UserHandle getUserHandle(Context context) { |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 387 | UserManager manager = UserManager.get(context); |
Katsiaryna Naliuka | 104380d | 2020-05-14 14:01:29 +0200 | [diff] [blame] | 388 | return manager.getUserInfo(getUserHandleOfForegroundApplication(context)).getUserHandle(); |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 389 | } |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 390 | |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 391 | 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 Kephart | ffb1897 | 2020-02-12 19:39:40 -0500 | [diff] [blame] | 401 | Intent intent = new Intent(context, GlobalScreenshot.SmartActionsReceiver.class) |
| 402 | .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent) |
| 403 | .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 404 | 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 Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 411 | } |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 412 | return broadcastActions; |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 413 | } |
| 414 | |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 415 | 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 Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 421 | } |
Miranda Kephart | 9bbecf3 | 2019-12-06 10:54:58 -0500 | [diff] [blame] | 422 | |
| 423 | |
Miranda Kephart | ca6c5eb | 2019-11-14 12:44:11 -0500 | [diff] [blame] | 424 | } |