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