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