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