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