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