blob: 119299f8d0820d66486c23aee5fb751d4d6b2883 [file] [log] [blame]
Winson Chung9112ec32011-06-27 13:15:32 -07001/*
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;
Winson Chung9112ec32011-06-27 13:15:32 -070022import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
Winson Chungc57ccf02011-10-13 15:04:59 -070024import android.app.Notification;
Chris Wren3745a3d2012-05-22 15:11:52 -040025import android.app.Notification.BigPictureStyle;
Winson Chungc57ccf02011-10-13 15:04:59 -070026import android.app.NotificationManager;
27import android.app.PendingIntent;
Mike Lockwood98377342011-07-26 13:55:16 -040028import android.content.ContentResolver;
Winson Chung9112ec32011-06-27 13:15:32 -070029import android.content.ContentValues;
30import android.content.Context;
Winson Chungc57ccf02011-10-13 15:04:59 -070031import android.content.Intent;
32import android.content.res.Resources;
Winson Chung9112ec32011-06-27 13:15:32 -070033import android.graphics.Bitmap;
34import android.graphics.Canvas;
Chris Wren3745a3d2012-05-22 15:11:52 -040035import android.graphics.ColorMatrix;
36import android.graphics.ColorMatrixColorFilter;
Winson Chung9112ec32011-06-27 13:15:32 -070037import android.graphics.Matrix;
Chris Wren3745a3d2012-05-22 15:11:52 -040038import android.graphics.Paint;
Winson Chung9112ec32011-06-27 13:15:32 -070039import android.graphics.PixelFormat;
Winson Chunga63bb842011-10-17 10:26:28 -070040import android.graphics.PointF;
Eino-Ville Talvalae6909582012-03-01 11:01:38 -080041import android.media.MediaActionSound;
Winson Chung9112ec32011-06-27 13:15:32 -070042import android.net.Uri;
43import android.os.AsyncTask;
Winson Chung9112ec32011-06-27 13:15:32 -070044import android.os.Environment;
Winson Chung36c9e292011-10-18 13:48:38 -070045import android.os.Process;
Winson Chung9112ec32011-06-27 13:15:32 -070046import android.provider.MediaStore;
47import android.util.DisplayMetrics;
Winson Chung9112ec32011-06-27 13:15:32 -070048import android.view.Display;
Winson Chung9112ec32011-06-27 13:15:32 -070049import android.view.LayoutInflater;
50import android.view.MotionEvent;
51import android.view.Surface;
Mathias Agopian3866f0d2013-02-11 22:08:48 -080052import android.view.SurfaceControl;
Winson Chung9112ec32011-06-27 13:15:32 -070053import android.view.View;
54import android.view.ViewGroup;
55import android.view.WindowManager;
Winson Chung22ca0952011-10-20 19:44:32 -070056import android.view.animation.Interpolator;
Winson Chung9112ec32011-06-27 13:15:32 -070057import android.widget.ImageView;
Winson Chung8d513ea2011-12-01 14:39:12 -080058
Winson Chung9112ec32011-06-27 13:15:32 -070059import com.android.systemui.R;
60
61import java.io.File;
Winson Chung9112ec32011-06-27 13:15:32 -070062import java.io.OutputStream;
Winson Chung9112ec32011-06-27 13:15:32 -070063import java.text.SimpleDateFormat;
64import java.util.Date;
65
66/**
67 * POD used in the AsyncTask which saves an image in the background.
68 */
69class SaveImageInBackgroundData {
70 Context context;
71 Bitmap image;
Winson Chung36c9e292011-10-18 13:48:38 -070072 Uri imageUri;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070073 Runnable finisher;
Winson Chunga63bb842011-10-17 10:26:28 -070074 int iconSize;
Winson Chung9112ec32011-06-27 13:15:32 -070075 int result;
Winson Chung0e6232c2013-01-22 14:11:46 -080076
77 void clearImage() {
Winson Chung0e6232c2013-01-22 14:11:46 -080078 image = null;
79 imageUri = null;
80 iconSize = 0;
81 }
Winson Chung3a5a7742013-04-01 16:04:28 -070082 void clearContext() {
83 context = null;
84 }
Winson Chung9112ec32011-06-27 13:15:32 -070085}
86
87/**
88 * An AsyncTask that saves an image to the media store in the background.
89 */
90class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void,
91 SaveImageInBackgroundData> {
Winson Chung5cc9a312013-02-11 14:58:45 -080092 private static final String TAG = "SaveImageInBackgroundTask";
93
Winson Chung9112ec32011-06-27 13:15:32 -070094 private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
Winson Chung753e40b2011-07-25 17:10:21 -070095 private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
Winson Chung224848f2012-07-18 16:47:51 -070096 private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
Winson Chung9112ec32011-06-27 13:15:32 -070097
Jeff Sharkey80b54de2013-01-16 15:07:37 -080098 private final int mNotificationId;
99 private final NotificationManager mNotificationManager;
100 private final Notification.Builder mNotificationBuilder;
101 private final File mScreenshotDir;
102 private final String mImageFileName;
103 private final String mImageFilePath;
104 private final long mImageTime;
105 private final BigPictureStyle mNotificationStyle;
106 private final int mImageWidth;
107 private final int mImageHeight;
Winson Chungc57ccf02011-10-13 15:04:59 -0700108
Winson Chunga63bb842011-10-17 10:26:28 -0700109 // WORKAROUND: We want the same notification across screenshots that we update so that we don't
110 // spam a user's notification drawer. However, we only show the ticker for the saving state
111 // and if the ticker text is the same as the previous notification, then it will not show. So
112 // for now, we just add and remove a space from the ticker text to trigger the animation when
113 // necessary.
114 private static boolean mTickerAddSpace;
115
116 SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
117 NotificationManager nManager, int nId) {
Winson Chungc57ccf02011-10-13 15:04:59 -0700118 Resources r = context.getResources();
119
120 // Prepare all the output metadata
121 mImageTime = System.currentTimeMillis();
Romain Guy8279acb2011-11-29 13:56:25 -0800122 String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
Romain Guy8279acb2011-11-29 13:56:25 -0800123 mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
Jeff Sharkey149e02c2013-01-16 14:38:49 -0800124
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800125 mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
Jeff Sharkey149e02c2013-01-16 14:38:49 -0800126 Environment.DIRECTORY_PICTURES), SCREENSHOTS_DIR_NAME);
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800127 mImageFilePath = new File(mScreenshotDir, mImageFileName).getAbsolutePath();
Winson Chungc57ccf02011-10-13 15:04:59 -0700128
Winson Chunga63bb842011-10-17 10:26:28 -0700129 // Create the large notification icon
Doris Liu5aa011b2012-11-06 16:29:46 -0800130 mImageWidth = data.image.getWidth();
131 mImageHeight = data.image.getHeight();
Chris Wren3745a3d2012-05-22 15:11:52 -0400132 int iconSize = data.iconSize;
133
Doris Liu5aa011b2012-11-06 16:29:46 -0800134 final int shortSide = mImageWidth < mImageHeight ? mImageWidth : mImageHeight;
Chris Wren3745a3d2012-05-22 15:11:52 -0400135 Bitmap preview = Bitmap.createBitmap(shortSide, shortSide, data.image.getConfig());
136 Canvas c = new Canvas(preview);
137 Paint paint = new Paint();
138 ColorMatrix desat = new ColorMatrix();
139 desat.setSaturation(0.25f);
140 paint.setColorFilter(new ColorMatrixColorFilter(desat));
141 Matrix matrix = new Matrix();
Doris Liu5aa011b2012-11-06 16:29:46 -0800142 matrix.postTranslate((shortSide - mImageWidth) / 2,
143 (shortSide - mImageHeight) / 2);
Chris Wren3745a3d2012-05-22 15:11:52 -0400144 c.drawBitmap(data.image, matrix, paint);
145 c.drawColor(0x40FFFFFF);
Winson Chung5cc9a312013-02-11 14:58:45 -0800146 c.setBitmap(null);
Chris Wren3745a3d2012-05-22 15:11:52 -0400147
148 Bitmap croppedIcon = Bitmap.createScaledBitmap(preview, iconSize, iconSize, true);
Winson Chunga63bb842011-10-17 10:26:28 -0700149
Winson Chungc57ccf02011-10-13 15:04:59 -0700150 // Show the intermediate notification
Winson Chunga63bb842011-10-17 10:26:28 -0700151 mTickerAddSpace = !mTickerAddSpace;
Winson Chungc57ccf02011-10-13 15:04:59 -0700152 mNotificationId = nId;
Winson Chung9c99aee2011-11-14 14:12:54 -0800153 mNotificationManager = nManager;
Winson Chungc57ccf02011-10-13 15:04:59 -0700154 mNotificationBuilder = new Notification.Builder(context)
Winson Chunga63bb842011-10-17 10:26:28 -0700155 .setTicker(r.getString(R.string.screenshot_saving_ticker)
156 + (mTickerAddSpace ? " " : ""))
Winson Chungc57ccf02011-10-13 15:04:59 -0700157 .setContentTitle(r.getString(R.string.screenshot_saving_title))
158 .setContentText(r.getString(R.string.screenshot_saving_text))
Winson Chungb787a752011-10-19 18:31:53 -0700159 .setSmallIcon(R.drawable.stat_notify_image)
Winson Chungc57ccf02011-10-13 15:04:59 -0700160 .setWhen(System.currentTimeMillis());
Chris Wren3745a3d2012-05-22 15:11:52 -0400161
162 mNotificationStyle = new Notification.BigPictureStyle()
163 .bigPicture(preview);
164 mNotificationBuilder.setStyle(mNotificationStyle);
165
166 Notification n = mNotificationBuilder.build();
Winson Chungc57ccf02011-10-13 15:04:59 -0700167 n.flags |= Notification.FLAG_NO_CLEAR;
Winson Chungc57ccf02011-10-13 15:04:59 -0700168 mNotificationManager.notify(nId, n);
Winson Chung9c99aee2011-11-14 14:12:54 -0800169
170 // On the tablet, the large icon makes the notification appear as if it is clickable (and
171 // on small devices, the large icon is not shown) so defer showing the large icon until
172 // we compose the final post-save notification below.
173 mNotificationBuilder.setLargeIcon(croppedIcon);
Chris Wren3745a3d2012-05-22 15:11:52 -0400174 // But we still don't set it for the expanded view, allowing the smallIcon to show here.
175 mNotificationStyle.bigLargeIcon(null);
Winson Chungc57ccf02011-10-13 15:04:59 -0700176 }
177
Winson Chung9112ec32011-06-27 13:15:32 -0700178 @Override
179 protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) {
180 if (params.length != 1) return null;
Winson Chung0e6232c2013-01-22 14:11:46 -0800181 if (isCancelled()) {
182 params[0].clearImage();
Winson Chung3a5a7742013-04-01 16:04:28 -0700183 params[0].clearContext();
Winson Chung0e6232c2013-01-22 14:11:46 -0800184 return null;
185 }
Winson Chung9112ec32011-06-27 13:15:32 -0700186
Winson Chung36c9e292011-10-18 13:48:38 -0700187 // By default, AsyncTask sets the worker thread to have background thread priority, so bump
188 // it back up so that we save a little quicker.
189 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
190
Winson Chung9112ec32011-06-27 13:15:32 -0700191 Context context = params[0].context;
192 Bitmap image = params[0].image;
Chris Wren3745a3d2012-05-22 15:11:52 -0400193 Resources r = context.getResources();
Winson Chung9112ec32011-06-27 13:15:32 -0700194
Winson Chungd859fa32011-07-22 12:19:52 -0700195 try {
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800196 // Create screenshot directory if it doesn't exist
197 mScreenshotDir.mkdirs();
198
Mike Lockwoood47611242013-05-02 09:24:37 -0700199 // media provider uses seconds, not milliseconds
200 long dateSeconds = mImageTime / 1000;
201
Winson Chung9112ec32011-06-27 13:15:32 -0700202 // Save the screenshot to the MediaStore
203 ContentValues values = new ContentValues();
Mike Lockwood98377342011-07-26 13:55:16 -0400204 ContentResolver resolver = context.getContentResolver();
Winson Chungc57ccf02011-10-13 15:04:59 -0700205 values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
206 values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
207 values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
Mike Lockwoood47611242013-05-02 09:24:37 -0700208 values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, dateSeconds);
209 values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
210 values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
Winson Chung9112ec32011-06-27 13:15:32 -0700211 values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
Doris Liu5aa011b2012-11-06 16:29:46 -0800212 values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
213 values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
Mike Lockwood98377342011-07-26 13:55:16 -0400214 Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
Winson Chung9112ec32011-06-27 13:15:32 -0700215
Winson Chung224848f2012-07-18 16:47:51 -0700216 String subjectDate = new SimpleDateFormat("hh:mma, MMM dd, yyyy")
217 .format(new Date(mImageTime));
218 String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
Chris Wren3745a3d2012-05-22 15:11:52 -0400219 Intent sharingIntent = new Intent(Intent.ACTION_SEND);
220 sharingIntent.setType("image/png");
221 sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
Winson Chung224848f2012-07-18 16:47:51 -0700222 sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
Daniel Sandler046fddf2012-05-31 15:40:25 -0400223
224 Intent chooserIntent = Intent.createChooser(sharingIntent, null);
John Spurlock209bede2013-07-17 12:23:27 -0400225 chooserIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
Daniel Sandler046fddf2012-05-31 15:40:25 -0400226 | Intent.FLAG_ACTIVITY_NEW_TASK);
227
Chris Wren3745a3d2012-05-22 15:11:52 -0400228 mNotificationBuilder.addAction(R.drawable.ic_menu_share,
229 r.getString(com.android.internal.R.string.share),
John Spurlock209bede2013-07-17 12:23:27 -0400230 PendingIntent.getActivity(context, 0, chooserIntent,
Daniel Sandler046fddf2012-05-31 15:40:25 -0400231 PendingIntent.FLAG_CANCEL_CURRENT));
Chris Wren3745a3d2012-05-22 15:11:52 -0400232
Mike Lockwood98377342011-07-26 13:55:16 -0400233 OutputStream out = resolver.openOutputStream(uri);
Winson Chung9112ec32011-06-27 13:15:32 -0700234 image.compress(Bitmap.CompressFormat.PNG, 100, out);
235 out.flush();
236 out.close();
237
Mike Lockwood98377342011-07-26 13:55:16 -0400238 // update file size in the database
239 values.clear();
Winson Chungc57ccf02011-10-13 15:04:59 -0700240 values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
Mike Lockwood98377342011-07-26 13:55:16 -0400241 resolver.update(uri, values, null, null);
242
Winson Chung36c9e292011-10-18 13:48:38 -0700243 params[0].imageUri = uri;
Winson Chungcd6a9ef2013-01-16 14:59:18 -0800244 params[0].image = null;
Winson Chung9112ec32011-06-27 13:15:32 -0700245 params[0].result = 0;
Winson Chungd859fa32011-07-22 12:19:52 -0700246 } catch (Exception e) {
247 // IOException/UnsupportedOperationException may be thrown if external storage is not
248 // mounted
Winson Chung0e6232c2013-01-22 14:11:46 -0800249 params[0].clearImage();
Winson Chung9112ec32011-06-27 13:15:32 -0700250 params[0].result = 1;
251 }
252
Winson Chungcd6a9ef2013-01-16 14:59:18 -0800253 // Recycle the bitmap data
254 if (image != null) {
255 image.recycle();
256 }
257
Winson Chung9112ec32011-06-27 13:15:32 -0700258 return params[0];
Romain Guy8279acb2011-11-29 13:56:25 -0800259 }
Winson Chung9112ec32011-06-27 13:15:32 -0700260
261 @Override
262 protected void onPostExecute(SaveImageInBackgroundData params) {
Winson Chung0e6232c2013-01-22 14:11:46 -0800263 if (isCancelled()) {
264 params.finisher.run();
265 params.clearImage();
Winson Chung3a5a7742013-04-01 16:04:28 -0700266 params.clearContext();
Winson Chung0e6232c2013-01-22 14:11:46 -0800267 return;
268 }
269
Winson Chung9112ec32011-06-27 13:15:32 -0700270 if (params.result > 0) {
271 // Show a message that we've failed to save the image to disk
Winson Chungc57ccf02011-10-13 15:04:59 -0700272 GlobalScreenshot.notifyScreenshotError(params.context, mNotificationManager);
Winson Chung9112ec32011-06-27 13:15:32 -0700273 } else {
Winson Chungc57ccf02011-10-13 15:04:59 -0700274 // Show the final notification to indicate screenshot saved
275 Resources r = params.context.getResources();
276
Winson Chung36c9e292011-10-18 13:48:38 -0700277 // Create the intent to show the screenshot in gallery
Romain Guy8279acb2011-11-29 13:56:25 -0800278 Intent launchIntent = new Intent(Intent.ACTION_VIEW);
279 launchIntent.setDataAndType(params.imageUri, "image/png");
280 launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Winson Chung36c9e292011-10-18 13:48:38 -0700281
Winson Chungc57ccf02011-10-13 15:04:59 -0700282 mNotificationBuilder
Winson Chungc57ccf02011-10-13 15:04:59 -0700283 .setContentTitle(r.getString(R.string.screenshot_saved_title))
284 .setContentText(r.getString(R.string.screenshot_saved_text))
Romain Guy8279acb2011-11-29 13:56:25 -0800285 .setContentIntent(PendingIntent.getActivity(params.context, 0, launchIntent, 0))
Winson Chungc57ccf02011-10-13 15:04:59 -0700286 .setWhen(System.currentTimeMillis())
287 .setAutoCancel(true);
288
Chris Wren3745a3d2012-05-22 15:11:52 -0400289 Notification n = mNotificationBuilder.build();
Winson Chungc57ccf02011-10-13 15:04:59 -0700290 n.flags &= ~Notification.FLAG_NO_CLEAR;
291 mNotificationManager.notify(mNotificationId, n);
Winson Chung9112ec32011-06-27 13:15:32 -0700292 }
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700293 params.finisher.run();
Winson Chung3a5a7742013-04-01 16:04:28 -0700294 params.clearContext();
Romain Guy8279acb2011-11-29 13:56:25 -0800295 }
Winson Chung9112ec32011-06-27 13:15:32 -0700296}
297
298/**
299 * TODO:
300 * - Performance when over gl surfaces? Ie. Gallery
301 * - what do we say in the Toast? Which icon do we get if the user uses another
302 * type of gallery?
303 */
304class GlobalScreenshot {
Winson Chung5cc9a312013-02-11 14:58:45 -0800305 private static final String TAG = "GlobalScreenshot";
306
Winson Chungc57ccf02011-10-13 15:04:59 -0700307 private static final int SCREENSHOT_NOTIFICATION_ID = 789;
Winson Chung22ca0952011-10-20 19:44:32 -0700308 private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
309 private static final int SCREENSHOT_DROP_IN_DURATION = 430;
310 private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
311 private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
312 private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
313 private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
314 private static final float BACKGROUND_ALPHA = 0.5f;
315 private static final float SCREENSHOT_SCALE = 1f;
316 private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
317 private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
318 private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
319 private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
Winson Chung9112ec32011-06-27 13:15:32 -0700320
321 private Context mContext;
Winson Chung9112ec32011-06-27 13:15:32 -0700322 private WindowManager mWindowManager;
323 private WindowManager.LayoutParams mWindowLayoutParams;
Winson Chungc57ccf02011-10-13 15:04:59 -0700324 private NotificationManager mNotificationManager;
Winson Chung9112ec32011-06-27 13:15:32 -0700325 private Display mDisplay;
326 private DisplayMetrics mDisplayMetrics;
327 private Matrix mDisplayMatrix;
328
329 private Bitmap mScreenBitmap;
330 private View mScreenshotLayout;
331 private ImageView mBackgroundView;
Winson Chung9112ec32011-06-27 13:15:32 -0700332 private ImageView mScreenshotView;
Winson Chung22ca0952011-10-20 19:44:32 -0700333 private ImageView mScreenshotFlash;
Winson Chung9112ec32011-06-27 13:15:32 -0700334
335 private AnimatorSet mScreenshotAnimation;
336
Winson Chunga63bb842011-10-17 10:26:28 -0700337 private int mNotificationIconSize;
Winson Chunga63bb842011-10-17 10:26:28 -0700338 private float mBgPadding;
339 private float mBgPaddingScale;
340
Winson Chung0e6232c2013-01-22 14:11:46 -0800341 private AsyncTask<SaveImageInBackgroundData, Void, SaveImageInBackgroundData> mSaveInBgTask;
342
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800343 private MediaActionSound mCameraSound;
Winson Chung8d513ea2011-12-01 14:39:12 -0800344
Winson Chung9112ec32011-06-27 13:15:32 -0700345
346 /**
347 * @param context everything needs a context :(
348 */
349 public GlobalScreenshot(Context context) {
Winson Chunga63bb842011-10-17 10:26:28 -0700350 Resources r = context.getResources();
Winson Chung9112ec32011-06-27 13:15:32 -0700351 mContext = context;
Romain Guy8279acb2011-11-29 13:56:25 -0800352 LayoutInflater layoutInflater = (LayoutInflater)
Winson Chung9112ec32011-06-27 13:15:32 -0700353 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
354
355 // Inflate the screenshot layout
Winson Chung9112ec32011-06-27 13:15:32 -0700356 mDisplayMatrix = new Matrix();
Romain Guy8279acb2011-11-29 13:56:25 -0800357 mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
Winson Chung9112ec32011-06-27 13:15:32 -0700358 mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
Winson Chung9112ec32011-06-27 13:15:32 -0700359 mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
Winson Chung22ca0952011-10-20 19:44:32 -0700360 mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
Winson Chung9112ec32011-06-27 13:15:32 -0700361 mScreenshotLayout.setFocusable(true);
362 mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
363 @Override
364 public boolean onTouch(View v, MotionEvent event) {
365 // Intercept and ignore all touch events
366 return true;
367 }
368 });
369
370 // Setup the window that we are going to use
Winson Chung9112ec32011-06-27 13:15:32 -0700371 mWindowLayoutParams = new WindowManager.LayoutParams(
372 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
373 WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
374 WindowManager.LayoutParams.FLAG_FULLSCREEN
375 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
Winson Chung9112ec32011-06-27 13:15:32 -0700376 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
377 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
378 PixelFormat.TRANSLUCENT);
Winson Chung9112ec32011-06-27 13:15:32 -0700379 mWindowLayoutParams.setTitle("ScreenshotAnimation");
380 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Winson Chungc57ccf02011-10-13 15:04:59 -0700381 mNotificationManager =
382 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Winson Chung9112ec32011-06-27 13:15:32 -0700383 mDisplay = mWindowManager.getDefaultDisplay();
Winson Chunga63bb842011-10-17 10:26:28 -0700384 mDisplayMetrics = new DisplayMetrics();
385 mDisplay.getRealMetrics(mDisplayMetrics);
386
387 // Get the various target sizes
Winson Chunga63bb842011-10-17 10:26:28 -0700388 mNotificationIconSize =
389 r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
Winson Chunga63bb842011-10-17 10:26:28 -0700390
391 // Scale has to account for both sides of the bg
392 mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
Winson Chung22ca0952011-10-20 19:44:32 -0700393 mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
Winson Chung8d513ea2011-12-01 14:39:12 -0800394
395 // Setup the Camera shutter sound
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800396 mCameraSound = new MediaActionSound();
397 mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
Winson Chung9112ec32011-06-27 13:15:32 -0700398 }
399
400 /**
401 * Creates a new worker thread and saves the screenshot to the media store.
402 */
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700403 private void saveScreenshotInWorkerThread(Runnable finisher) {
Winson Chung9112ec32011-06-27 13:15:32 -0700404 SaveImageInBackgroundData data = new SaveImageInBackgroundData();
405 data.context = mContext;
406 data.image = mScreenBitmap;
Winson Chunga63bb842011-10-17 10:26:28 -0700407 data.iconSize = mNotificationIconSize;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700408 data.finisher = finisher;
Winson Chung0e6232c2013-01-22 14:11:46 -0800409 if (mSaveInBgTask != null) {
410 mSaveInBgTask.cancel(false);
411 }
412 mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
Winson Chunga63bb842011-10-17 10:26:28 -0700413 SCREENSHOT_NOTIFICATION_ID).execute(data);
Winson Chung9112ec32011-06-27 13:15:32 -0700414 }
415
416 /**
417 * @return the current display rotation in degrees
418 */
419 private float getDegreesForRotation(int value) {
420 switch (value) {
421 case Surface.ROTATION_90:
Winson Chunga63bb842011-10-17 10:26:28 -0700422 return 360f - 90f;
Winson Chung9112ec32011-06-27 13:15:32 -0700423 case Surface.ROTATION_180:
Winson Chunga63bb842011-10-17 10:26:28 -0700424 return 360f - 180f;
Winson Chung9112ec32011-06-27 13:15:32 -0700425 case Surface.ROTATION_270:
Winson Chunga63bb842011-10-17 10:26:28 -0700426 return 360f - 270f;
Winson Chung9112ec32011-06-27 13:15:32 -0700427 }
428 return 0f;
429 }
430
431 /**
432 * Takes a screenshot of the current display and shows an animation.
433 */
Winson Chunga63bb842011-10-17 10:26:28 -0700434 void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
Winson Chung9112ec32011-06-27 13:15:32 -0700435 // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
436 // only in the natural orientation of the device :!)
437 mDisplay.getRealMetrics(mDisplayMetrics);
438 float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
439 float degrees = getDegreesForRotation(mDisplay.getRotation());
440 boolean requiresRotation = (degrees > 0);
441 if (requiresRotation) {
442 // Get the dimensions of the device in its native orientation
443 mDisplayMatrix.reset();
444 mDisplayMatrix.preRotate(-degrees);
445 mDisplayMatrix.mapPoints(dims);
446 dims[0] = Math.abs(dims[0]);
447 dims[1] = Math.abs(dims[1]);
448 }
Winson Chunga46d7782012-01-04 16:43:10 -0800449
450 // Take the screenshot
Mathias Agopian3866f0d2013-02-11 22:08:48 -0800451 mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
Winson Chunga46d7782012-01-04 16:43:10 -0800452 if (mScreenBitmap == null) {
453 notifyScreenshotError(mContext, mNotificationManager);
454 finisher.run();
455 return;
456 }
457
Winson Chung9112ec32011-06-27 13:15:32 -0700458 if (requiresRotation) {
459 // Rotate the screenshot to the current orientation
460 Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
461 mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
462 Canvas c = new Canvas(ss);
463 c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
Winson Chunga63bb842011-10-17 10:26:28 -0700464 c.rotate(degrees);
Winson Chung9112ec32011-06-27 13:15:32 -0700465 c.translate(-dims[0] / 2, -dims[1] / 2);
466 c.drawBitmap(mScreenBitmap, 0, 0, null);
Dianne Hackborn6311d0a2011-08-02 16:37:58 -0700467 c.setBitmap(null);
Winson Chung5cc9a312013-02-11 14:58:45 -0800468 // Recycle the previous bitmap
469 mScreenBitmap.recycle();
Winson Chung9112ec32011-06-27 13:15:32 -0700470 mScreenBitmap = ss;
471 }
472
Winson Chunga63bb842011-10-17 10:26:28 -0700473 // Optimizations
474 mScreenBitmap.setHasAlpha(false);
475 mScreenBitmap.prepareToDraw();
476
Winson Chung9112ec32011-06-27 13:15:32 -0700477 // Start the post-screenshot animation
Winson Chunga63bb842011-10-17 10:26:28 -0700478 startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
479 statusBarVisible, navBarVisible);
Winson Chung9112ec32011-06-27 13:15:32 -0700480 }
481
482
483 /**
484 * Starts the animation after taking the screenshot
485 */
Winson Chunga63bb842011-10-17 10:26:28 -0700486 private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
487 boolean navBarVisible) {
Winson Chung9112ec32011-06-27 13:15:32 -0700488 // Add the view for the animation
489 mScreenshotView.setImageBitmap(mScreenBitmap);
490 mScreenshotLayout.requestFocus();
491
492 // Setup the animation with the screenshot just taken
493 if (mScreenshotAnimation != null) {
494 mScreenshotAnimation.end();
Winson Chung0e6232c2013-01-22 14:11:46 -0800495 mScreenshotAnimation.removeAllListeners();
Winson Chung9112ec32011-06-27 13:15:32 -0700496 }
497
498 mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
Winson Chung22ca0952011-10-20 19:44:32 -0700499 ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
500 ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
Winson Chunga63bb842011-10-17 10:26:28 -0700501 statusBarVisible, navBarVisible);
Winson Chung9112ec32011-06-27 13:15:32 -0700502 mScreenshotAnimation = new AnimatorSet();
Winson Chung22ca0952011-10-20 19:44:32 -0700503 mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
Winson Chung9112ec32011-06-27 13:15:32 -0700504 mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
505 @Override
506 public void onAnimationEnd(Animator animation) {
507 // Save the screenshot once we have a bit of time now
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700508 saveScreenshotInWorkerThread(finisher);
Winson Chung9112ec32011-06-27 13:15:32 -0700509 mWindowManager.removeView(mScreenshotLayout);
Winson Chungcd6a9ef2013-01-16 14:59:18 -0800510
511 // Clear any references to the bitmap
512 mScreenBitmap = null;
513 mScreenshotView.setImageBitmap(null);
Winson Chung9112ec32011-06-27 13:15:32 -0700514 }
515 });
Winson Chunga63bb842011-10-17 10:26:28 -0700516 mScreenshotLayout.post(new Runnable() {
517 @Override
518 public void run() {
Winson Chung8d513ea2011-12-01 14:39:12 -0800519 // Play the shutter sound to notify that we've taken a screenshot
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800520 mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
Winson Chung8d513ea2011-12-01 14:39:12 -0800521
Romain Guy8279acb2011-11-29 13:56:25 -0800522 mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
523 mScreenshotView.buildLayer();
Winson Chunga63bb842011-10-17 10:26:28 -0700524 mScreenshotAnimation.start();
525 }
526 });
Winson Chung9112ec32011-06-27 13:15:32 -0700527 }
Winson Chung22ca0952011-10-20 19:44:32 -0700528 private ValueAnimator createScreenshotDropInAnimation() {
529 final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
530 / SCREENSHOT_DROP_IN_DURATION);
531 final float flashDurationPct = 2f * flashPeakDurationPct;
532 final Interpolator flashAlphaInterpolator = new Interpolator() {
533 @Override
534 public float getInterpolation(float x) {
535 // Flash the flash view in and out quickly
536 if (x <= flashDurationPct) {
537 return (float) Math.sin(Math.PI * (x / flashDurationPct));
538 }
539 return 0;
540 }
541 };
542 final Interpolator scaleInterpolator = new Interpolator() {
543 @Override
544 public float getInterpolation(float x) {
545 // We start scaling when the flash is at it's peak
546 if (x < flashPeakDurationPct) {
547 return 0;
548 }
549 return (x - flashDurationPct) / (1f - flashDurationPct);
550 }
551 };
Winson Chung9112ec32011-06-27 13:15:32 -0700552 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
Winson Chung22ca0952011-10-20 19:44:32 -0700553 anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
Winson Chung9112ec32011-06-27 13:15:32 -0700554 anim.addListener(new AnimatorListenerAdapter() {
555 @Override
556 public void onAnimationStart(Animator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800557 mBackgroundView.setAlpha(0f);
Winson Chung9112ec32011-06-27 13:15:32 -0700558 mBackgroundView.setVisibility(View.VISIBLE);
Romain Guy8279acb2011-11-29 13:56:25 -0800559 mScreenshotView.setAlpha(0f);
560 mScreenshotView.setTranslationX(0f);
561 mScreenshotView.setTranslationY(0f);
562 mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
563 mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
564 mScreenshotView.setVisibility(View.VISIBLE);
565 mScreenshotFlash.setAlpha(0f);
Winson Chung22ca0952011-10-20 19:44:32 -0700566 mScreenshotFlash.setVisibility(View.VISIBLE);
567 }
568 @Override
569 public void onAnimationEnd(android.animation.Animator animation) {
570 mScreenshotFlash.setVisibility(View.GONE);
Winson Chung9112ec32011-06-27 13:15:32 -0700571 }
572 });
573 anim.addUpdateListener(new AnimatorUpdateListener() {
574 @Override
575 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800576 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700577 float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
Romain Guy8279acb2011-11-29 13:56:25 -0800578 - scaleInterpolator.getInterpolation(t)
Winson Chung22ca0952011-10-20 19:44:32 -0700579 * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
Romain Guy8279acb2011-11-29 13:56:25 -0800580 mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
581 mScreenshotView.setAlpha(t);
582 mScreenshotView.setScaleX(scaleT);
583 mScreenshotView.setScaleY(scaleT);
584 mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
Winson Chung9112ec32011-06-27 13:15:32 -0700585 }
586 });
587 return anim;
588 }
Winson Chung22ca0952011-10-20 19:44:32 -0700589 private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
Winson Chunga63bb842011-10-17 10:26:28 -0700590 boolean navBarVisible) {
591 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
Winson Chung22ca0952011-10-20 19:44:32 -0700592 anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
Winson Chung9112ec32011-06-27 13:15:32 -0700593 anim.addListener(new AnimatorListenerAdapter() {
594 @Override
595 public void onAnimationEnd(Animator animation) {
596 mBackgroundView.setVisibility(View.GONE);
Romain Guy8279acb2011-11-29 13:56:25 -0800597 mScreenshotView.setVisibility(View.GONE);
598 mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
Winson Chung9112ec32011-06-27 13:15:32 -0700599 }
600 });
Winson Chunga63bb842011-10-17 10:26:28 -0700601
602 if (!statusBarVisible || !navBarVisible) {
603 // There is no status bar/nav bar, so just fade the screenshot away in place
Winson Chung22ca0952011-10-20 19:44:32 -0700604 anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
Winson Chunga63bb842011-10-17 10:26:28 -0700605 anim.addUpdateListener(new AnimatorUpdateListener() {
606 @Override
607 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800608 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700609 float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
Romain Guy8279acb2011-11-29 13:56:25 -0800610 - t * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
611 mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
612 mScreenshotView.setAlpha(1f - t);
613 mScreenshotView.setScaleX(scaleT);
614 mScreenshotView.setScaleY(scaleT);
Winson Chunga63bb842011-10-17 10:26:28 -0700615 }
616 });
617 } else {
Winson Chung22ca0952011-10-20 19:44:32 -0700618 // In the case where there is a status bar, animate to the origin of the bar (top-left)
619 final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
620 / SCREENSHOT_DROP_OUT_DURATION;
621 final Interpolator scaleInterpolator = new Interpolator() {
622 @Override
623 public float getInterpolation(float x) {
624 if (x < scaleDurationPct) {
625 // Decelerate, and scale the input accordingly
626 return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
627 }
628 return 1f;
629 }
630 };
631
Winson Chunga63bb842011-10-17 10:26:28 -0700632 // Determine the bounds of how to scale
633 float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
634 float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
Winson Chung22ca0952011-10-20 19:44:32 -0700635 final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
636 final PointF finalPos = new PointF(
637 -halfScreenWidth + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
638 -halfScreenHeight + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
Winson Chunga63bb842011-10-17 10:26:28 -0700639
640 // Animate the screenshot to the status bar
Winson Chung22ca0952011-10-20 19:44:32 -0700641 anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
Winson Chunga63bb842011-10-17 10:26:28 -0700642 anim.addUpdateListener(new AnimatorUpdateListener() {
643 @Override
644 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800645 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700646 float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
Romain Guy8279acb2011-11-29 13:56:25 -0800647 - scaleInterpolator.getInterpolation(t)
Winson Chung22ca0952011-10-20 19:44:32 -0700648 * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
Romain Guy8279acb2011-11-29 13:56:25 -0800649 mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
650 mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
651 mScreenshotView.setScaleX(scaleT);
652 mScreenshotView.setScaleY(scaleT);
653 mScreenshotView.setTranslationX(t * finalPos.x);
654 mScreenshotView.setTranslationY(t * finalPos.y);
Winson Chunga63bb842011-10-17 10:26:28 -0700655 }
656 });
657 }
Winson Chung9112ec32011-06-27 13:15:32 -0700658 return anim;
659 }
Winson Chungc57ccf02011-10-13 15:04:59 -0700660
661 static void notifyScreenshotError(Context context, NotificationManager nManager) {
662 Resources r = context.getResources();
663
664 // Clear all existing notification, compose the new notification and show it
Winson Chung224848f2012-07-18 16:47:51 -0700665 Notification.Builder b = new Notification.Builder(context)
Winson Chungc57ccf02011-10-13 15:04:59 -0700666 .setTicker(r.getString(R.string.screenshot_failed_title))
667 .setContentTitle(r.getString(R.string.screenshot_failed_title))
668 .setContentText(r.getString(R.string.screenshot_failed_text))
Winson Chungb787a752011-10-19 18:31:53 -0700669 .setSmallIcon(R.drawable.stat_notify_image_error)
Winson Chungc57ccf02011-10-13 15:04:59 -0700670 .setWhen(System.currentTimeMillis())
Winson Chung224848f2012-07-18 16:47:51 -0700671 .setAutoCancel(true);
672 Notification n =
673 new Notification.BigTextStyle(b)
674 .bigText(r.getString(R.string.screenshot_failed_text))
675 .build();
Winson Chungc57ccf02011-10-13 15:04:59 -0700676 nManager.notify(SCREENSHOT_NOTIFICATION_ID, n);
677 }
Winson Chung9112ec32011-06-27 13:15:32 -0700678}