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