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