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