Home | History | Annotate | Download | only in screenshot
      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 
    101     void clearImage() {
    102         image = null;
    103         imageUri = null;
    104         iconSize = 0;
    105     }
    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 
    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      */
    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
    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
    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
    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 
    411     DeleteImageInBackgroundTask(Context context) {
    412         mContext = context;
    413     }
    414 
    415     @Override
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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     }
    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     }
    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 
    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
    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
    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
    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