• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
20 
21 import static com.android.systemui.screenshot.GlobalScreenshot.SHARING_INTENT;
22 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorListenerAdapter;
26 import android.animation.AnimatorSet;
27 import android.animation.ValueAnimator;
28 import android.animation.ValueAnimator.AnimatorUpdateListener;
29 import android.app.ActivityManager;
30 import android.app.ActivityOptions;
31 import android.app.Notification;
32 import android.app.Notification.BigPictureStyle;
33 import android.app.NotificationManager;
34 import android.app.PendingIntent;
35 import android.app.admin.DevicePolicyManager;
36 import android.content.BroadcastReceiver;
37 import android.content.ComponentName;
38 import android.content.ContentResolver;
39 import android.content.ContentValues;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.res.Resources;
43 import android.graphics.Bitmap;
44 import android.graphics.Canvas;
45 import android.graphics.ColorMatrix;
46 import android.graphics.ColorMatrixColorFilter;
47 import android.graphics.Matrix;
48 import android.graphics.Paint;
49 import android.graphics.Picture;
50 import android.graphics.PixelFormat;
51 import android.graphics.PointF;
52 import android.graphics.Rect;
53 import android.media.MediaActionSound;
54 import android.net.Uri;
55 import android.os.AsyncTask;
56 import android.os.Environment;
57 import android.os.PowerManager;
58 import android.os.Process;
59 import android.os.RemoteException;
60 import android.os.UserHandle;
61 import android.provider.MediaStore;
62 import android.util.DisplayMetrics;
63 import android.util.Slog;
64 import android.view.Display;
65 import android.view.LayoutInflater;
66 import android.view.MotionEvent;
67 import android.view.Surface;
68 import android.view.SurfaceControl;
69 import android.view.View;
70 import android.view.ViewGroup;
71 import android.view.WindowManager;
72 import android.view.animation.Interpolator;
73 import android.widget.ImageView;
74 import android.widget.Toast;
75 
76 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
77 import com.android.systemui.R;
78 import com.android.systemui.SystemUI;
79 import com.android.systemui.util.NotificationChannels;
80 
81 import java.io.File;
82 import java.io.FileOutputStream;
83 import java.io.OutputStream;
84 import java.text.DateFormat;
85 import java.text.SimpleDateFormat;
86 import java.util.Date;
87 
88 /**
89  * POD used in the AsyncTask which saves an image in the background.
90  */
91 class SaveImageInBackgroundData {
92     Context context;
93     Bitmap image;
94     Uri imageUri;
95     Runnable finisher;
96     int iconSize;
97     int previewWidth;
98     int previewheight;
99     int errorMsgResId;
100 
clearImage()101     void clearImage() {
102         image = null;
103         imageUri = null;
104         iconSize = 0;
105     }
clearContext()106     void clearContext() {
107         context = null;
108     }
109 }
110 
111 /**
112  * An AsyncTask that saves an image to the media store in the background.
113  */
114 class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
115     private static final String TAG = "SaveImageInBackgroundTask";
116 
117     private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
118     private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
119     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
120 
121     private final SaveImageInBackgroundData mParams;
122     private final NotificationManager mNotificationManager;
123     private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
124     private final File mScreenshotDir;
125     private final String mImageFileName;
126     private final String mImageFilePath;
127     private final long mImageTime;
128     private final BigPictureStyle mNotificationStyle;
129     private final int mImageWidth;
130     private final int mImageHeight;
131 
SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data, NotificationManager nManager)132     SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
133             NotificationManager nManager) {
134         Resources r = context.getResources();
135 
136         // Prepare all the output metadata
137         mParams = data;
138         mImageTime = System.currentTimeMillis();
139         String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
140         mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
141 
142         mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
143                 Environment.DIRECTORY_PICTURES), SCREENSHOTS_DIR_NAME);
144         mImageFilePath = new File(mScreenshotDir, mImageFileName).getAbsolutePath();
145 
146         // Create the large notification icon
147         mImageWidth = data.image.getWidth();
148         mImageHeight = data.image.getHeight();
149         int iconSize = data.iconSize;
150         int previewWidth = data.previewWidth;
151         int previewHeight = data.previewheight;
152 
153         Paint paint = new Paint();
154         ColorMatrix desat = new ColorMatrix();
155         desat.setSaturation(0.25f);
156         paint.setColorFilter(new ColorMatrixColorFilter(desat));
157         Matrix matrix = new Matrix();
158         int overlayColor = 0x40FFFFFF;
159 
160         matrix.setTranslate((previewWidth - mImageWidth) / 2, (previewHeight - mImageHeight) / 2);
161         Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight, matrix,
162                 paint, overlayColor);
163 
164         // Note, we can't use the preview for the small icon, since it is non-square
165         float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
166         matrix.setScale(scale, scale);
167         matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
168                 (iconSize - (scale * mImageHeight)) / 2);
169         Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint,
170                 overlayColor);
171 
172         mNotificationManager = nManager;
173         final long now = System.currentTimeMillis();
174 
175         // Setup the notification
176         mNotificationStyle = new Notification.BigPictureStyle()
177                 .bigPicture(picture.createAshmemBitmap());
178 
179         // The public notification will show similar info but with the actual screenshot omitted
180         mPublicNotificationBuilder =
181                 new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP)
182                         .setContentTitle(r.getString(R.string.screenshot_saving_title))
183                         .setSmallIcon(R.drawable.stat_notify_image)
184                         .setCategory(Notification.CATEGORY_PROGRESS)
185                         .setWhen(now)
186                         .setShowWhen(true)
187                         .setColor(r.getColor(
188                                 com.android.internal.R.color.system_notification_accent_color));
189         SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder, true);
190 
191         mNotificationBuilder = new Notification.Builder(context,
192                 NotificationChannels.SCREENSHOTS_HEADSUP)
193             .setContentTitle(r.getString(R.string.screenshot_saving_title))
194             .setSmallIcon(R.drawable.stat_notify_image)
195             .setWhen(now)
196             .setShowWhen(true)
197             .setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color))
198             .setStyle(mNotificationStyle)
199             .setPublicVersion(mPublicNotificationBuilder.build());
200         mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
201         SystemUI.overrideNotificationAppName(context, mNotificationBuilder, true);
202 
203         mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT,
204                 mNotificationBuilder.build());
205 
206         /**
207          * NOTE: The following code prepares the notification builder for updating the notification
208          * after the screenshot has been written to disk.
209          */
210 
211         // On the tablet, the large icon makes the notification appear as if it is clickable (and
212         // on small devices, the large icon is not shown) so defer showing the large icon until
213         // we compose the final post-save notification below.
214         mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
215         // But we still don't set it for the expanded view, allowing the smallIcon to show here.
216         mNotificationStyle.bigLargeIcon((Bitmap) null);
217     }
218 
219     /**
220      * Generates a new hardware bitmap with specified values, copying the content from the passed
221      * in bitmap.
222      */
generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix, Paint paint, int color)223     private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
224             Paint paint, int color) {
225         Picture picture = new Picture();
226         Canvas canvas = picture.beginRecording(width, height);
227         canvas.drawColor(color);
228         canvas.drawBitmap(bitmap, matrix, paint);
229         picture.endRecording();
230         return Bitmap.createBitmap(picture);
231     }
232 
233     @Override
doInBackground(Void... params)234     protected Void doInBackground(Void... params) {
235         if (isCancelled()) {
236             return null;
237         }
238 
239         // By default, AsyncTask sets the worker thread to have background thread priority, so bump
240         // it back up so that we save a little quicker.
241         Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
242 
243         Context context = mParams.context;
244         Bitmap image = mParams.image;
245         Resources r = context.getResources();
246 
247         try {
248             // Create screenshot directory if it doesn't exist
249             mScreenshotDir.mkdirs();
250 
251             // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds
252             // for DATE_TAKEN
253             long dateSeconds = mImageTime / 1000;
254 
255             // Save
256             OutputStream out = new FileOutputStream(mImageFilePath);
257             image.compress(Bitmap.CompressFormat.PNG, 100, out);
258             out.flush();
259             out.close();
260 
261             // Save the screenshot to the MediaStore
262             ContentValues values = new ContentValues();
263             ContentResolver resolver = context.getContentResolver();
264             values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
265             values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
266             values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
267             values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
268             values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
269             values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
270             values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
271             values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
272             values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
273             values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
274             Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
275 
276             // Create a share intent
277             String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
278             String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
279             Intent sharingIntent = new Intent(Intent.ACTION_SEND);
280             sharingIntent.setType("image/png");
281             sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
282             sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
283             sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
284 
285             // Create a share action for the notification. Note, we proxy the call to
286             // ScreenshotActionReceiver because RemoteViews currently forces an activity options
287             // on the PendingIntent being launched, and since we don't want to trigger the share
288             // sheet in this case, we start the chooser activity directly in
289             // ScreenshotActionReceiver.
290             PendingIntent shareAction = PendingIntent.getBroadcast(context, 0,
291                     new Intent(context, GlobalScreenshot.ScreenshotActionReceiver.class)
292                             .putExtra(SHARING_INTENT, sharingIntent),
293                     PendingIntent.FLAG_CANCEL_CURRENT);
294             Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
295                     R.drawable.ic_screenshot_share,
296                     r.getString(com.android.internal.R.string.share), shareAction);
297             mNotificationBuilder.addAction(shareActionBuilder.build());
298 
299             Intent editIntent = new Intent(Intent.ACTION_EDIT);
300             editIntent.setType("image/png");
301             editIntent.setData(uri);
302             editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
303             editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
304 
305             // Create a edit action for the notification the same way.
306             PendingIntent editAction = PendingIntent.getBroadcast(context, 1,
307                     new Intent(context, GlobalScreenshot.ScreenshotActionReceiver.class)
308                             .putExtra(SHARING_INTENT, editIntent),
309                     PendingIntent.FLAG_CANCEL_CURRENT);
310             Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
311                     R.drawable.ic_screenshot_edit,
312                     r.getString(com.android.internal.R.string.screenshot_edit), editAction);
313             mNotificationBuilder.addAction(editActionBuilder.build());
314 
315 
316             // Create a delete action for the notification
317             PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
318                     new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
319                             .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
320                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
321             Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
322                     R.drawable.ic_screenshot_delete,
323                     r.getString(com.android.internal.R.string.delete), deleteAction);
324             mNotificationBuilder.addAction(deleteActionBuilder.build());
325 
326             mParams.imageUri = uri;
327             mParams.image = null;
328             mParams.errorMsgResId = 0;
329         } catch (Exception e) {
330             // IOException/UnsupportedOperationException may be thrown if external storage is not
331             // mounted
332             Slog.e(TAG, "unable to save screenshot", e);
333             mParams.clearImage();
334             mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
335         }
336 
337         // Recycle the bitmap data
338         if (image != null) {
339             image.recycle();
340         }
341 
342         return null;
343     }
344 
345     @Override
onPostExecute(Void params)346     protected void onPostExecute(Void params) {
347         if (mParams.errorMsgResId != 0) {
348             // Show a message that we've failed to save the image to disk
349             GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager,
350                     mParams.errorMsgResId);
351         } else {
352             // Show the final notification to indicate screenshot saved
353             Context context = mParams.context;
354             Resources r = context.getResources();
355 
356             // Create the intent to show the screenshot in gallery
357             Intent launchIntent = new Intent(Intent.ACTION_VIEW);
358             launchIntent.setDataAndType(mParams.imageUri, "image/png");
359             launchIntent.setFlags(
360                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
361 
362             final long now = System.currentTimeMillis();
363 
364             // Update the text and the icon for the existing notification
365             mPublicNotificationBuilder
366                     .setContentTitle(r.getString(R.string.screenshot_saved_title))
367                     .setContentText(r.getString(R.string.screenshot_saved_text))
368                     .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
369                     .setWhen(now)
370                     .setAutoCancel(true)
371                     .setColor(context.getColor(
372                             com.android.internal.R.color.system_notification_accent_color));
373             mNotificationBuilder
374                 .setContentTitle(r.getString(R.string.screenshot_saved_title))
375                 .setContentText(r.getString(R.string.screenshot_saved_text))
376                 .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
377                 .setWhen(now)
378                 .setAutoCancel(true)
379                 .setColor(context.getColor(
380                         com.android.internal.R.color.system_notification_accent_color))
381                 .setPublicVersion(mPublicNotificationBuilder.build())
382                 .setFlag(Notification.FLAG_NO_CLEAR, false);
383 
384             mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT,
385                     mNotificationBuilder.build());
386         }
387         mParams.finisher.run();
388         mParams.clearContext();
389     }
390 
391     @Override
onCancelled(Void params)392     protected void onCancelled(Void params) {
393         // If we are cancelled while the task is running in the background, we may get null params.
394         // The finisher is expected to always be called back, so just use the baked-in params from
395         // the ctor in any case.
396         mParams.finisher.run();
397         mParams.clearImage();
398         mParams.clearContext();
399 
400         // Cancel the posted notification
401         mNotificationManager.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
402     }
403 }
404 
405 /**
406  * An AsyncTask that deletes an image from the media store in the background.
407  */
408 class DeleteImageInBackgroundTask extends AsyncTask<Uri, Void, Void> {
409     private Context mContext;
410 
DeleteImageInBackgroundTask(Context context)411     DeleteImageInBackgroundTask(Context context) {
412         mContext = context;
413     }
414 
415     @Override
doInBackground(Uri... params)416     protected Void doInBackground(Uri... params) {
417         if (params.length != 1) return null;
418 
419         Uri screenshotUri = params[0];
420         ContentResolver resolver = mContext.getContentResolver();
421         resolver.delete(screenshotUri, null, null);
422         return null;
423     }
424 }
425 
426 class GlobalScreenshot {
427     static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
428     static final String SHARING_INTENT = "android:screenshot_sharing_intent";
429 
430     private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
431     private static final int SCREENSHOT_DROP_IN_DURATION = 430;
432     private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
433     private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
434     private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
435     private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
436     private static final float BACKGROUND_ALPHA = 0.5f;
437     private static final float SCREENSHOT_SCALE = 1f;
438     private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
439     private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
440     private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
441     private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
442     private final int mPreviewWidth;
443     private final int mPreviewHeight;
444 
445     private Context mContext;
446     private WindowManager mWindowManager;
447     private WindowManager.LayoutParams mWindowLayoutParams;
448     private NotificationManager mNotificationManager;
449     private Display mDisplay;
450     private DisplayMetrics mDisplayMetrics;
451     private Matrix mDisplayMatrix;
452 
453     private Bitmap mScreenBitmap;
454     private View mScreenshotLayout;
455     private ScreenshotSelectorView mScreenshotSelectorView;
456     private ImageView mBackgroundView;
457     private ImageView mScreenshotView;
458     private ImageView mScreenshotFlash;
459 
460     private AnimatorSet mScreenshotAnimation;
461 
462     private int mNotificationIconSize;
463     private float mBgPadding;
464     private float mBgPaddingScale;
465 
466     private AsyncTask<Void, Void, Void> mSaveInBgTask;
467 
468     private MediaActionSound mCameraSound;
469 
470 
471     /**
472      * @param context everything needs a context :(
473      */
GlobalScreenshot(Context context)474     public GlobalScreenshot(Context context) {
475         Resources r = context.getResources();
476         mContext = context;
477         LayoutInflater layoutInflater = (LayoutInflater)
478                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
479 
480         // Inflate the screenshot layout
481         mDisplayMatrix = new Matrix();
482         mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
483         mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
484         mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
485         mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
486         mScreenshotSelectorView = (ScreenshotSelectorView) mScreenshotLayout.findViewById(
487                 R.id.global_screenshot_selector);
488         mScreenshotLayout.setFocusable(true);
489         mScreenshotSelectorView.setFocusable(true);
490         mScreenshotSelectorView.setFocusableInTouchMode(true);
491         mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
492             @Override
493             public boolean onTouch(View v, MotionEvent event) {
494                 // Intercept and ignore all touch events
495                 return true;
496             }
497         });
498 
499         // Setup the window that we are going to use
500         mWindowLayoutParams = new WindowManager.LayoutParams(
501                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
502                 WindowManager.LayoutParams.TYPE_SCREENSHOT,
503                 WindowManager.LayoutParams.FLAG_FULLSCREEN
504                     | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
505                     | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
506                 PixelFormat.TRANSLUCENT);
507         mWindowLayoutParams.setTitle("ScreenshotAnimation");
508         mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
509         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
510         mNotificationManager =
511             (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
512         mDisplay = mWindowManager.getDefaultDisplay();
513         mDisplayMetrics = new DisplayMetrics();
514         mDisplay.getRealMetrics(mDisplayMetrics);
515 
516         // Get the various target sizes
517         mNotificationIconSize =
518             r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
519 
520         // Scale has to account for both sides of the bg
521         mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
522         mBgPaddingScale = mBgPadding /  mDisplayMetrics.widthPixels;
523 
524         // determine the optimal preview size
525         int panelWidth = 0;
526         try {
527             panelWidth = r.getDimensionPixelSize(R.dimen.notification_panel_width);
528         } catch (Resources.NotFoundException e) {
529         }
530         if (panelWidth <= 0) {
531             // includes notification_panel_width==match_parent (-1)
532             panelWidth = mDisplayMetrics.widthPixels;
533         }
534         mPreviewWidth = panelWidth;
535         mPreviewHeight = r.getDimensionPixelSize(R.dimen.notification_max_height);
536 
537         // Setup the Camera shutter sound
538         mCameraSound = new MediaActionSound();
539         mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
540     }
541 
542     /**
543      * Creates a new worker thread and saves the screenshot to the media store.
544      */
saveScreenshotInWorkerThread(Runnable finisher)545     private void saveScreenshotInWorkerThread(Runnable finisher) {
546         SaveImageInBackgroundData data = new SaveImageInBackgroundData();
547         data.context = mContext;
548         data.image = mScreenBitmap;
549         data.iconSize = mNotificationIconSize;
550         data.finisher = finisher;
551         data.previewWidth = mPreviewWidth;
552         data.previewheight = mPreviewHeight;
553         if (mSaveInBgTask != null) {
554             mSaveInBgTask.cancel(false);
555         }
556         mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
557                 .execute();
558     }
559 
560     /**
561      * @return the current display rotation in degrees
562      */
getDegreesForRotation(int value)563     private float getDegreesForRotation(int value) {
564         switch (value) {
565         case Surface.ROTATION_90:
566             return 360f - 90f;
567         case Surface.ROTATION_180:
568             return 360f - 180f;
569         case Surface.ROTATION_270:
570             return 360f - 270f;
571         }
572         return 0f;
573     }
574 
575     /**
576      * Takes a screenshot of the current display and shows an animation.
577      */
takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible, Rect crop)578     private void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
579             Rect crop) {
580         int rot = mDisplay.getRotation();
581         int width = crop.width();
582         int height = crop.height();
583 
584         // Take the screenshot
585         mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
586         if (mScreenBitmap == null) {
587             notifyScreenshotError(mContext, mNotificationManager,
588                     R.string.screenshot_failed_to_capture_text);
589             finisher.run();
590             return;
591         }
592 
593         // Optimizations
594         mScreenBitmap.setHasAlpha(false);
595         mScreenBitmap.prepareToDraw();
596 
597         // Start the post-screenshot animation
598         startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
599                 statusBarVisible, navBarVisible);
600     }
601 
takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible)602     void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
603         mDisplay.getRealMetrics(mDisplayMetrics);
604         takeScreenshot(finisher, statusBarVisible, navBarVisible,
605                 new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
606     }
607 
608     /**
609      * Displays a screenshot selector
610      */
takeScreenshotPartial(final Runnable finisher, final boolean statusBarVisible, final boolean navBarVisible)611     void takeScreenshotPartial(final Runnable finisher, final boolean statusBarVisible,
612             final boolean navBarVisible) {
613         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
614         mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
615             @Override
616             public boolean onTouch(View v, MotionEvent event) {
617                 ScreenshotSelectorView view = (ScreenshotSelectorView) v;
618                 switch (event.getAction()) {
619                     case MotionEvent.ACTION_DOWN:
620                         view.startSelection((int) event.getX(), (int) event.getY());
621                         return true;
622                     case MotionEvent.ACTION_MOVE:
623                         view.updateSelection((int) event.getX(), (int) event.getY());
624                         return true;
625                     case MotionEvent.ACTION_UP:
626                         view.setVisibility(View.GONE);
627                         mWindowManager.removeView(mScreenshotLayout);
628                         final Rect rect = view.getSelectionRect();
629                         if (rect != null) {
630                             if (rect.width() != 0 && rect.height() != 0) {
631                                 // Need mScreenshotLayout to handle it after the view disappears
632                                 mScreenshotLayout.post(new Runnable() {
633                                     public void run() {
634                                         takeScreenshot(finisher, statusBarVisible, navBarVisible,
635                                                 rect);
636                                     }
637                                 });
638                             }
639                         }
640 
641                         view.stopSelection();
642                         return true;
643                 }
644 
645                 return false;
646             }
647         });
648         mScreenshotLayout.post(new Runnable() {
649             @Override
650             public void run() {
651                 mScreenshotSelectorView.setVisibility(View.VISIBLE);
652                 mScreenshotSelectorView.requestFocus();
653             }
654         });
655     }
656 
657     /**
658      * Cancels screenshot request
659      */
stopScreenshot()660     void stopScreenshot() {
661         // If the selector layer still presents on screen, we remove it and resets its state.
662         if (mScreenshotSelectorView.getSelectionRect() != null) {
663             mWindowManager.removeView(mScreenshotLayout);
664             mScreenshotSelectorView.stopSelection();
665         }
666     }
667 
668     /**
669      * Starts the animation after taking the screenshot
670      */
startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible, boolean navBarVisible)671     private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
672             boolean navBarVisible) {
673         // If power save is on, show a toast so there is some visual indication that a screenshot
674         // has been taken.
675         PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
676         if (powerManager.isPowerSaveMode()) {
677             Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
678         }
679 
680         // Add the view for the animation
681         mScreenshotView.setImageBitmap(mScreenBitmap);
682         mScreenshotLayout.requestFocus();
683 
684         // Setup the animation with the screenshot just taken
685         if (mScreenshotAnimation != null) {
686             if (mScreenshotAnimation.isStarted()) {
687                 mScreenshotAnimation.end();
688             }
689             mScreenshotAnimation.removeAllListeners();
690         }
691 
692         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
693         ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
694         ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
695                 statusBarVisible, navBarVisible);
696         mScreenshotAnimation = new AnimatorSet();
697         mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
698         mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
699             @Override
700             public void onAnimationEnd(Animator animation) {
701                 // Save the screenshot once we have a bit of time now
702                 saveScreenshotInWorkerThread(finisher);
703                 mWindowManager.removeView(mScreenshotLayout);
704 
705                 // Clear any references to the bitmap
706                 mScreenBitmap = null;
707                 mScreenshotView.setImageBitmap(null);
708             }
709         });
710         mScreenshotLayout.post(new Runnable() {
711             @Override
712             public void run() {
713                 // Play the shutter sound to notify that we've taken a screenshot
714                 mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
715 
716                 mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
717                 mScreenshotView.buildLayer();
718                 mScreenshotAnimation.start();
719             }
720         });
721     }
createScreenshotDropInAnimation()722     private ValueAnimator createScreenshotDropInAnimation() {
723         final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
724                 / SCREENSHOT_DROP_IN_DURATION);
725         final float flashDurationPct = 2f * flashPeakDurationPct;
726         final Interpolator flashAlphaInterpolator = new Interpolator() {
727             @Override
728             public float getInterpolation(float x) {
729                 // Flash the flash view in and out quickly
730                 if (x <= flashDurationPct) {
731                     return (float) Math.sin(Math.PI * (x / flashDurationPct));
732                 }
733                 return 0;
734             }
735         };
736         final Interpolator scaleInterpolator = new Interpolator() {
737             @Override
738             public float getInterpolation(float x) {
739                 // We start scaling when the flash is at it's peak
740                 if (x < flashPeakDurationPct) {
741                     return 0;
742                 }
743                 return (x - flashDurationPct) / (1f - flashDurationPct);
744             }
745         };
746         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
747         anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
748         anim.addListener(new AnimatorListenerAdapter() {
749             @Override
750             public void onAnimationStart(Animator animation) {
751                 mBackgroundView.setAlpha(0f);
752                 mBackgroundView.setVisibility(View.VISIBLE);
753                 mScreenshotView.setAlpha(0f);
754                 mScreenshotView.setTranslationX(0f);
755                 mScreenshotView.setTranslationY(0f);
756                 mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
757                 mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
758                 mScreenshotView.setVisibility(View.VISIBLE);
759                 mScreenshotFlash.setAlpha(0f);
760                 mScreenshotFlash.setVisibility(View.VISIBLE);
761             }
762             @Override
763             public void onAnimationEnd(android.animation.Animator animation) {
764                 mScreenshotFlash.setVisibility(View.GONE);
765             }
766         });
767         anim.addUpdateListener(new AnimatorUpdateListener() {
768             @Override
769             public void onAnimationUpdate(ValueAnimator animation) {
770                 float t = (Float) animation.getAnimatedValue();
771                 float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
772                     - scaleInterpolator.getInterpolation(t)
773                         * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
774                 mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
775                 mScreenshotView.setAlpha(t);
776                 mScreenshotView.setScaleX(scaleT);
777                 mScreenshotView.setScaleY(scaleT);
778                 mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
779             }
780         });
781         return anim;
782     }
createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible, boolean navBarVisible)783     private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
784             boolean navBarVisible) {
785         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
786         anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
787         anim.addListener(new AnimatorListenerAdapter() {
788             @Override
789             public void onAnimationEnd(Animator animation) {
790                 mBackgroundView.setVisibility(View.GONE);
791                 mScreenshotView.setVisibility(View.GONE);
792                 mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
793             }
794         });
795 
796         if (!statusBarVisible || !navBarVisible) {
797             // There is no status bar/nav bar, so just fade the screenshot away in place
798             anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
799             anim.addUpdateListener(new AnimatorUpdateListener() {
800                 @Override
801                 public void onAnimationUpdate(ValueAnimator animation) {
802                     float t = (Float) animation.getAnimatedValue();
803                     float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
804                             - t * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
805                     mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
806                     mScreenshotView.setAlpha(1f - t);
807                     mScreenshotView.setScaleX(scaleT);
808                     mScreenshotView.setScaleY(scaleT);
809                 }
810             });
811         } else {
812             // In the case where there is a status bar, animate to the origin of the bar (top-left)
813             final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
814                     / SCREENSHOT_DROP_OUT_DURATION;
815             final Interpolator scaleInterpolator = new Interpolator() {
816                 @Override
817                 public float getInterpolation(float x) {
818                     if (x < scaleDurationPct) {
819                         // Decelerate, and scale the input accordingly
820                         return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
821                     }
822                     return 1f;
823                 }
824             };
825 
826             // Determine the bounds of how to scale
827             float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
828             float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
829             final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
830             final PointF finalPos = new PointF(
831                 -halfScreenWidth + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
832                 -halfScreenHeight + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
833 
834             // Animate the screenshot to the status bar
835             anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
836             anim.addUpdateListener(new AnimatorUpdateListener() {
837                 @Override
838                 public void onAnimationUpdate(ValueAnimator animation) {
839                     float t = (Float) animation.getAnimatedValue();
840                     float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
841                         - scaleInterpolator.getInterpolation(t)
842                             * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
843                     mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
844                     mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
845                     mScreenshotView.setScaleX(scaleT);
846                     mScreenshotView.setScaleY(scaleT);
847                     mScreenshotView.setTranslationX(t * finalPos.x);
848                     mScreenshotView.setTranslationY(t * finalPos.y);
849                 }
850             });
851         }
852         return anim;
853     }
854 
notifyScreenshotError(Context context, NotificationManager nManager, int msgResId)855     static void notifyScreenshotError(Context context, NotificationManager nManager, int msgResId) {
856         Resources r = context.getResources();
857         String errorMsg = r.getString(msgResId);
858 
859         // Repurpose the existing notification to notify the user of the error
860         Notification.Builder b = new Notification.Builder(context, NotificationChannels.ALERTS)
861             .setTicker(r.getString(R.string.screenshot_failed_title))
862             .setContentTitle(r.getString(R.string.screenshot_failed_title))
863             .setContentText(errorMsg)
864             .setSmallIcon(R.drawable.stat_notify_image_error)
865             .setWhen(System.currentTimeMillis())
866             .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
867             .setCategory(Notification.CATEGORY_ERROR)
868             .setAutoCancel(true)
869             .setColor(context.getColor(
870                         com.android.internal.R.color.system_notification_accent_color));
871         final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
872                 Context.DEVICE_POLICY_SERVICE);
873         final Intent intent = dpm.createAdminSupportIntent(
874                 DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
875         if (intent != null) {
876             final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
877                     context, 0, intent, 0, null, UserHandle.CURRENT);
878             b.setContentIntent(pendingIntent);
879         }
880 
881         SystemUI.overrideNotificationAppName(context, b, true);
882 
883         Notification n = new Notification.BigTextStyle(b)
884                 .bigText(errorMsg)
885                 .build();
886         nManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
887     }
888 
889     /**
890      * Receiver to proxy the share or edit intent.
891      */
892     public static class ScreenshotActionReceiver extends BroadcastReceiver {
893         @Override
onReceive(Context context, Intent intent)894         public void onReceive(Context context, Intent intent) {
895             try {
896                 ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_SCREENSHOT);
897             } catch (RemoteException e) {
898             }
899 
900             Intent actionIntent = intent.getParcelableExtra(SHARING_INTENT);
901 
902             // If this is an edit & default editor exists, route straight there.
903             String editorPackage = context.getResources().getString(R.string.config_screenshotEditor);
904             if (actionIntent.getAction() == Intent.ACTION_EDIT &&
905                     editorPackage != null && editorPackage.length() > 0) {
906                 actionIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
907                 final NotificationManager nm =
908                         (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
909                 nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
910             } else {
911                 PendingIntent chooseAction = PendingIntent.getBroadcast(context, 0,
912                         new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
913                         PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
914                 actionIntent = Intent.createChooser(actionIntent, null,
915                         chooseAction.getIntentSender())
916                         .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
917             }
918 
919             ActivityOptions opts = ActivityOptions.makeBasic();
920             opts.setDisallowEnterPictureInPictureWhileLaunching(true);
921 
922             context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
923         }
924     }
925 
926     /**
927      * Removes the notification for a screenshot after a share or edit target is chosen.
928      */
929     public static class TargetChosenReceiver extends BroadcastReceiver {
930         @Override
onReceive(Context context, Intent intent)931         public void onReceive(Context context, Intent intent) {
932             // Clear the notification
933             final NotificationManager nm =
934                     (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
935             nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
936         }
937     }
938 
939     /**
940      * Removes the last screenshot.
941      */
942     public static class DeleteScreenshotReceiver extends BroadcastReceiver {
943         @Override
onReceive(Context context, Intent intent)944         public void onReceive(Context context, Intent intent) {
945             if (!intent.hasExtra(SCREENSHOT_URI_ID)) {
946                 return;
947             }
948 
949             // Clear the notification
950             final NotificationManager nm =
951                     (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
952             final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
953             nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
954 
955             // And delete the image from the media store
956             new DeleteImageInBackgroundTask(context).execute(uri);
957         }
958     }
959 }
960