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