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