blob: e516bb854c93266e149b5d0bab94a7a70009f0e6 [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;
Victoria Leasee45e1f22013-09-30 15:33:43 -070063import java.text.DateFormat;
Winson Chung9112ec32011-06-27 13:15:32 -070064import java.text.SimpleDateFormat;
65import java.util.Date;
66
67/**
68 * POD used in the AsyncTask which saves an image in the background.
69 */
70class SaveImageInBackgroundData {
71 Context context;
72 Bitmap image;
Winson Chung36c9e292011-10-18 13:48:38 -070073 Uri imageUri;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -070074 Runnable finisher;
Winson Chunga63bb842011-10-17 10:26:28 -070075 int iconSize;
Winson Chung9112ec32011-06-27 13:15:32 -070076 int result;
Winson Chung0e6232c2013-01-22 14:11:46 -080077
78 void clearImage() {
Winson Chung0e6232c2013-01-22 14:11:46 -080079 image = null;
80 imageUri = null;
81 iconSize = 0;
82 }
Winson Chung3a5a7742013-04-01 16:04:28 -070083 void clearContext() {
84 context = null;
85 }
Winson Chung9112ec32011-06-27 13:15:32 -070086}
87
88/**
89 * An AsyncTask that saves an image to the media store in the background.
90 */
91class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void,
92 SaveImageInBackgroundData> {
Winson Chung5cc9a312013-02-11 14:58:45 -080093 private static final String TAG = "SaveImageInBackgroundTask";
94
Winson Chung9112ec32011-06-27 13:15:32 -070095 private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
Winson Chung753e40b2011-07-25 17:10:21 -070096 private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
Winson Chung224848f2012-07-18 16:47:51 -070097 private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
Winson Chung9112ec32011-06-27 13:15:32 -070098
Jeff Sharkey80b54de2013-01-16 15:07:37 -080099 private final int mNotificationId;
100 private final NotificationManager mNotificationManager;
Dan Sandler156e97f2014-01-30 11:14:07 -0500101 private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800102 private final File mScreenshotDir;
103 private final String mImageFileName;
104 private final String mImageFilePath;
105 private final long mImageTime;
106 private final BigPictureStyle mNotificationStyle;
107 private final int mImageWidth;
108 private final int mImageHeight;
Winson Chungc57ccf02011-10-13 15:04:59 -0700109
Winson Chunga63bb842011-10-17 10:26:28 -0700110 // WORKAROUND: We want the same notification across screenshots that we update so that we don't
111 // spam a user's notification drawer. However, we only show the ticker for the saving state
112 // and if the ticker text is the same as the previous notification, then it will not show. So
113 // for now, we just add and remove a space from the ticker text to trigger the animation when
114 // necessary.
115 private static boolean mTickerAddSpace;
116
117 SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
118 NotificationManager nManager, int nId) {
Winson Chungc57ccf02011-10-13 15:04:59 -0700119 Resources r = context.getResources();
120
121 // Prepare all the output metadata
122 mImageTime = System.currentTimeMillis();
Romain Guy8279acb2011-11-29 13:56:25 -0800123 String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
Romain Guy8279acb2011-11-29 13:56:25 -0800124 mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
Jeff Sharkey149e02c2013-01-16 14:38:49 -0800125
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800126 mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
Jeff Sharkey149e02c2013-01-16 14:38:49 -0800127 Environment.DIRECTORY_PICTURES), SCREENSHOTS_DIR_NAME);
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800128 mImageFilePath = new File(mScreenshotDir, mImageFileName).getAbsolutePath();
Winson Chungc57ccf02011-10-13 15:04:59 -0700129
Winson Chunga63bb842011-10-17 10:26:28 -0700130 // Create the large notification icon
Doris Liu5aa011b2012-11-06 16:29:46 -0800131 mImageWidth = data.image.getWidth();
132 mImageHeight = data.image.getHeight();
Chris Wren3745a3d2012-05-22 15:11:52 -0400133 int iconSize = data.iconSize;
134
Doris Liu5aa011b2012-11-06 16:29:46 -0800135 final int shortSide = mImageWidth < mImageHeight ? mImageWidth : mImageHeight;
Chris Wren3745a3d2012-05-22 15:11:52 -0400136 Bitmap preview = Bitmap.createBitmap(shortSide, shortSide, data.image.getConfig());
137 Canvas c = new Canvas(preview);
138 Paint paint = new Paint();
139 ColorMatrix desat = new ColorMatrix();
140 desat.setSaturation(0.25f);
141 paint.setColorFilter(new ColorMatrixColorFilter(desat));
142 Matrix matrix = new Matrix();
Doris Liu5aa011b2012-11-06 16:29:46 -0800143 matrix.postTranslate((shortSide - mImageWidth) / 2,
144 (shortSide - mImageHeight) / 2);
Chris Wren3745a3d2012-05-22 15:11:52 -0400145 c.drawBitmap(data.image, matrix, paint);
146 c.drawColor(0x40FFFFFF);
Winson Chung5cc9a312013-02-11 14:58:45 -0800147 c.setBitmap(null);
Chris Wren3745a3d2012-05-22 15:11:52 -0400148
149 Bitmap croppedIcon = Bitmap.createScaledBitmap(preview, iconSize, iconSize, true);
Winson Chunga63bb842011-10-17 10:26:28 -0700150
Winson Chungc57ccf02011-10-13 15:04:59 -0700151 // Show the intermediate notification
Winson Chunga63bb842011-10-17 10:26:28 -0700152 mTickerAddSpace = !mTickerAddSpace;
Winson Chungc57ccf02011-10-13 15:04:59 -0700153 mNotificationId = nId;
Winson Chung9c99aee2011-11-14 14:12:54 -0800154 mNotificationManager = nManager;
Dan Sandler156e97f2014-01-30 11:14:07 -0500155 final long now = System.currentTimeMillis();
156
Winson Chungc57ccf02011-10-13 15:04:59 -0700157 mNotificationBuilder = new Notification.Builder(context)
Winson Chunga63bb842011-10-17 10:26:28 -0700158 .setTicker(r.getString(R.string.screenshot_saving_ticker)
159 + (mTickerAddSpace ? " " : ""))
Winson Chungc57ccf02011-10-13 15:04:59 -0700160 .setContentTitle(r.getString(R.string.screenshot_saving_title))
161 .setContentText(r.getString(R.string.screenshot_saving_text))
Winson Chungb787a752011-10-19 18:31:53 -0700162 .setSmallIcon(R.drawable.stat_notify_image)
Dan Sandler156e97f2014-01-30 11:14:07 -0500163 .setWhen(now);
Chris Wren3745a3d2012-05-22 15:11:52 -0400164
165 mNotificationStyle = new Notification.BigPictureStyle()
166 .bigPicture(preview);
167 mNotificationBuilder.setStyle(mNotificationStyle);
168
Dan Sandler156e97f2014-01-30 11:14:07 -0500169 // For "public" situations we want to show all the same info but
170 // omit the actual screenshot image.
171 mPublicNotificationBuilder = new Notification.Builder(context)
172 .setContentTitle(r.getString(R.string.screenshot_saving_title))
173 .setContentText(r.getString(R.string.screenshot_saving_text))
174 .setSmallIcon(R.drawable.stat_notify_image)
175 .setWhen(now);
176
177 mNotificationBuilder.setPublicVersion(mPublicNotificationBuilder.build());
178
Chris Wren3745a3d2012-05-22 15:11:52 -0400179 Notification n = mNotificationBuilder.build();
Winson Chungc57ccf02011-10-13 15:04:59 -0700180 n.flags |= Notification.FLAG_NO_CLEAR;
Winson Chungc57ccf02011-10-13 15:04:59 -0700181 mNotificationManager.notify(nId, n);
Winson Chung9c99aee2011-11-14 14:12:54 -0800182
183 // On the tablet, the large icon makes the notification appear as if it is clickable (and
184 // on small devices, the large icon is not shown) so defer showing the large icon until
185 // we compose the final post-save notification below.
186 mNotificationBuilder.setLargeIcon(croppedIcon);
Chris Wren3745a3d2012-05-22 15:11:52 -0400187 // But we still don't set it for the expanded view, allowing the smallIcon to show here.
188 mNotificationStyle.bigLargeIcon(null);
Winson Chungc57ccf02011-10-13 15:04:59 -0700189 }
190
Winson Chung9112ec32011-06-27 13:15:32 -0700191 @Override
192 protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) {
193 if (params.length != 1) return null;
Winson Chung0e6232c2013-01-22 14:11:46 -0800194 if (isCancelled()) {
195 params[0].clearImage();
Winson Chung3a5a7742013-04-01 16:04:28 -0700196 params[0].clearContext();
Winson Chung0e6232c2013-01-22 14:11:46 -0800197 return null;
198 }
Winson Chung9112ec32011-06-27 13:15:32 -0700199
Winson Chung36c9e292011-10-18 13:48:38 -0700200 // By default, AsyncTask sets the worker thread to have background thread priority, so bump
201 // it back up so that we save a little quicker.
202 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
203
Winson Chung9112ec32011-06-27 13:15:32 -0700204 Context context = params[0].context;
205 Bitmap image = params[0].image;
Chris Wren3745a3d2012-05-22 15:11:52 -0400206 Resources r = context.getResources();
Winson Chung9112ec32011-06-27 13:15:32 -0700207
Winson Chungd859fa32011-07-22 12:19:52 -0700208 try {
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800209 // Create screenshot directory if it doesn't exist
210 mScreenshotDir.mkdirs();
211
Marco Nelissen9b64ca72013-08-21 13:13:29 -0700212 // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds
213 // for DATE_TAKEN
Mike Lockwoood47611242013-05-02 09:24:37 -0700214 long dateSeconds = mImageTime / 1000;
215
Winson Chung9112ec32011-06-27 13:15:32 -0700216 // Save the screenshot to the MediaStore
217 ContentValues values = new ContentValues();
Mike Lockwood98377342011-07-26 13:55:16 -0400218 ContentResolver resolver = context.getContentResolver();
Winson Chungc57ccf02011-10-13 15:04:59 -0700219 values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
220 values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
221 values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
Marco Nelissen9b64ca72013-08-21 13:13:29 -0700222 values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
Mike Lockwoood47611242013-05-02 09:24:37 -0700223 values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
224 values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
Winson Chung9112ec32011-06-27 13:15:32 -0700225 values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
Doris Liu5aa011b2012-11-06 16:29:46 -0800226 values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
227 values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
Mike Lockwood98377342011-07-26 13:55:16 -0400228 Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
Winson Chung9112ec32011-06-27 13:15:32 -0700229
Victoria Leasee45e1f22013-09-30 15:33:43 -0700230 String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
Winson Chung224848f2012-07-18 16:47:51 -0700231 String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
Chris Wren3745a3d2012-05-22 15:11:52 -0400232 Intent sharingIntent = new Intent(Intent.ACTION_SEND);
233 sharingIntent.setType("image/png");
234 sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
Winson Chung224848f2012-07-18 16:47:51 -0700235 sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
Daniel Sandler046fddf2012-05-31 15:40:25 -0400236
237 Intent chooserIntent = Intent.createChooser(sharingIntent, null);
John Spurlock209bede2013-07-17 12:23:27 -0400238 chooserIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
Daniel Sandler046fddf2012-05-31 15:40:25 -0400239 | Intent.FLAG_ACTIVITY_NEW_TASK);
240
Chris Wren3745a3d2012-05-22 15:11:52 -0400241 mNotificationBuilder.addAction(R.drawable.ic_menu_share,
242 r.getString(com.android.internal.R.string.share),
John Spurlock209bede2013-07-17 12:23:27 -0400243 PendingIntent.getActivity(context, 0, chooserIntent,
Daniel Sandler046fddf2012-05-31 15:40:25 -0400244 PendingIntent.FLAG_CANCEL_CURRENT));
Chris Wren3745a3d2012-05-22 15:11:52 -0400245
Mike Lockwood98377342011-07-26 13:55:16 -0400246 OutputStream out = resolver.openOutputStream(uri);
Winson Chung9112ec32011-06-27 13:15:32 -0700247 image.compress(Bitmap.CompressFormat.PNG, 100, out);
248 out.flush();
249 out.close();
250
Mike Lockwood98377342011-07-26 13:55:16 -0400251 // update file size in the database
252 values.clear();
Winson Chungc57ccf02011-10-13 15:04:59 -0700253 values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
Mike Lockwood98377342011-07-26 13:55:16 -0400254 resolver.update(uri, values, null, null);
255
Winson Chung36c9e292011-10-18 13:48:38 -0700256 params[0].imageUri = uri;
Winson Chungcd6a9ef2013-01-16 14:59:18 -0800257 params[0].image = null;
Winson Chung9112ec32011-06-27 13:15:32 -0700258 params[0].result = 0;
Winson Chungd859fa32011-07-22 12:19:52 -0700259 } catch (Exception e) {
260 // IOException/UnsupportedOperationException may be thrown if external storage is not
261 // mounted
Winson Chung0e6232c2013-01-22 14:11:46 -0800262 params[0].clearImage();
Winson Chung9112ec32011-06-27 13:15:32 -0700263 params[0].result = 1;
264 }
265
Winson Chungcd6a9ef2013-01-16 14:59:18 -0800266 // Recycle the bitmap data
267 if (image != null) {
268 image.recycle();
269 }
270
Winson Chung9112ec32011-06-27 13:15:32 -0700271 return params[0];
Romain Guy8279acb2011-11-29 13:56:25 -0800272 }
Winson Chung9112ec32011-06-27 13:15:32 -0700273
274 @Override
275 protected void onPostExecute(SaveImageInBackgroundData params) {
Winson Chung0e6232c2013-01-22 14:11:46 -0800276 if (isCancelled()) {
277 params.finisher.run();
278 params.clearImage();
Winson Chung3a5a7742013-04-01 16:04:28 -0700279 params.clearContext();
Winson Chung0e6232c2013-01-22 14:11:46 -0800280 return;
281 }
282
Winson Chung9112ec32011-06-27 13:15:32 -0700283 if (params.result > 0) {
284 // Show a message that we've failed to save the image to disk
Winson Chungc57ccf02011-10-13 15:04:59 -0700285 GlobalScreenshot.notifyScreenshotError(params.context, mNotificationManager);
Winson Chung9112ec32011-06-27 13:15:32 -0700286 } else {
Winson Chungc57ccf02011-10-13 15:04:59 -0700287 // Show the final notification to indicate screenshot saved
288 Resources r = params.context.getResources();
289
Winson Chung36c9e292011-10-18 13:48:38 -0700290 // Create the intent to show the screenshot in gallery
Romain Guy8279acb2011-11-29 13:56:25 -0800291 Intent launchIntent = new Intent(Intent.ACTION_VIEW);
292 launchIntent.setDataAndType(params.imageUri, "image/png");
293 launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Winson Chung36c9e292011-10-18 13:48:38 -0700294
Dan Sandler156e97f2014-01-30 11:14:07 -0500295 final long now = System.currentTimeMillis();
296
Winson Chungc57ccf02011-10-13 15:04:59 -0700297 mNotificationBuilder
Winson Chungc57ccf02011-10-13 15:04:59 -0700298 .setContentTitle(r.getString(R.string.screenshot_saved_title))
299 .setContentText(r.getString(R.string.screenshot_saved_text))
Romain Guy8279acb2011-11-29 13:56:25 -0800300 .setContentIntent(PendingIntent.getActivity(params.context, 0, launchIntent, 0))
Dan Sandler156e97f2014-01-30 11:14:07 -0500301 .setWhen(now)
Winson Chungc57ccf02011-10-13 15:04:59 -0700302 .setAutoCancel(true);
303
Dan Sandler156e97f2014-01-30 11:14:07 -0500304 // Update the text in the public version as well
305 mPublicNotificationBuilder
306 .setContentTitle(r.getString(R.string.screenshot_saved_title))
307 .setContentText(r.getString(R.string.screenshot_saved_text))
308 .setContentIntent(PendingIntent.getActivity(params.context, 0, launchIntent, 0))
309 .setWhen(now)
310 .setAutoCancel(true);
311
312 mNotificationBuilder.setPublicVersion(mPublicNotificationBuilder.build());
313
Chris Wren3745a3d2012-05-22 15:11:52 -0400314 Notification n = mNotificationBuilder.build();
Winson Chungc57ccf02011-10-13 15:04:59 -0700315 n.flags &= ~Notification.FLAG_NO_CLEAR;
316 mNotificationManager.notify(mNotificationId, n);
Winson Chung9112ec32011-06-27 13:15:32 -0700317 }
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700318 params.finisher.run();
Winson Chung3a5a7742013-04-01 16:04:28 -0700319 params.clearContext();
Romain Guy8279acb2011-11-29 13:56:25 -0800320 }
Winson Chung9112ec32011-06-27 13:15:32 -0700321}
322
323/**
324 * TODO:
325 * - Performance when over gl surfaces? Ie. Gallery
326 * - what do we say in the Toast? Which icon do we get if the user uses another
327 * type of gallery?
328 */
329class GlobalScreenshot {
Winson Chung5cc9a312013-02-11 14:58:45 -0800330 private static final String TAG = "GlobalScreenshot";
331
Winson Chungc57ccf02011-10-13 15:04:59 -0700332 private static final int SCREENSHOT_NOTIFICATION_ID = 789;
Winson Chung22ca0952011-10-20 19:44:32 -0700333 private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
334 private static final int SCREENSHOT_DROP_IN_DURATION = 430;
335 private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
336 private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
337 private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
338 private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
339 private static final float BACKGROUND_ALPHA = 0.5f;
340 private static final float SCREENSHOT_SCALE = 1f;
341 private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
342 private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
343 private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
344 private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
Winson Chung9112ec32011-06-27 13:15:32 -0700345
346 private Context mContext;
Winson Chung9112ec32011-06-27 13:15:32 -0700347 private WindowManager mWindowManager;
348 private WindowManager.LayoutParams mWindowLayoutParams;
Winson Chungc57ccf02011-10-13 15:04:59 -0700349 private NotificationManager mNotificationManager;
Winson Chung9112ec32011-06-27 13:15:32 -0700350 private Display mDisplay;
351 private DisplayMetrics mDisplayMetrics;
352 private Matrix mDisplayMatrix;
353
354 private Bitmap mScreenBitmap;
355 private View mScreenshotLayout;
356 private ImageView mBackgroundView;
Winson Chung9112ec32011-06-27 13:15:32 -0700357 private ImageView mScreenshotView;
Winson Chung22ca0952011-10-20 19:44:32 -0700358 private ImageView mScreenshotFlash;
Winson Chung9112ec32011-06-27 13:15:32 -0700359
360 private AnimatorSet mScreenshotAnimation;
361
Winson Chunga63bb842011-10-17 10:26:28 -0700362 private int mNotificationIconSize;
Winson Chunga63bb842011-10-17 10:26:28 -0700363 private float mBgPadding;
364 private float mBgPaddingScale;
365
Winson Chung0e6232c2013-01-22 14:11:46 -0800366 private AsyncTask<SaveImageInBackgroundData, Void, SaveImageInBackgroundData> mSaveInBgTask;
367
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800368 private MediaActionSound mCameraSound;
Winson Chung8d513ea2011-12-01 14:39:12 -0800369
Winson Chung9112ec32011-06-27 13:15:32 -0700370
371 /**
372 * @param context everything needs a context :(
373 */
374 public GlobalScreenshot(Context context) {
Winson Chunga63bb842011-10-17 10:26:28 -0700375 Resources r = context.getResources();
Winson Chung9112ec32011-06-27 13:15:32 -0700376 mContext = context;
Romain Guy8279acb2011-11-29 13:56:25 -0800377 LayoutInflater layoutInflater = (LayoutInflater)
Winson Chung9112ec32011-06-27 13:15:32 -0700378 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
379
380 // Inflate the screenshot layout
Winson Chung9112ec32011-06-27 13:15:32 -0700381 mDisplayMatrix = new Matrix();
Romain Guy8279acb2011-11-29 13:56:25 -0800382 mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
Winson Chung9112ec32011-06-27 13:15:32 -0700383 mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
Winson Chung9112ec32011-06-27 13:15:32 -0700384 mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
Winson Chung22ca0952011-10-20 19:44:32 -0700385 mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
Winson Chung9112ec32011-06-27 13:15:32 -0700386 mScreenshotLayout.setFocusable(true);
387 mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
388 @Override
389 public boolean onTouch(View v, MotionEvent event) {
390 // Intercept and ignore all touch events
391 return true;
392 }
393 });
394
395 // Setup the window that we are going to use
Winson Chung9112ec32011-06-27 13:15:32 -0700396 mWindowLayoutParams = new WindowManager.LayoutParams(
397 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
398 WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
399 WindowManager.LayoutParams.FLAG_FULLSCREEN
400 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
Winson Chung9112ec32011-06-27 13:15:32 -0700401 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
402 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
403 PixelFormat.TRANSLUCENT);
Winson Chung9112ec32011-06-27 13:15:32 -0700404 mWindowLayoutParams.setTitle("ScreenshotAnimation");
405 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Winson Chungc57ccf02011-10-13 15:04:59 -0700406 mNotificationManager =
407 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Winson Chung9112ec32011-06-27 13:15:32 -0700408 mDisplay = mWindowManager.getDefaultDisplay();
Winson Chunga63bb842011-10-17 10:26:28 -0700409 mDisplayMetrics = new DisplayMetrics();
410 mDisplay.getRealMetrics(mDisplayMetrics);
411
412 // Get the various target sizes
Winson Chunga63bb842011-10-17 10:26:28 -0700413 mNotificationIconSize =
414 r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
Winson Chunga63bb842011-10-17 10:26:28 -0700415
416 // Scale has to account for both sides of the bg
417 mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
Winson Chung22ca0952011-10-20 19:44:32 -0700418 mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
Winson Chung8d513ea2011-12-01 14:39:12 -0800419
420 // Setup the Camera shutter sound
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800421 mCameraSound = new MediaActionSound();
422 mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
Winson Chung9112ec32011-06-27 13:15:32 -0700423 }
424
425 /**
426 * Creates a new worker thread and saves the screenshot to the media store.
427 */
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700428 private void saveScreenshotInWorkerThread(Runnable finisher) {
Winson Chung9112ec32011-06-27 13:15:32 -0700429 SaveImageInBackgroundData data = new SaveImageInBackgroundData();
430 data.context = mContext;
431 data.image = mScreenBitmap;
Winson Chunga63bb842011-10-17 10:26:28 -0700432 data.iconSize = mNotificationIconSize;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700433 data.finisher = finisher;
Winson Chung0e6232c2013-01-22 14:11:46 -0800434 if (mSaveInBgTask != null) {
435 mSaveInBgTask.cancel(false);
436 }
437 mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
Winson Chunga63bb842011-10-17 10:26:28 -0700438 SCREENSHOT_NOTIFICATION_ID).execute(data);
Winson Chung9112ec32011-06-27 13:15:32 -0700439 }
440
441 /**
442 * @return the current display rotation in degrees
443 */
444 private float getDegreesForRotation(int value) {
445 switch (value) {
446 case Surface.ROTATION_90:
Winson Chunga63bb842011-10-17 10:26:28 -0700447 return 360f - 90f;
Winson Chung9112ec32011-06-27 13:15:32 -0700448 case Surface.ROTATION_180:
Winson Chunga63bb842011-10-17 10:26:28 -0700449 return 360f - 180f;
Winson Chung9112ec32011-06-27 13:15:32 -0700450 case Surface.ROTATION_270:
Winson Chunga63bb842011-10-17 10:26:28 -0700451 return 360f - 270f;
Winson Chung9112ec32011-06-27 13:15:32 -0700452 }
453 return 0f;
454 }
455
456 /**
457 * Takes a screenshot of the current display and shows an animation.
458 */
Winson Chunga63bb842011-10-17 10:26:28 -0700459 void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
Winson Chung9112ec32011-06-27 13:15:32 -0700460 // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
461 // only in the natural orientation of the device :!)
462 mDisplay.getRealMetrics(mDisplayMetrics);
463 float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
464 float degrees = getDegreesForRotation(mDisplay.getRotation());
465 boolean requiresRotation = (degrees > 0);
466 if (requiresRotation) {
467 // Get the dimensions of the device in its native orientation
468 mDisplayMatrix.reset();
469 mDisplayMatrix.preRotate(-degrees);
470 mDisplayMatrix.mapPoints(dims);
471 dims[0] = Math.abs(dims[0]);
472 dims[1] = Math.abs(dims[1]);
473 }
Winson Chunga46d7782012-01-04 16:43:10 -0800474
475 // Take the screenshot
Mathias Agopian3866f0d2013-02-11 22:08:48 -0800476 mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
Winson Chunga46d7782012-01-04 16:43:10 -0800477 if (mScreenBitmap == null) {
478 notifyScreenshotError(mContext, mNotificationManager);
479 finisher.run();
480 return;
481 }
482
Winson Chung9112ec32011-06-27 13:15:32 -0700483 if (requiresRotation) {
484 // Rotate the screenshot to the current orientation
485 Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
486 mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
487 Canvas c = new Canvas(ss);
488 c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
Winson Chunga63bb842011-10-17 10:26:28 -0700489 c.rotate(degrees);
Winson Chung9112ec32011-06-27 13:15:32 -0700490 c.translate(-dims[0] / 2, -dims[1] / 2);
491 c.drawBitmap(mScreenBitmap, 0, 0, null);
Dianne Hackborn6311d0a2011-08-02 16:37:58 -0700492 c.setBitmap(null);
Winson Chung5cc9a312013-02-11 14:58:45 -0800493 // Recycle the previous bitmap
494 mScreenBitmap.recycle();
Winson Chung9112ec32011-06-27 13:15:32 -0700495 mScreenBitmap = ss;
496 }
497
Winson Chunga63bb842011-10-17 10:26:28 -0700498 // Optimizations
499 mScreenBitmap.setHasAlpha(false);
500 mScreenBitmap.prepareToDraw();
501
Winson Chung9112ec32011-06-27 13:15:32 -0700502 // Start the post-screenshot animation
Winson Chunga63bb842011-10-17 10:26:28 -0700503 startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
504 statusBarVisible, navBarVisible);
Winson Chung9112ec32011-06-27 13:15:32 -0700505 }
506
507
508 /**
509 * Starts the animation after taking the screenshot
510 */
Winson Chunga63bb842011-10-17 10:26:28 -0700511 private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
512 boolean navBarVisible) {
Winson Chung9112ec32011-06-27 13:15:32 -0700513 // Add the view for the animation
514 mScreenshotView.setImageBitmap(mScreenBitmap);
515 mScreenshotLayout.requestFocus();
516
517 // Setup the animation with the screenshot just taken
518 if (mScreenshotAnimation != null) {
519 mScreenshotAnimation.end();
Winson Chung0e6232c2013-01-22 14:11:46 -0800520 mScreenshotAnimation.removeAllListeners();
Winson Chung9112ec32011-06-27 13:15:32 -0700521 }
522
523 mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
Winson Chung22ca0952011-10-20 19:44:32 -0700524 ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
525 ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
Winson Chunga63bb842011-10-17 10:26:28 -0700526 statusBarVisible, navBarVisible);
Winson Chung9112ec32011-06-27 13:15:32 -0700527 mScreenshotAnimation = new AnimatorSet();
Winson Chung22ca0952011-10-20 19:44:32 -0700528 mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
Winson Chung9112ec32011-06-27 13:15:32 -0700529 mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
530 @Override
531 public void onAnimationEnd(Animator animation) {
532 // Save the screenshot once we have a bit of time now
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700533 saveScreenshotInWorkerThread(finisher);
Winson Chung9112ec32011-06-27 13:15:32 -0700534 mWindowManager.removeView(mScreenshotLayout);
Winson Chungcd6a9ef2013-01-16 14:59:18 -0800535
536 // Clear any references to the bitmap
537 mScreenBitmap = null;
538 mScreenshotView.setImageBitmap(null);
Winson Chung9112ec32011-06-27 13:15:32 -0700539 }
540 });
Winson Chunga63bb842011-10-17 10:26:28 -0700541 mScreenshotLayout.post(new Runnable() {
542 @Override
543 public void run() {
Winson Chung8d513ea2011-12-01 14:39:12 -0800544 // Play the shutter sound to notify that we've taken a screenshot
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800545 mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
Winson Chung8d513ea2011-12-01 14:39:12 -0800546
Romain Guy8279acb2011-11-29 13:56:25 -0800547 mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
548 mScreenshotView.buildLayer();
Winson Chunga63bb842011-10-17 10:26:28 -0700549 mScreenshotAnimation.start();
550 }
551 });
Winson Chung9112ec32011-06-27 13:15:32 -0700552 }
Winson Chung22ca0952011-10-20 19:44:32 -0700553 private ValueAnimator createScreenshotDropInAnimation() {
554 final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
555 / SCREENSHOT_DROP_IN_DURATION);
556 final float flashDurationPct = 2f * flashPeakDurationPct;
557 final Interpolator flashAlphaInterpolator = new Interpolator() {
558 @Override
559 public float getInterpolation(float x) {
560 // Flash the flash view in and out quickly
561 if (x <= flashDurationPct) {
562 return (float) Math.sin(Math.PI * (x / flashDurationPct));
563 }
564 return 0;
565 }
566 };
567 final Interpolator scaleInterpolator = new Interpolator() {
568 @Override
569 public float getInterpolation(float x) {
570 // We start scaling when the flash is at it's peak
571 if (x < flashPeakDurationPct) {
572 return 0;
573 }
574 return (x - flashDurationPct) / (1f - flashDurationPct);
575 }
576 };
Winson Chung9112ec32011-06-27 13:15:32 -0700577 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
Winson Chung22ca0952011-10-20 19:44:32 -0700578 anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
Winson Chung9112ec32011-06-27 13:15:32 -0700579 anim.addListener(new AnimatorListenerAdapter() {
580 @Override
581 public void onAnimationStart(Animator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800582 mBackgroundView.setAlpha(0f);
Winson Chung9112ec32011-06-27 13:15:32 -0700583 mBackgroundView.setVisibility(View.VISIBLE);
Romain Guy8279acb2011-11-29 13:56:25 -0800584 mScreenshotView.setAlpha(0f);
585 mScreenshotView.setTranslationX(0f);
586 mScreenshotView.setTranslationY(0f);
587 mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
588 mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
589 mScreenshotView.setVisibility(View.VISIBLE);
590 mScreenshotFlash.setAlpha(0f);
Winson Chung22ca0952011-10-20 19:44:32 -0700591 mScreenshotFlash.setVisibility(View.VISIBLE);
592 }
593 @Override
594 public void onAnimationEnd(android.animation.Animator animation) {
595 mScreenshotFlash.setVisibility(View.GONE);
Winson Chung9112ec32011-06-27 13:15:32 -0700596 }
597 });
598 anim.addUpdateListener(new AnimatorUpdateListener() {
599 @Override
600 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800601 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700602 float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
Romain Guy8279acb2011-11-29 13:56:25 -0800603 - scaleInterpolator.getInterpolation(t)
Winson Chung22ca0952011-10-20 19:44:32 -0700604 * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
Romain Guy8279acb2011-11-29 13:56:25 -0800605 mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
606 mScreenshotView.setAlpha(t);
607 mScreenshotView.setScaleX(scaleT);
608 mScreenshotView.setScaleY(scaleT);
609 mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
Winson Chung9112ec32011-06-27 13:15:32 -0700610 }
611 });
612 return anim;
613 }
Winson Chung22ca0952011-10-20 19:44:32 -0700614 private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
Winson Chunga63bb842011-10-17 10:26:28 -0700615 boolean navBarVisible) {
616 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
Winson Chung22ca0952011-10-20 19:44:32 -0700617 anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
Winson Chung9112ec32011-06-27 13:15:32 -0700618 anim.addListener(new AnimatorListenerAdapter() {
619 @Override
620 public void onAnimationEnd(Animator animation) {
621 mBackgroundView.setVisibility(View.GONE);
Romain Guy8279acb2011-11-29 13:56:25 -0800622 mScreenshotView.setVisibility(View.GONE);
623 mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
Winson Chung9112ec32011-06-27 13:15:32 -0700624 }
625 });
Winson Chunga63bb842011-10-17 10:26:28 -0700626
627 if (!statusBarVisible || !navBarVisible) {
628 // There is no status bar/nav bar, so just fade the screenshot away in place
Winson Chung22ca0952011-10-20 19:44:32 -0700629 anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
Winson Chunga63bb842011-10-17 10:26:28 -0700630 anim.addUpdateListener(new AnimatorUpdateListener() {
631 @Override
632 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800633 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700634 float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
Romain Guy8279acb2011-11-29 13:56:25 -0800635 - t * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
636 mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
637 mScreenshotView.setAlpha(1f - t);
638 mScreenshotView.setScaleX(scaleT);
639 mScreenshotView.setScaleY(scaleT);
Winson Chunga63bb842011-10-17 10:26:28 -0700640 }
641 });
642 } else {
Winson Chung22ca0952011-10-20 19:44:32 -0700643 // In the case where there is a status bar, animate to the origin of the bar (top-left)
644 final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
645 / SCREENSHOT_DROP_OUT_DURATION;
646 final Interpolator scaleInterpolator = new Interpolator() {
647 @Override
648 public float getInterpolation(float x) {
649 if (x < scaleDurationPct) {
650 // Decelerate, and scale the input accordingly
651 return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
652 }
653 return 1f;
654 }
655 };
656
Winson Chunga63bb842011-10-17 10:26:28 -0700657 // Determine the bounds of how to scale
658 float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
659 float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
Winson Chung22ca0952011-10-20 19:44:32 -0700660 final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
661 final PointF finalPos = new PointF(
662 -halfScreenWidth + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
663 -halfScreenHeight + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
Winson Chunga63bb842011-10-17 10:26:28 -0700664
665 // Animate the screenshot to the status bar
Winson Chung22ca0952011-10-20 19:44:32 -0700666 anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
Winson Chunga63bb842011-10-17 10:26:28 -0700667 anim.addUpdateListener(new AnimatorUpdateListener() {
668 @Override
669 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800670 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700671 float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
Romain Guy8279acb2011-11-29 13:56:25 -0800672 - scaleInterpolator.getInterpolation(t)
Winson Chung22ca0952011-10-20 19:44:32 -0700673 * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
Romain Guy8279acb2011-11-29 13:56:25 -0800674 mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
675 mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
676 mScreenshotView.setScaleX(scaleT);
677 mScreenshotView.setScaleY(scaleT);
678 mScreenshotView.setTranslationX(t * finalPos.x);
679 mScreenshotView.setTranslationY(t * finalPos.y);
Winson Chunga63bb842011-10-17 10:26:28 -0700680 }
681 });
682 }
Winson Chung9112ec32011-06-27 13:15:32 -0700683 return anim;
684 }
Winson Chungc57ccf02011-10-13 15:04:59 -0700685
686 static void notifyScreenshotError(Context context, NotificationManager nManager) {
687 Resources r = context.getResources();
688
689 // Clear all existing notification, compose the new notification and show it
Winson Chung224848f2012-07-18 16:47:51 -0700690 Notification.Builder b = new Notification.Builder(context)
Winson Chungc57ccf02011-10-13 15:04:59 -0700691 .setTicker(r.getString(R.string.screenshot_failed_title))
692 .setContentTitle(r.getString(R.string.screenshot_failed_title))
693 .setContentText(r.getString(R.string.screenshot_failed_text))
Winson Chungb787a752011-10-19 18:31:53 -0700694 .setSmallIcon(R.drawable.stat_notify_image_error)
Winson Chungc57ccf02011-10-13 15:04:59 -0700695 .setWhen(System.currentTimeMillis())
Dan Sandler156e97f2014-01-30 11:14:07 -0500696 .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
Winson Chung224848f2012-07-18 16:47:51 -0700697 .setAutoCancel(true);
698 Notification n =
699 new Notification.BigTextStyle(b)
700 .bigText(r.getString(R.string.screenshot_failed_text))
701 .build();
Winson Chungc57ccf02011-10-13 15:04:59 -0700702 nManager.notify(SCREENSHOT_NOTIFICATION_ID, n);
703 }
Winson Chung9112ec32011-06-27 13:15:32 -0700704}