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