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