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