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