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