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