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