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