/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java

https://github.com/aizuzi/platform_frameworks_base · Java · 679 lines · 513 code · 72 blank · 94 comment · 23 complexity · cfa6ae06eeb56a3eb764f530cc385945 MD5 · raw file

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