GlobalScreenshot.java revision e69095869c1b324aa20991ed1805c492c3f731e8
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.media.MediaActionSound;
38import android.net.Uri;
39import android.os.AsyncTask;
40import android.os.Environment;
41import android.os.Process;
42import android.provider.MediaStore;
43import android.util.DisplayMetrics;
44import android.view.Display;
45import android.view.LayoutInflater;
46import android.view.MotionEvent;
47import android.view.Surface;
48import android.view.View;
49import android.view.ViewGroup;
50import android.view.WindowManager;
51import android.view.animation.Interpolator;
52import android.widget.ImageView;
53
54import com.android.systemui.R;
55
56import java.io.File;
57import java.io.OutputStream;
58import java.text.SimpleDateFormat;
59import java.util.Date;
60
61/**
62 * POD used in the AsyncTask which saves an image in the background.
63 */
64class 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 */
76class 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 */
224class 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 MediaActionSound 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 MediaActionSound();
313        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
314    }
315
316    /**
317     * Creates a new worker thread and saves the screenshot to the media store.
318     */
319    private void saveScreenshotInWorkerThread(Runnable finisher) {
320        SaveImageInBackgroundData data = new SaveImageInBackgroundData();
321        data.context = mContext;
322        data.image = mScreenBitmap;
323        data.iconSize = mNotificationIconSize;
324        data.finisher = finisher;
325        new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
326                SCREENSHOT_NOTIFICATION_ID).execute(data);
327    }
328
329    /**
330     * @return the current display rotation in degrees
331     */
332    private float getDegreesForRotation(int value) {
333        switch (value) {
334        case Surface.ROTATION_90:
335            return 360f - 90f;
336        case Surface.ROTATION_180:
337            return 360f - 180f;
338        case Surface.ROTATION_270:
339            return 360f - 270f;
340        }
341        return 0f;
342    }
343
344    /**
345     * Takes a screenshot of the current display and shows an animation.
346     */
347    void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
348        // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
349        // only in the natural orientation of the device :!)
350        mDisplay.getRealMetrics(mDisplayMetrics);
351        float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
352        float degrees = getDegreesForRotation(mDisplay.getRotation());
353        boolean requiresRotation = (degrees > 0);
354        if (requiresRotation) {
355            // Get the dimensions of the device in its native orientation
356            mDisplayMatrix.reset();
357            mDisplayMatrix.preRotate(-degrees);
358            mDisplayMatrix.mapPoints(dims);
359            dims[0] = Math.abs(dims[0]);
360            dims[1] = Math.abs(dims[1]);
361        }
362
363        // Take the screenshot
364        mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
365        if (mScreenBitmap == null) {
366            notifyScreenshotError(mContext, mNotificationManager);
367            finisher.run();
368            return;
369        }
370
371        if (requiresRotation) {
372            // Rotate the screenshot to the current orientation
373            Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
374                    mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
375            Canvas c = new Canvas(ss);
376            c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
377            c.rotate(degrees);
378            c.translate(-dims[0] / 2, -dims[1] / 2);
379            c.drawBitmap(mScreenBitmap, 0, 0, null);
380            c.setBitmap(null);
381            mScreenBitmap = ss;
382        }
383
384        // Optimizations
385        mScreenBitmap.setHasAlpha(false);
386        mScreenBitmap.prepareToDraw();
387
388        // Start the post-screenshot animation
389        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
390                statusBarVisible, navBarVisible);
391    }
392
393
394    /**
395     * Starts the animation after taking the screenshot
396     */
397    private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
398            boolean navBarVisible) {
399        // Add the view for the animation
400        mScreenshotView.setImageBitmap(mScreenBitmap);
401        mScreenshotLayout.requestFocus();
402
403        // Setup the animation with the screenshot just taken
404        if (mScreenshotAnimation != null) {
405            mScreenshotAnimation.end();
406        }
407
408        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
409        ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
410        ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
411                statusBarVisible, navBarVisible);
412        mScreenshotAnimation = new AnimatorSet();
413        mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
414        mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
415            @Override
416            public void onAnimationEnd(Animator animation) {
417                // Save the screenshot once we have a bit of time now
418                saveScreenshotInWorkerThread(finisher);
419                mWindowManager.removeView(mScreenshotLayout);
420            }
421        });
422        mScreenshotLayout.post(new Runnable() {
423            @Override
424            public void run() {
425                // Play the shutter sound to notify that we've taken a screenshot
426                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
427
428                mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
429                mScreenshotView.buildLayer();
430                mScreenshotAnimation.start();
431            }
432        });
433    }
434    private ValueAnimator createScreenshotDropInAnimation() {
435        final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
436                / SCREENSHOT_DROP_IN_DURATION);
437        final float flashDurationPct = 2f * flashPeakDurationPct;
438        final Interpolator flashAlphaInterpolator = new Interpolator() {
439            @Override
440            public float getInterpolation(float x) {
441                // Flash the flash view in and out quickly
442                if (x <= flashDurationPct) {
443                    return (float) Math.sin(Math.PI * (x / flashDurationPct));
444                }
445                return 0;
446            }
447        };
448        final Interpolator scaleInterpolator = new Interpolator() {
449            @Override
450            public float getInterpolation(float x) {
451                // We start scaling when the flash is at it's peak
452                if (x < flashPeakDurationPct) {
453                    return 0;
454                }
455                return (x - flashDurationPct) / (1f - flashDurationPct);
456            }
457        };
458        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
459        anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
460        anim.addListener(new AnimatorListenerAdapter() {
461            @Override
462            public void onAnimationStart(Animator animation) {
463                mBackgroundView.setAlpha(0f);
464                mBackgroundView.setVisibility(View.VISIBLE);
465                mScreenshotView.setAlpha(0f);
466                mScreenshotView.setTranslationX(0f);
467                mScreenshotView.setTranslationY(0f);
468                mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
469                mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
470                mScreenshotView.setVisibility(View.VISIBLE);
471                mScreenshotFlash.setAlpha(0f);
472                mScreenshotFlash.setVisibility(View.VISIBLE);
473            }
474            @Override
475            public void onAnimationEnd(android.animation.Animator animation) {
476                mScreenshotFlash.setVisibility(View.GONE);
477            }
478        });
479        anim.addUpdateListener(new AnimatorUpdateListener() {
480            @Override
481            public void onAnimationUpdate(ValueAnimator animation) {
482                float t = (Float) animation.getAnimatedValue();
483                float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
484                    - scaleInterpolator.getInterpolation(t)
485                        * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
486                mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
487                mScreenshotView.setAlpha(t);
488                mScreenshotView.setScaleX(scaleT);
489                mScreenshotView.setScaleY(scaleT);
490                mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
491            }
492        });
493        return anim;
494    }
495    private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
496            boolean navBarVisible) {
497        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
498        anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
499        anim.addListener(new AnimatorListenerAdapter() {
500            @Override
501            public void onAnimationEnd(Animator animation) {
502                mBackgroundView.setVisibility(View.GONE);
503                mScreenshotView.setVisibility(View.GONE);
504                mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
505            }
506        });
507
508        if (!statusBarVisible || !navBarVisible) {
509            // There is no status bar/nav bar, so just fade the screenshot away in place
510            anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
511            anim.addUpdateListener(new AnimatorUpdateListener() {
512                @Override
513                public void onAnimationUpdate(ValueAnimator animation) {
514                    float t = (Float) animation.getAnimatedValue();
515                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
516                            - t * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
517                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
518                    mScreenshotView.setAlpha(1f - t);
519                    mScreenshotView.setScaleX(scaleT);
520                    mScreenshotView.setScaleY(scaleT);
521                }
522            });
523        } else {
524            // In the case where there is a status bar, animate to the origin of the bar (top-left)
525            final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
526                    / SCREENSHOT_DROP_OUT_DURATION;
527            final Interpolator scaleInterpolator = new Interpolator() {
528                @Override
529                public float getInterpolation(float x) {
530                    if (x < scaleDurationPct) {
531                        // Decelerate, and scale the input accordingly
532                        return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
533                    }
534                    return 1f;
535                }
536            };
537
538            // Determine the bounds of how to scale
539            float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
540            float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
541            final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
542            final PointF finalPos = new PointF(
543                -halfScreenWidth + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
544                -halfScreenHeight + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
545
546            // Animate the screenshot to the status bar
547            anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
548            anim.addUpdateListener(new AnimatorUpdateListener() {
549                @Override
550                public void onAnimationUpdate(ValueAnimator animation) {
551                    float t = (Float) animation.getAnimatedValue();
552                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
553                        - scaleInterpolator.getInterpolation(t)
554                            * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
555                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
556                    mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
557                    mScreenshotView.setScaleX(scaleT);
558                    mScreenshotView.setScaleY(scaleT);
559                    mScreenshotView.setTranslationX(t * finalPos.x);
560                    mScreenshotView.setTranslationY(t * finalPos.y);
561                }
562            });
563        }
564        return anim;
565    }
566
567    static void notifyScreenshotError(Context context, NotificationManager nManager) {
568        Resources r = context.getResources();
569
570        // Clear all existing notification, compose the new notification and show it
571        Notification n = new Notification.Builder(context)
572            .setTicker(r.getString(R.string.screenshot_failed_title))
573            .setContentTitle(r.getString(R.string.screenshot_failed_title))
574            .setContentText(r.getString(R.string.screenshot_failed_text))
575            .setSmallIcon(R.drawable.stat_notify_image_error)
576            .setWhen(System.currentTimeMillis())
577            .setAutoCancel(true)
578            .getNotification();
579        nManager.notify(SCREENSHOT_NOTIFICATION_ID, n);
580    }
581}
582