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