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