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