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 static android.content.Context.NOTIFICATION_SERVICE; 20 21 import android.app.Notification; 22 import android.app.NotificationManager; 23 import android.app.PendingIntent; 24 import android.app.admin.DevicePolicyManager; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.res.Resources; 28 import android.graphics.Bitmap; 29 import android.graphics.Canvas; 30 import android.graphics.ColorMatrix; 31 import android.graphics.ColorMatrixColorFilter; 32 import android.graphics.Matrix; 33 import android.graphics.Paint; 34 import android.graphics.Picture; 35 import android.os.UserHandle; 36 import android.util.DisplayMetrics; 37 import android.view.WindowManager; 38 39 import com.android.internal.messages.nano.SystemMessageProto; 40 import com.android.systemui.R; 41 import com.android.systemui.SystemUI; 42 import com.android.systemui.util.NotificationChannels; 43 44 import javax.inject.Inject; 45 46 /** 47 * Convenience class to handle showing and hiding notifications while taking a screenshot. 48 */ 49 public class ScreenshotNotificationsController { 50 private static final String TAG = "ScreenshotNotificationManager"; 51 52 private final Context mContext; 53 private final Resources mResources; 54 private final NotificationManager mNotificationManager; 55 private final Notification.BigPictureStyle mNotificationStyle; 56 57 private int mIconSize; 58 private int mPreviewWidth, mPreviewHeight; 59 private Notification.Builder mNotificationBuilder, mPublicNotificationBuilder; 60 61 @Inject ScreenshotNotificationsController(Context context, WindowManager windowManager)62 ScreenshotNotificationsController(Context context, WindowManager windowManager) { 63 mContext = context; 64 mResources = context.getResources(); 65 66 mNotificationManager = 67 (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); 68 69 mIconSize = mResources.getDimensionPixelSize( 70 android.R.dimen.notification_large_icon_height); 71 72 DisplayMetrics displayMetrics = new DisplayMetrics(); 73 windowManager.getDefaultDisplay().getRealMetrics(displayMetrics); 74 75 76 // determine the optimal preview size 77 int panelWidth = 0; 78 try { 79 panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width); 80 } catch (Resources.NotFoundException e) { 81 } 82 if (panelWidth <= 0) { 83 // includes notification_panel_width==match_parent (-1) 84 panelWidth = displayMetrics.widthPixels; 85 } 86 mPreviewWidth = panelWidth; 87 mPreviewHeight = mResources.getDimensionPixelSize(R.dimen.notification_max_height); 88 89 // Setup the notification 90 mNotificationStyle = new Notification.BigPictureStyle(); 91 } 92 93 /** 94 * Resets the notification builders. 95 */ reset()96 public void reset() { 97 // The public notification will show similar info but with the actual screenshot omitted 98 mPublicNotificationBuilder = 99 new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP); 100 mNotificationBuilder = 101 new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP); 102 } 103 104 /** 105 * Sets the current screenshot bitmap. 106 * 107 * @param image the bitmap of the current screenshot (used for preview) 108 */ setImage(Bitmap image)109 public void setImage(Bitmap image) { 110 // Create the large notification icon 111 int imageWidth = image.getWidth(); 112 int imageHeight = image.getHeight(); 113 114 Paint paint = new Paint(); 115 ColorMatrix desat = new ColorMatrix(); 116 desat.setSaturation(0.25f); 117 paint.setColorFilter(new ColorMatrixColorFilter(desat)); 118 Matrix matrix = new Matrix(); 119 int overlayColor = 0x40FFFFFF; 120 121 matrix.setTranslate((mPreviewWidth - imageWidth) / 2f, (mPreviewHeight - imageHeight) / 2f); 122 123 Bitmap picture = generateAdjustedHwBitmap( 124 image, mPreviewWidth, mPreviewHeight, matrix, paint, overlayColor); 125 126 mNotificationStyle.bigPicture(picture.createAshmemBitmap()); 127 128 // Note, we can't use the preview for the small icon, since it is non-square 129 float scale = (float) mIconSize / Math.min(imageWidth, imageHeight); 130 matrix.setScale(scale, scale); 131 matrix.postTranslate( 132 (mIconSize - (scale * imageWidth)) / 2, 133 (mIconSize - (scale * imageHeight)) / 2); 134 Bitmap icon = 135 generateAdjustedHwBitmap(image, mIconSize, mIconSize, matrix, paint, overlayColor); 136 137 /** 138 * NOTE: The following code prepares the notification builder for updating the 139 * notification after the screenshot has been written to disk. 140 */ 141 142 // On the tablet, the large icon makes the notification appear as if it is clickable 143 // (and on small devices, the large icon is not shown) so defer showing the large icon 144 // until we compose the final post-save notification below. 145 mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap()); 146 // But we still don't set it for the expanded view, allowing the smallIcon to show here. 147 mNotificationStyle.bigLargeIcon((Bitmap) null); 148 } 149 150 /** 151 * Shows a notification to inform the user that a screenshot is currently being saved. 152 */ showSavingScreenshotNotification()153 public void showSavingScreenshotNotification() { 154 final long now = System.currentTimeMillis(); 155 156 mPublicNotificationBuilder 157 .setContentTitle(mResources.getString(R.string.screenshot_saving_title)) 158 .setSmallIcon(R.drawable.stat_notify_image) 159 .setCategory(Notification.CATEGORY_PROGRESS) 160 .setWhen(now) 161 .setShowWhen(true) 162 .setColor(mResources.getColor( 163 com.android.internal.R.color.system_notification_accent_color)); 164 SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true); 165 166 mNotificationBuilder 167 .setContentTitle(mResources.getString(R.string.screenshot_saving_title)) 168 .setSmallIcon(R.drawable.stat_notify_image) 169 .setWhen(now) 170 .setShowWhen(true) 171 .setColor(mResources.getColor( 172 com.android.internal.R.color.system_notification_accent_color)) 173 .setStyle(mNotificationStyle) 174 .setPublicVersion(mPublicNotificationBuilder.build()); 175 mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true); 176 SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true); 177 178 mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, 179 mNotificationBuilder.build()); 180 } 181 182 /** 183 * Shows a notification with the saved screenshot and actions that can be taken with it. 184 * 185 * @param actionData SavedImageData struct with image URI and actions 186 */ showScreenshotActionsNotification( GlobalScreenshot.SavedImageData actionData)187 public void showScreenshotActionsNotification( 188 GlobalScreenshot.SavedImageData actionData) { 189 mNotificationBuilder.addAction(actionData.shareAction); 190 mNotificationBuilder.addAction(actionData.editAction); 191 mNotificationBuilder.addAction(actionData.deleteAction); 192 for (Notification.Action smartAction : actionData.smartActions) { 193 mNotificationBuilder.addAction(smartAction); 194 } 195 196 // Create the intent to show the screenshot in gallery 197 Intent launchIntent = new Intent(Intent.ACTION_VIEW); 198 launchIntent.setDataAndType(actionData.uri, "image/png"); 199 launchIntent.setFlags( 200 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); 201 202 final long now = System.currentTimeMillis(); 203 204 // Update the text and the icon for the existing notification 205 mPublicNotificationBuilder 206 .setContentTitle(mResources.getString(R.string.screenshot_saved_title)) 207 .setContentText(mResources.getString(R.string.screenshot_saved_text)) 208 .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0)) 209 .setWhen(now) 210 .setAutoCancel(true) 211 .setColor(mContext.getColor( 212 com.android.internal.R.color.system_notification_accent_color)); 213 mNotificationBuilder 214 .setContentTitle(mResources.getString(R.string.screenshot_saved_title)) 215 .setContentText(mResources.getString(R.string.screenshot_saved_text)) 216 .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0)) 217 .setWhen(now) 218 .setAutoCancel(true) 219 .setColor(mContext.getColor( 220 com.android.internal.R.color.system_notification_accent_color)) 221 .setPublicVersion(mPublicNotificationBuilder.build()) 222 .setFlag(Notification.FLAG_NO_CLEAR, false); 223 224 mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, 225 mNotificationBuilder.build()); 226 } 227 228 /** 229 * Sends a notification that the screenshot capture has failed. 230 */ notifyScreenshotError(int msgResId)231 public void notifyScreenshotError(int msgResId) { 232 Resources res = mContext.getResources(); 233 String errorMsg = res.getString(msgResId); 234 235 // Repurpose the existing notification to notify the user of the error 236 Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS) 237 .setTicker(res.getString(R.string.screenshot_failed_title)) 238 .setContentTitle(res.getString(R.string.screenshot_failed_title)) 239 .setContentText(errorMsg) 240 .setSmallIcon(R.drawable.stat_notify_image_error) 241 .setWhen(System.currentTimeMillis()) 242 .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen 243 .setCategory(Notification.CATEGORY_ERROR) 244 .setAutoCancel(true) 245 .setColor(mContext.getColor( 246 com.android.internal.R.color.system_notification_accent_color)); 247 final DevicePolicyManager dpm = 248 (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 249 final Intent intent = 250 dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE); 251 if (intent != null) { 252 final PendingIntent pendingIntent = PendingIntent.getActivityAsUser( 253 mContext, 0, intent, 0, null, UserHandle.CURRENT); 254 b.setContentIntent(pendingIntent); 255 } 256 257 SystemUI.overrideNotificationAppName(mContext, b, true); 258 259 Notification n = new Notification.BigTextStyle(b) 260 .bigText(errorMsg) 261 .build(); 262 mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n); 263 } 264 265 /** 266 * Cancels the current screenshot notification. 267 */ cancelNotification()268 public void cancelNotification() { 269 mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT); 270 } 271 272 /** 273 * Generates a new hardware bitmap with specified values, copying the content from the 274 * passed in bitmap. 275 */ generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix, Paint paint, int color)276 private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix, 277 Paint paint, int color) { 278 Picture picture = new Picture(); 279 Canvas canvas = picture.beginRecording(width, height); 280 canvas.drawColor(color); 281 canvas.drawBitmap(bitmap, matrix, paint); 282 picture.endRecording(); 283 return Bitmap.createBitmap(picture); 284 } 285 cancelScreenshotNotification(Context context)286 static void cancelScreenshotNotification(Context context) { 287 final NotificationManager nm = 288 (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); 289 nm.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT); 290 } 291 } 292