blob: 341461bf72023c27b73cf970fef05d6c7c6fceb2 [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
Winson Chung06ca2742018-06-29 12:26:49 -070019import static android.content.Context.NOTIFICATION_SERVICE;
Adrian Roosd1229772018-03-21 14:46:10 +010020import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
Alison Cichowlas3e340502018-08-07 17:15:01 -040021
Winson Chung06ca2742018-06-29 12:26:49 -070022import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT;
23import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION;
24import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP;
Winson Chunged376a32017-09-07 14:05:42 -070025import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
26
Winson Chung9112ec32011-06-27 13:15:32 -070027import android.animation.Animator;
28import android.animation.AnimatorListenerAdapter;
29import android.animation.AnimatorSet;
Winson Chung9112ec32011-06-27 13:15:32 -070030import android.animation.ValueAnimator;
31import android.animation.ValueAnimator.AnimatorUpdateListener;
Winson Chunged376a32017-09-07 14:05:42 -070032import android.app.ActivityOptions;
Winson Chungc57ccf02011-10-13 15:04:59 -070033import android.app.Notification;
Chris Wren3745a3d2012-05-22 15:11:52 -040034import android.app.Notification.BigPictureStyle;
Winson Chungc57ccf02011-10-13 15:04:59 -070035import android.app.NotificationManager;
36import android.app.PendingIntent;
Adrian Roosd1229772018-03-21 14:46:10 +010037import android.app.admin.DevicePolicyManager;
Adam Powelld4d34152015-04-17 12:13:40 -070038import android.content.BroadcastReceiver;
Alison Cichowlas09e5ea32018-09-07 15:52:24 -040039import android.content.ClipData;
40import android.content.ClipDescription;
Alison Cichowlasf2806532018-03-06 19:08:18 -050041import android.content.ComponentName;
Mike Lockwood98377342011-07-26 13:55:16 -040042import android.content.ContentResolver;
Winson Chung9112ec32011-06-27 13:15:32 -070043import android.content.Context;
Winson Chungc57ccf02011-10-13 15:04:59 -070044import android.content.Intent;
Beth Thibodeaud98a9452019-03-08 14:32:51 -050045import android.content.res.Configuration;
Winson Chungc57ccf02011-10-13 15:04:59 -070046import android.content.res.Resources;
Winson Chung9112ec32011-06-27 13:15:32 -070047import android.graphics.Bitmap;
John Reck519ad482018-02-12 17:08:48 -080048import android.graphics.Canvas;
Beth Thibodeaud98a9452019-03-08 14:32:51 -050049import android.graphics.Color;
Chris Wren3745a3d2012-05-22 15:11:52 -040050import android.graphics.ColorMatrix;
51import android.graphics.ColorMatrixColorFilter;
Winson Chung9112ec32011-06-27 13:15:32 -070052import android.graphics.Matrix;
Chris Wren3745a3d2012-05-22 15:11:52 -040053import android.graphics.Paint;
John Reck519ad482018-02-12 17:08:48 -080054import android.graphics.Picture;
Winson Chung9112ec32011-06-27 13:15:32 -070055import android.graphics.PixelFormat;
Winson Chunga63bb842011-10-17 10:26:28 -070056import android.graphics.PointF;
Muyuan Li6ca619f2016-03-08 13:30:42 -080057import android.graphics.Rect;
Eino-Ville Talvalae6909582012-03-01 11:01:38 -080058import android.media.MediaActionSound;
Winson Chung9112ec32011-06-27 13:15:32 -070059import android.net.Uri;
60import android.os.AsyncTask;
Winson Chung9112ec32011-06-27 13:15:32 -070061import android.os.Environment;
Geoffrey Pitsched5de312017-06-27 11:42:12 -040062import android.os.PowerManager;
Winson Chung36c9e292011-10-18 13:48:38 -070063import android.os.Process;
phweissec3d9ca2017-02-09 16:57:39 +010064import android.os.UserHandle;
Winson Chung9112ec32011-06-27 13:15:32 -070065import android.provider.MediaStore;
Winson Chung06ca2742018-06-29 12:26:49 -070066import android.text.TextUtils;
Winson Chung9112ec32011-06-27 13:15:32 -070067import android.util.DisplayMetrics;
Dan Sandler150651b2017-06-14 10:43:07 -040068import android.util.Slog;
Winson Chung9112ec32011-06-27 13:15:32 -070069import android.view.Display;
Winson Chung9112ec32011-06-27 13:15:32 -070070import android.view.LayoutInflater;
71import android.view.MotionEvent;
Mathias Agopian3866f0d2013-02-11 22:08:48 -080072import android.view.SurfaceControl;
Winson Chung9112ec32011-06-27 13:15:32 -070073import android.view.View;
74import android.view.ViewGroup;
75import android.view.WindowManager;
Winson Chung22ca0952011-10-20 19:44:32 -070076import android.view.animation.Interpolator;
Winson Chung9112ec32011-06-27 13:15:32 -070077import android.widget.ImageView;
Geoffrey Pitsched5de312017-06-27 11:42:12 -040078import android.widget.Toast;
Alison Cichowlas3e340502018-08-07 17:15:01 -040079
Chris Wren5e6c0ff2017-01-05 12:57:06 -050080import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
Winson Chung9112ec32011-06-27 13:15:32 -070081import com.android.systemui.R;
Winson Chung06ca2742018-06-29 12:26:49 -070082import com.android.systemui.SysUiServiceProvider;
Adrian Roose25c18d2016-06-17 15:59:49 -070083import com.android.systemui.SystemUI;
Winson Chung06ca2742018-06-29 12:26:49 -070084import com.android.systemui.shared.system.ActivityManagerWrapper;
85import com.android.systemui.statusbar.phone.StatusBar;
Dan Sandler8e032e12017-01-25 13:41:38 -050086import com.android.systemui.util.NotificationChannels;
Alison Cichowlas3e340502018-08-07 17:15:01 -040087
Jeff Sharkey7bba5072018-11-02 14:37:07 -060088import libcore.io.IoUtils;
89
90import java.io.IOException;
Winson Chung9112ec32011-06-27 13:15:32 -070091import java.io.OutputStream;
Victoria Leasee45e1f22013-09-30 15:33:43 -070092import java.text.DateFormat;
Winson Chung9112ec32011-06-27 13:15:32 -070093import java.text.SimpleDateFormat;
94import java.util.Date;
95
96/**
97 * POD used in the AsyncTask which saves an image in the background.
98 */
99class SaveImageInBackgroundData {
100 Context context;
101 Bitmap image;
Winson Chung36c9e292011-10-18 13:48:38 -0700102 Uri imageUri;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700103 Runnable finisher;
Winson Chunga63bb842011-10-17 10:26:28 -0700104 int iconSize;
Chris Wrenf63185e2014-07-31 13:52:39 +0100105 int previewWidth;
106 int previewheight;
Winsone5591712016-02-18 13:45:54 -0800107 int errorMsgResId;
Winson Chung0e6232c2013-01-22 14:11:46 -0800108
109 void clearImage() {
Winson Chung0e6232c2013-01-22 14:11:46 -0800110 image = null;
111 imageUri = null;
112 iconSize = 0;
113 }
Winson Chung3a5a7742013-04-01 16:04:28 -0700114 void clearContext() {
115 context = null;
116 }
Winson Chung9112ec32011-06-27 13:15:32 -0700117}
118
119/**
120 * An AsyncTask that saves an image to the media store in the background.
121 */
Winsonc7aa65b2016-03-10 12:52:27 -0800122class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Dan Sandler150651b2017-06-14 10:43:07 -0400123 private static final String TAG = "SaveImageInBackgroundTask";
Winson Chung5cc9a312013-02-11 14:58:45 -0800124
Winson Chung753e40b2011-07-25 17:10:21 -0700125 private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
Winson Chung224848f2012-07-18 16:47:51 -0700126 private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
Winson Chung9112ec32011-06-27 13:15:32 -0700127
Winsonc7aa65b2016-03-10 12:52:27 -0800128 private final SaveImageInBackgroundData mParams;
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800129 private final NotificationManager mNotificationManager;
Dan Sandler156e97f2014-01-30 11:14:07 -0500130 private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800131 private final String mImageFileName;
Jeff Sharkey80b54de2013-01-16 15:07:37 -0800132 private final long mImageTime;
133 private final BigPictureStyle mNotificationStyle;
134 private final int mImageWidth;
135 private final int mImageHeight;
Winson Chungc57ccf02011-10-13 15:04:59 -0700136
Winson Chunga63bb842011-10-17 10:26:28 -0700137 SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
Winsone5591712016-02-18 13:45:54 -0800138 NotificationManager nManager) {
Winson Chungc57ccf02011-10-13 15:04:59 -0700139 Resources r = context.getResources();
140
141 // Prepare all the output metadata
Winsonc7aa65b2016-03-10 12:52:27 -0800142 mParams = data;
Winson Chungc57ccf02011-10-13 15:04:59 -0700143 mImageTime = System.currentTimeMillis();
Winson Chungb9c74ac2015-04-03 10:01:40 -0700144 String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
Romain Guy8279acb2011-11-29 13:56:25 -0800145 mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
Jeff Sharkey149e02c2013-01-16 14:38:49 -0800146
Winson Chunga63bb842011-10-17 10:26:28 -0700147 // Create the large notification icon
Doris Liu5aa011b2012-11-06 16:29:46 -0800148 mImageWidth = data.image.getWidth();
149 mImageHeight = data.image.getHeight();
Chris Wren3745a3d2012-05-22 15:11:52 -0400150 int iconSize = data.iconSize;
Chris Wrenf63185e2014-07-31 13:52:39 +0100151 int previewWidth = data.previewWidth;
152 int previewHeight = data.previewheight;
Chris Wren3745a3d2012-05-22 15:11:52 -0400153
Chris Wren3745a3d2012-05-22 15:11:52 -0400154 Paint paint = new Paint();
155 ColorMatrix desat = new ColorMatrix();
156 desat.setSaturation(0.25f);
157 paint.setColorFilter(new ColorMatrixColorFilter(desat));
158 Matrix matrix = new Matrix();
Winson Chungc6caff82015-05-27 12:41:29 -0700159 int overlayColor = 0x40FFFFFF;
160
Winson Chungc6caff82015-05-27 12:41:29 -0700161 matrix.setTranslate((previewWidth - mImageWidth) / 2, (previewHeight - mImageHeight) / 2);
chaviwa0539262017-12-12 14:46:40 -0800162 Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight, matrix,
163 paint, overlayColor);
Chris Wren3745a3d2012-05-22 15:11:52 -0400164
Winson Chungc6caff82015-05-27 12:41:29 -0700165 // Note, we can't use the preview for the small icon, since it is non-square
166 float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
Winson Chungc6caff82015-05-27 12:41:29 -0700167 matrix.setScale(scale, scale);
168 matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
169 (iconSize - (scale * mImageHeight)) / 2);
chaviwa0539262017-12-12 14:46:40 -0800170 Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint,
171 overlayColor);
Winson Chunga63bb842011-10-17 10:26:28 -0700172
Winson Chung9c99aee2011-11-14 14:12:54 -0800173 mNotificationManager = nManager;
Dan Sandler156e97f2014-01-30 11:14:07 -0500174 final long now = System.currentTimeMillis();
175
Winsone5591712016-02-18 13:45:54 -0800176 // Setup the notification
Chris Wren3745a3d2012-05-22 15:11:52 -0400177 mNotificationStyle = new Notification.BigPictureStyle()
Winsone5591712016-02-18 13:45:54 -0800178 .bigPicture(picture.createAshmemBitmap());
Chris Wren3745a3d2012-05-22 15:11:52 -0400179
Winsone5591712016-02-18 13:45:54 -0800180 // The public notification will show similar info but with the actual screenshot omitted
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500181 mPublicNotificationBuilder =
Alison Cichowlas08abf6d2018-01-22 20:46:38 -0500182 new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP)
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500183 .setContentTitle(r.getString(R.string.screenshot_saving_title))
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500184 .setSmallIcon(R.drawable.stat_notify_image)
185 .setCategory(Notification.CATEGORY_PROGRESS)
186 .setWhen(now)
187 .setShowWhen(true)
188 .setColor(r.getColor(
189 com.android.internal.R.color.system_notification_accent_color));
Julia Reynolds037d8082018-03-18 15:25:19 -0400190 SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder, true);
Dan Sandler156e97f2014-01-30 11:14:07 -0500191
Alison Cichowlas08abf6d2018-01-22 20:46:38 -0500192 mNotificationBuilder = new Notification.Builder(context,
193 NotificationChannels.SCREENSHOTS_HEADSUP)
Winsone5591712016-02-18 13:45:54 -0800194 .setContentTitle(r.getString(R.string.screenshot_saving_title))
Winsone5591712016-02-18 13:45:54 -0800195 .setSmallIcon(R.drawable.stat_notify_image)
196 .setWhen(now)
Dan Sandlerd52ea0c2016-04-14 10:16:28 -0400197 .setShowWhen(true)
Winsone5591712016-02-18 13:45:54 -0800198 .setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color))
199 .setStyle(mNotificationStyle)
200 .setPublicVersion(mPublicNotificationBuilder.build());
201 mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
Julia Reynolds037d8082018-03-18 15:25:19 -0400202 SystemUI.overrideNotificationAppName(context, mNotificationBuilder, true);
Dan Sandler156e97f2014-01-30 11:14:07 -0500203
Chris Wren5e6c0ff2017-01-05 12:57:06 -0500204 mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT,
205 mNotificationBuilder.build());
Winsone5591712016-02-18 13:45:54 -0800206
207 /**
208 * NOTE: The following code prepares the notification builder for updating the notification
209 * after the screenshot has been written to disk.
210 */
Winson Chung9c99aee2011-11-14 14:12:54 -0800211
212 // On the tablet, the large icon makes the notification appear as if it is clickable (and
213 // on small devices, the large icon is not shown) so defer showing the large icon until
214 // we compose the final post-save notification below.
Winson Chung1b87be42015-06-02 12:18:05 -0700215 mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
Chris Wren3745a3d2012-05-22 15:11:52 -0400216 // But we still don't set it for the expanded view, allowing the smallIcon to show here.
Dan Sandlerd63f9322015-05-06 15:18:49 -0400217 mNotificationStyle.bigLargeIcon((Bitmap) null);
Winson Chungc57ccf02011-10-13 15:04:59 -0700218 }
219
chaviwa0539262017-12-12 14:46:40 -0800220 /**
221 * Generates a new hardware bitmap with specified values, copying the content from the passed
222 * in bitmap.
223 */
224 private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
225 Paint paint, int color) {
John Reck519ad482018-02-12 17:08:48 -0800226 Picture picture = new Picture();
227 Canvas canvas = picture.beginRecording(width, height);
chaviwa0539262017-12-12 14:46:40 -0800228 canvas.drawColor(color);
229 canvas.drawBitmap(bitmap, matrix, paint);
John Reck519ad482018-02-12 17:08:48 -0800230 picture.endRecording();
231 return Bitmap.createBitmap(picture);
chaviwa0539262017-12-12 14:46:40 -0800232 }
233
Winson Chung9112ec32011-06-27 13:15:32 -0700234 @Override
Jeff Sharkey7bba5072018-11-02 14:37:07 -0600235 protected Void doInBackground(Void... paramsUnused) {
Winson Chung0e6232c2013-01-22 14:11:46 -0800236 if (isCancelled()) {
Winsonc7aa65b2016-03-10 12:52:27 -0800237 return null;
Winson Chung0e6232c2013-01-22 14:11:46 -0800238 }
Winson Chung9112ec32011-06-27 13:15:32 -0700239
Winson Chung36c9e292011-10-18 13:48:38 -0700240 // By default, AsyncTask sets the worker thread to have background thread priority, so bump
241 // it back up so that we save a little quicker.
242 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
243
Winsonc7aa65b2016-03-10 12:52:27 -0800244 Context context = mParams.context;
245 Bitmap image = mParams.image;
Chris Wren3745a3d2012-05-22 15:11:52 -0400246 Resources r = context.getResources();
Winson Chung9112ec32011-06-27 13:15:32 -0700247
Winson Chungd859fa32011-07-22 12:19:52 -0700248 try {
Winson Chung9112ec32011-06-27 13:15:32 -0700249 // Save the screenshot to the MediaStore
Jeff Sharkey7bba5072018-11-02 14:37:07 -0600250 final MediaStore.PendingParams params = new MediaStore.PendingParams(
251 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
252 params.setPrimaryDirectory(Environment.DIRECTORY_PICTURES);
253 params.setSecondaryDirectory(Environment.DIRECTORY_SCREENSHOTS);
254
255 final Uri uri = MediaStore.createPending(context, params);
256 final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
257 try {
258 try (OutputStream out = session.openOutputStream()) {
259 if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
260 throw new IOException("Failed to compress");
261 }
262 }
263 session.publish();
264 } catch (Exception e) {
265 session.abandon();
266 throw e;
267 } finally {
268 IoUtils.closeQuietly(session);
269 }
Winson Chung9112ec32011-06-27 13:15:32 -0700270
Winson Chung06ca2742018-06-29 12:26:49 -0700271 // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
272 // order to do some common work like dismissing the keyguard and sending
273 // closeSystemWindows
274
275 // Create a share intent, this will always go through the chooser activity first which
276 // should not trigger auto-enter PiP
Victoria Leasee45e1f22013-09-30 15:33:43 -0700277 String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
Winson Chung224848f2012-07-18 16:47:51 -0700278 String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
Chris Wren3745a3d2012-05-22 15:11:52 -0400279 Intent sharingIntent = new Intent(Intent.ACTION_SEND);
280 sharingIntent.setType("image/png");
281 sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
Alison Cichowlas09e5ea32018-09-07 15:52:24 -0400282 // Include URI in ClipData also, so that grantPermission picks it up.
283 // We don't use setData here because some apps interpret this as "to:".
284 ClipData clipdata = new ClipData(new ClipDescription("content",
285 new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
286 new ClipData.Item(uri));
287 sharingIntent.setClipData(clipdata);
Winson Chung224848f2012-07-18 16:47:51 -0700288 sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
Alison Cichowlas4f1297a2018-01-30 11:40:36 -0500289 sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Daniel Sandler046fddf2012-05-31 15:40:25 -0400290
Winson Chung06ca2742018-06-29 12:26:49 -0700291 PendingIntent chooserAction = PendingIntent.getBroadcast(context, 0,
292 new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
293 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
294 Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
295 chooserAction.getIntentSender())
Alison Cichowlas09e5ea32018-09-07 15:52:24 -0400296 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
297 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Winson Chung06ca2742018-06-29 12:26:49 -0700298
299 // Create a share action for the notification
Winson Chung6c454f62018-07-09 15:55:56 -0700300 PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
Winson Chung06ca2742018-06-29 12:26:49 -0700301 new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
302 .putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
303 .putExtra(EXTRA_DISALLOW_ENTER_PIP, true),
Winson Chung6c454f62018-07-09 15:55:56 -0700304 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Winsone5591712016-02-18 13:45:54 -0800305 Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
306 R.drawable.ic_screenshot_share,
307 r.getString(com.android.internal.R.string.share), shareAction);
308 mNotificationBuilder.addAction(shareActionBuilder.build());
Daniel Sandler046fddf2012-05-31 15:40:25 -0400309
Winson Chung06ca2742018-06-29 12:26:49 -0700310 // Create an edit intent, if a specific package is provided as the editor, then launch
311 // that directly
312 String editorPackage = context.getString(R.string.config_screenshotEditor);
Alison Cichowlas134f2cc2018-01-19 19:16:31 -0500313 Intent editIntent = new Intent(Intent.ACTION_EDIT);
Winson Chung06ca2742018-06-29 12:26:49 -0700314 if (!TextUtils.isEmpty(editorPackage)) {
315 editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
316 }
Alison Cichowlas134f2cc2018-01-19 19:16:31 -0500317 editIntent.setType("image/png");
Alison Cichowlas4f1297a2018-01-30 11:40:36 -0500318 editIntent.setData(uri);
319 editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
320 editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Alison Cichowlas134f2cc2018-01-19 19:16:31 -0500321
Winson Chung06ca2742018-06-29 12:26:49 -0700322 // Create a edit action
Winson Chung6c454f62018-07-09 15:55:56 -0700323 PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
Winson Chung06ca2742018-06-29 12:26:49 -0700324 new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
325 .putExtra(EXTRA_ACTION_INTENT, editIntent)
326 .putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null),
Winson Chung6c454f62018-07-09 15:55:56 -0700327 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Alison Cichowlas134f2cc2018-01-19 19:16:31 -0500328 Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
329 R.drawable.ic_screenshot_edit,
330 r.getString(com.android.internal.R.string.screenshot_edit), editAction);
331 mNotificationBuilder.addAction(editActionBuilder.build());
Mike Lockwood98377342011-07-26 13:55:16 -0400332
Alison Cichowlasf2806532018-03-06 19:08:18 -0500333 // Create a delete action for the notification
334 PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
335 new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
336 .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
337 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
338 Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
339 R.drawable.ic_screenshot_delete,
340 r.getString(com.android.internal.R.string.delete), deleteAction);
341 mNotificationBuilder.addAction(deleteActionBuilder.build());
342
Winsonc7aa65b2016-03-10 12:52:27 -0800343 mParams.imageUri = uri;
344 mParams.image = null;
345 mParams.errorMsgResId = 0;
Winson Chungd859fa32011-07-22 12:19:52 -0700346 } catch (Exception e) {
347 // IOException/UnsupportedOperationException may be thrown if external storage is not
348 // mounted
Dan Sandler150651b2017-06-14 10:43:07 -0400349 Slog.e(TAG, "unable to save screenshot", e);
Winsonc7aa65b2016-03-10 12:52:27 -0800350 mParams.clearImage();
351 mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
Winson Chung9112ec32011-06-27 13:15:32 -0700352 }
353
Winson Chungcd6a9ef2013-01-16 14:59:18 -0800354 // Recycle the bitmap data
355 if (image != null) {
356 image.recycle();
357 }
358
Winsonc7aa65b2016-03-10 12:52:27 -0800359 return null;
Romain Guy8279acb2011-11-29 13:56:25 -0800360 }
Winson Chung9112ec32011-06-27 13:15:32 -0700361
362 @Override
Winsonc7aa65b2016-03-10 12:52:27 -0800363 protected void onPostExecute(Void params) {
364 if (mParams.errorMsgResId != 0) {
Winson Chung9112ec32011-06-27 13:15:32 -0700365 // Show a message that we've failed to save the image to disk
Winsonc7aa65b2016-03-10 12:52:27 -0800366 GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager,
367 mParams.errorMsgResId);
Winson Chung9112ec32011-06-27 13:15:32 -0700368 } else {
Winson Chungc57ccf02011-10-13 15:04:59 -0700369 // Show the final notification to indicate screenshot saved
Winsonc7aa65b2016-03-10 12:52:27 -0800370 Context context = mParams.context;
Winsone5591712016-02-18 13:45:54 -0800371 Resources r = context.getResources();
Winson Chungc57ccf02011-10-13 15:04:59 -0700372
Winson Chung36c9e292011-10-18 13:48:38 -0700373 // Create the intent to show the screenshot in gallery
Romain Guy8279acb2011-11-29 13:56:25 -0800374 Intent launchIntent = new Intent(Intent.ACTION_VIEW);
Winsonc7aa65b2016-03-10 12:52:27 -0800375 launchIntent.setDataAndType(mParams.imageUri, "image/png");
Jeff Sharkey72b84a22016-12-01 17:10:27 -0700376 launchIntent.setFlags(
377 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
Winson Chung36c9e292011-10-18 13:48:38 -0700378
Dan Sandler156e97f2014-01-30 11:14:07 -0500379 final long now = System.currentTimeMillis();
380
Winsone5591712016-02-18 13:45:54 -0800381 // Update the text and the icon for the existing notification
382 mPublicNotificationBuilder
383 .setContentTitle(r.getString(R.string.screenshot_saved_title))
384 .setContentText(r.getString(R.string.screenshot_saved_text))
Winsonc7aa65b2016-03-10 12:52:27 -0800385 .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
Winsone5591712016-02-18 13:45:54 -0800386 .setWhen(now)
387 .setAutoCancel(true)
388 .setColor(context.getColor(
389 com.android.internal.R.color.system_notification_accent_color));
Winson Chungc57ccf02011-10-13 15:04:59 -0700390 mNotificationBuilder
Winson Chungc57ccf02011-10-13 15:04:59 -0700391 .setContentTitle(r.getString(R.string.screenshot_saved_title))
392 .setContentText(r.getString(R.string.screenshot_saved_text))
Winsonc7aa65b2016-03-10 12:52:27 -0800393 .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
Dan Sandler156e97f2014-01-30 11:14:07 -0500394 .setWhen(now)
Selim Cinek255dd042014-08-19 22:29:02 +0200395 .setAutoCancel(true)
Winsone5591712016-02-18 13:45:54 -0800396 .setColor(context.getColor(
397 com.android.internal.R.color.system_notification_accent_color))
398 .setPublicVersion(mPublicNotificationBuilder.build())
399 .setFlag(Notification.FLAG_NO_CLEAR, false);
Winson Chungc57ccf02011-10-13 15:04:59 -0700400
Chris Wren5e6c0ff2017-01-05 12:57:06 -0500401 mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT,
402 mNotificationBuilder.build());
Winson Chung9112ec32011-06-27 13:15:32 -0700403 }
Winsonc7aa65b2016-03-10 12:52:27 -0800404 mParams.finisher.run();
405 mParams.clearContext();
Romain Guy8279acb2011-11-29 13:56:25 -0800406 }
Winsone5591712016-02-18 13:45:54 -0800407
408 @Override
Winsonc7aa65b2016-03-10 12:52:27 -0800409 protected void onCancelled(Void params) {
410 // If we are cancelled while the task is running in the background, we may get null params.
411 // The finisher is expected to always be called back, so just use the baked-in params from
412 // the ctor in any case.
413 mParams.finisher.run();
414 mParams.clearImage();
415 mParams.clearContext();
Winsone5591712016-02-18 13:45:54 -0800416
417 // Cancel the posted notification
Chris Wren5e6c0ff2017-01-05 12:57:06 -0500418 mNotificationManager.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
Winsone5591712016-02-18 13:45:54 -0800419 }
Winson Chung9112ec32011-06-27 13:15:32 -0700420}
421
422/**
Winson Chung8858e6e2015-05-27 17:18:56 -0700423 * An AsyncTask that deletes an image from the media store in the background.
424 */
425class DeleteImageInBackgroundTask extends AsyncTask<Uri, Void, Void> {
Winson Chung8858e6e2015-05-27 17:18:56 -0700426 private Context mContext;
427
428 DeleteImageInBackgroundTask(Context context) {
429 mContext = context;
430 }
431
432 @Override
433 protected Void doInBackground(Uri... params) {
434 if (params.length != 1) return null;
435
436 Uri screenshotUri = params[0];
437 ContentResolver resolver = mContext.getContentResolver();
438 resolver.delete(screenshotUri, null, null);
439 return null;
440 }
441}
442
Winson Chung9112ec32011-06-27 13:15:32 -0700443class GlobalScreenshot {
Winson Chung8858e6e2015-05-27 17:18:56 -0700444 static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
Winson Chung06ca2742018-06-29 12:26:49 -0700445 static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
446 static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
447 static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
Adam Powelld4d34152015-04-17 12:13:40 -0700448
Winson Chung22ca0952011-10-20 19:44:32 -0700449 private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
450 private static final int SCREENSHOT_DROP_IN_DURATION = 430;
451 private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
452 private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
453 private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
454 private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
455 private static final float BACKGROUND_ALPHA = 0.5f;
456 private static final float SCREENSHOT_SCALE = 1f;
457 private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
458 private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
459 private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
460 private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
Chris Wrenf63185e2014-07-31 13:52:39 +0100461 private final int mPreviewWidth;
462 private final int mPreviewHeight;
Winson Chung9112ec32011-06-27 13:15:32 -0700463
464 private Context mContext;
Winson Chung9112ec32011-06-27 13:15:32 -0700465 private WindowManager mWindowManager;
466 private WindowManager.LayoutParams mWindowLayoutParams;
Winson Chungc57ccf02011-10-13 15:04:59 -0700467 private NotificationManager mNotificationManager;
Winson Chung9112ec32011-06-27 13:15:32 -0700468 private Display mDisplay;
469 private DisplayMetrics mDisplayMetrics;
Winson Chung9112ec32011-06-27 13:15:32 -0700470
471 private Bitmap mScreenBitmap;
472 private View mScreenshotLayout;
Muyuan Li6ca619f2016-03-08 13:30:42 -0800473 private ScreenshotSelectorView mScreenshotSelectorView;
Winson Chung9112ec32011-06-27 13:15:32 -0700474 private ImageView mBackgroundView;
Winson Chung9112ec32011-06-27 13:15:32 -0700475 private ImageView mScreenshotView;
Winson Chung22ca0952011-10-20 19:44:32 -0700476 private ImageView mScreenshotFlash;
Winson Chung9112ec32011-06-27 13:15:32 -0700477
478 private AnimatorSet mScreenshotAnimation;
479
Winson Chunga63bb842011-10-17 10:26:28 -0700480 private int mNotificationIconSize;
Winson Chunga63bb842011-10-17 10:26:28 -0700481 private float mBgPadding;
482 private float mBgPaddingScale;
483
Winsonc7aa65b2016-03-10 12:52:27 -0800484 private AsyncTask<Void, Void, Void> mSaveInBgTask;
Winson Chung0e6232c2013-01-22 14:11:46 -0800485
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800486 private MediaActionSound mCameraSound;
Winson Chung8d513ea2011-12-01 14:39:12 -0800487
Winson Chung9112ec32011-06-27 13:15:32 -0700488
489 /**
490 * @param context everything needs a context :(
491 */
492 public GlobalScreenshot(Context context) {
Winson Chunga63bb842011-10-17 10:26:28 -0700493 Resources r = context.getResources();
Winson Chung9112ec32011-06-27 13:15:32 -0700494 mContext = context;
Romain Guy8279acb2011-11-29 13:56:25 -0800495 LayoutInflater layoutInflater = (LayoutInflater)
Winson Chung9112ec32011-06-27 13:15:32 -0700496 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
497
498 // Inflate the screenshot layout
Romain Guy8279acb2011-11-29 13:56:25 -0800499 mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
Winson Chung9112ec32011-06-27 13:15:32 -0700500 mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
Winson Chung9112ec32011-06-27 13:15:32 -0700501 mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
Winson Chung22ca0952011-10-20 19:44:32 -0700502 mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
Muyuan Li6ca619f2016-03-08 13:30:42 -0800503 mScreenshotSelectorView = (ScreenshotSelectorView) mScreenshotLayout.findViewById(
504 R.id.global_screenshot_selector);
Winson Chung9112ec32011-06-27 13:15:32 -0700505 mScreenshotLayout.setFocusable(true);
Muyuan Li6ca619f2016-03-08 13:30:42 -0800506 mScreenshotSelectorView.setFocusable(true);
507 mScreenshotSelectorView.setFocusableInTouchMode(true);
Winson Chung9112ec32011-06-27 13:15:32 -0700508 mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
509 @Override
510 public boolean onTouch(View v, MotionEvent event) {
511 // Intercept and ignore all touch events
512 return true;
513 }
514 });
515
516 // Setup the window that we are going to use
Winson Chung9112ec32011-06-27 13:15:32 -0700517 mWindowLayoutParams = new WindowManager.LayoutParams(
518 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
Muyuan Li6ca619f2016-03-08 13:30:42 -0800519 WindowManager.LayoutParams.TYPE_SCREENSHOT,
Winson Chung9112ec32011-06-27 13:15:32 -0700520 WindowManager.LayoutParams.FLAG_FULLSCREEN
Winson Chung9112ec32011-06-27 13:15:32 -0700521 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
522 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
523 PixelFormat.TRANSLUCENT);
Winson Chung9112ec32011-06-27 13:15:32 -0700524 mWindowLayoutParams.setTitle("ScreenshotAnimation");
Adrian Roosd1229772018-03-21 14:46:10 +0100525 mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
Winson Chung9112ec32011-06-27 13:15:32 -0700526 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Winson Chungc57ccf02011-10-13 15:04:59 -0700527 mNotificationManager =
Winson Chung06ca2742018-06-29 12:26:49 -0700528 (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
Winson Chung9112ec32011-06-27 13:15:32 -0700529 mDisplay = mWindowManager.getDefaultDisplay();
Winson Chunga63bb842011-10-17 10:26:28 -0700530 mDisplayMetrics = new DisplayMetrics();
531 mDisplay.getRealMetrics(mDisplayMetrics);
532
533 // Get the various target sizes
Winson Chunga63bb842011-10-17 10:26:28 -0700534 mNotificationIconSize =
535 r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
Winson Chunga63bb842011-10-17 10:26:28 -0700536
537 // Scale has to account for both sides of the bg
538 mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
Winson Chung22ca0952011-10-20 19:44:32 -0700539 mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
Winson Chung8d513ea2011-12-01 14:39:12 -0800540
Chris Wrenf63185e2014-07-31 13:52:39 +0100541 // determine the optimal preview size
542 int panelWidth = 0;
543 try {
544 panelWidth = r.getDimensionPixelSize(R.dimen.notification_panel_width);
545 } catch (Resources.NotFoundException e) {
Dan Sandlerdeb0d112014-08-07 09:21:06 -0400546 }
547 if (panelWidth <= 0) {
548 // includes notification_panel_width==match_parent (-1)
Chris Wrenf63185e2014-07-31 13:52:39 +0100549 panelWidth = mDisplayMetrics.widthPixels;
550 }
551 mPreviewWidth = panelWidth;
552 mPreviewHeight = r.getDimensionPixelSize(R.dimen.notification_max_height);
553
Winson Chung8d513ea2011-12-01 14:39:12 -0800554 // Setup the Camera shutter sound
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800555 mCameraSound = new MediaActionSound();
556 mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
Winson Chung9112ec32011-06-27 13:15:32 -0700557 }
558
559 /**
560 * Creates a new worker thread and saves the screenshot to the media store.
561 */
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700562 private void saveScreenshotInWorkerThread(Runnable finisher) {
Winson Chung9112ec32011-06-27 13:15:32 -0700563 SaveImageInBackgroundData data = new SaveImageInBackgroundData();
564 data.context = mContext;
565 data.image = mScreenBitmap;
Winson Chunga63bb842011-10-17 10:26:28 -0700566 data.iconSize = mNotificationIconSize;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700567 data.finisher = finisher;
Chris Wrenf63185e2014-07-31 13:52:39 +0100568 data.previewWidth = mPreviewWidth;
569 data.previewheight = mPreviewHeight;
Winson Chung0e6232c2013-01-22 14:11:46 -0800570 if (mSaveInBgTask != null) {
571 mSaveInBgTask.cancel(false);
572 }
Winsone5591712016-02-18 13:45:54 -0800573 mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
Winsonc7aa65b2016-03-10 12:52:27 -0800574 .execute();
Winson Chung9112ec32011-06-27 13:15:32 -0700575 }
576
577 /**
Winson Chung9112ec32011-06-27 13:15:32 -0700578 * Takes a screenshot of the current display and shows an animation.
579 */
chaviwa69e0a72017-11-29 17:55:12 -0800580 private void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
581 Rect crop) {
582 int rot = mDisplay.getRotation();
583 int width = crop.width();
584 int height = crop.height();
Winson Chunga46d7782012-01-04 16:43:10 -0800585
586 // Take the screenshot
chaviwa69e0a72017-11-29 17:55:12 -0800587 mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
Winson Chunga46d7782012-01-04 16:43:10 -0800588 if (mScreenBitmap == null) {
Winsone5591712016-02-18 13:45:54 -0800589 notifyScreenshotError(mContext, mNotificationManager,
590 R.string.screenshot_failed_to_capture_text);
Winson Chunga46d7782012-01-04 16:43:10 -0800591 finisher.run();
592 return;
593 }
594
Winson Chunga63bb842011-10-17 10:26:28 -0700595 // Optimizations
596 mScreenBitmap.setHasAlpha(false);
597 mScreenBitmap.prepareToDraw();
598
Winson Chung9112ec32011-06-27 13:15:32 -0700599 // Start the post-screenshot animation
Winson Chunga63bb842011-10-17 10:26:28 -0700600 startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
601 statusBarVisible, navBarVisible);
Winson Chung9112ec32011-06-27 13:15:32 -0700602 }
603
Muyuan Li6ca619f2016-03-08 13:30:42 -0800604 void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
605 mDisplay.getRealMetrics(mDisplayMetrics);
chaviwa69e0a72017-11-29 17:55:12 -0800606 takeScreenshot(finisher, statusBarVisible, navBarVisible,
607 new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
Muyuan Li6ca619f2016-03-08 13:30:42 -0800608 }
609
610 /**
611 * Displays a screenshot selector
612 */
613 void takeScreenshotPartial(final Runnable finisher, final boolean statusBarVisible,
614 final boolean navBarVisible) {
615 mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
616 mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
617 @Override
618 public boolean onTouch(View v, MotionEvent event) {
619 ScreenshotSelectorView view = (ScreenshotSelectorView) v;
620 switch (event.getAction()) {
621 case MotionEvent.ACTION_DOWN:
622 view.startSelection((int) event.getX(), (int) event.getY());
623 return true;
624 case MotionEvent.ACTION_MOVE:
625 view.updateSelection((int) event.getX(), (int) event.getY());
626 return true;
627 case MotionEvent.ACTION_UP:
628 view.setVisibility(View.GONE);
629 mWindowManager.removeView(mScreenshotLayout);
630 final Rect rect = view.getSelectionRect();
631 if (rect != null) {
632 if (rect.width() != 0 && rect.height() != 0) {
633 // Need mScreenshotLayout to handle it after the view disappears
634 mScreenshotLayout.post(new Runnable() {
635 public void run() {
636 takeScreenshot(finisher, statusBarVisible, navBarVisible,
chaviwa69e0a72017-11-29 17:55:12 -0800637 rect);
Muyuan Li6ca619f2016-03-08 13:30:42 -0800638 }
639 });
640 }
641 }
642
643 view.stopSelection();
644 return true;
645 }
646
647 return false;
648 }
649 });
650 mScreenshotLayout.post(new Runnable() {
651 @Override
652 public void run() {
653 mScreenshotSelectorView.setVisibility(View.VISIBLE);
654 mScreenshotSelectorView.requestFocus();
655 }
656 });
657 }
658
659 /**
660 * Cancels screenshot request
661 */
662 void stopScreenshot() {
663 // If the selector layer still presents on screen, we remove it and resets its state.
664 if (mScreenshotSelectorView.getSelectionRect() != null) {
665 mWindowManager.removeView(mScreenshotLayout);
666 mScreenshotSelectorView.stopSelection();
667 }
668 }
Winson Chung9112ec32011-06-27 13:15:32 -0700669
670 /**
671 * Starts the animation after taking the screenshot
672 */
Winson Chunga63bb842011-10-17 10:26:28 -0700673 private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
674 boolean navBarVisible) {
Geoffrey Pitsched5de312017-06-27 11:42:12 -0400675 // If power save is on, show a toast so there is some visual indication that a screenshot
676 // has been taken.
677 PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
678 if (powerManager.isPowerSaveMode()) {
679 Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
680 }
681
Winson Chung9112ec32011-06-27 13:15:32 -0700682 // Add the view for the animation
683 mScreenshotView.setImageBitmap(mScreenBitmap);
684 mScreenshotLayout.requestFocus();
685
686 // Setup the animation with the screenshot just taken
687 if (mScreenshotAnimation != null) {
Dan Sandlerd9cd20b2015-11-13 15:01:44 -0500688 if (mScreenshotAnimation.isStarted()) {
689 mScreenshotAnimation.end();
690 }
Winson Chung0e6232c2013-01-22 14:11:46 -0800691 mScreenshotAnimation.removeAllListeners();
Winson Chung9112ec32011-06-27 13:15:32 -0700692 }
693
694 mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
Winson Chung22ca0952011-10-20 19:44:32 -0700695 ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
696 ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
Winson Chunga63bb842011-10-17 10:26:28 -0700697 statusBarVisible, navBarVisible);
Winson Chung9112ec32011-06-27 13:15:32 -0700698 mScreenshotAnimation = new AnimatorSet();
Winson Chung22ca0952011-10-20 19:44:32 -0700699 mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
Winson Chung9112ec32011-06-27 13:15:32 -0700700 mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
701 @Override
702 public void onAnimationEnd(Animator animation) {
703 // Save the screenshot once we have a bit of time now
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700704 saveScreenshotInWorkerThread(finisher);
Winson Chung9112ec32011-06-27 13:15:32 -0700705 mWindowManager.removeView(mScreenshotLayout);
Winson Chungcd6a9ef2013-01-16 14:59:18 -0800706
707 // Clear any references to the bitmap
708 mScreenBitmap = null;
709 mScreenshotView.setImageBitmap(null);
Winson Chung9112ec32011-06-27 13:15:32 -0700710 }
711 });
Winson Chunga63bb842011-10-17 10:26:28 -0700712 mScreenshotLayout.post(new Runnable() {
713 @Override
714 public void run() {
Winson Chung8d513ea2011-12-01 14:39:12 -0800715 // Play the shutter sound to notify that we've taken a screenshot
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800716 mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
Winson Chung8d513ea2011-12-01 14:39:12 -0800717
Romain Guy8279acb2011-11-29 13:56:25 -0800718 mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
719 mScreenshotView.buildLayer();
Winson Chunga63bb842011-10-17 10:26:28 -0700720 mScreenshotAnimation.start();
721 }
722 });
Winson Chung9112ec32011-06-27 13:15:32 -0700723 }
Winson Chung22ca0952011-10-20 19:44:32 -0700724 private ValueAnimator createScreenshotDropInAnimation() {
725 final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
726 / SCREENSHOT_DROP_IN_DURATION);
727 final float flashDurationPct = 2f * flashPeakDurationPct;
728 final Interpolator flashAlphaInterpolator = new Interpolator() {
729 @Override
730 public float getInterpolation(float x) {
731 // Flash the flash view in and out quickly
732 if (x <= flashDurationPct) {
733 return (float) Math.sin(Math.PI * (x / flashDurationPct));
734 }
735 return 0;
736 }
737 };
738 final Interpolator scaleInterpolator = new Interpolator() {
739 @Override
740 public float getInterpolation(float x) {
741 // We start scaling when the flash is at it's peak
742 if (x < flashPeakDurationPct) {
743 return 0;
744 }
745 return (x - flashDurationPct) / (1f - flashDurationPct);
746 }
747 };
Beth Thibodeaud98a9452019-03-08 14:32:51 -0500748
749 Resources r = mContext.getResources();
750 if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
751 == Configuration.UI_MODE_NIGHT_YES) {
752 mScreenshotView.getBackground().setTint(Color.BLACK);
753 } else {
754 mScreenshotView.getBackground().setTintList(null);
755 }
756
Winson Chung9112ec32011-06-27 13:15:32 -0700757 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
Winson Chung22ca0952011-10-20 19:44:32 -0700758 anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
Winson Chung9112ec32011-06-27 13:15:32 -0700759 anim.addListener(new AnimatorListenerAdapter() {
760 @Override
761 public void onAnimationStart(Animator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800762 mBackgroundView.setAlpha(0f);
Winson Chung9112ec32011-06-27 13:15:32 -0700763 mBackgroundView.setVisibility(View.VISIBLE);
Romain Guy8279acb2011-11-29 13:56:25 -0800764 mScreenshotView.setAlpha(0f);
765 mScreenshotView.setTranslationX(0f);
766 mScreenshotView.setTranslationY(0f);
767 mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
768 mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
769 mScreenshotView.setVisibility(View.VISIBLE);
770 mScreenshotFlash.setAlpha(0f);
Winson Chung22ca0952011-10-20 19:44:32 -0700771 mScreenshotFlash.setVisibility(View.VISIBLE);
772 }
773 @Override
774 public void onAnimationEnd(android.animation.Animator animation) {
775 mScreenshotFlash.setVisibility(View.GONE);
Winson Chung9112ec32011-06-27 13:15:32 -0700776 }
777 });
778 anim.addUpdateListener(new AnimatorUpdateListener() {
779 @Override
780 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800781 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700782 float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
Romain Guy8279acb2011-11-29 13:56:25 -0800783 - scaleInterpolator.getInterpolation(t)
Winson Chung22ca0952011-10-20 19:44:32 -0700784 * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
Romain Guy8279acb2011-11-29 13:56:25 -0800785 mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
786 mScreenshotView.setAlpha(t);
787 mScreenshotView.setScaleX(scaleT);
788 mScreenshotView.setScaleY(scaleT);
789 mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
Winson Chung9112ec32011-06-27 13:15:32 -0700790 }
791 });
792 return anim;
793 }
Winson Chung22ca0952011-10-20 19:44:32 -0700794 private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
Winson Chunga63bb842011-10-17 10:26:28 -0700795 boolean navBarVisible) {
796 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
Winson Chung22ca0952011-10-20 19:44:32 -0700797 anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
Winson Chung9112ec32011-06-27 13:15:32 -0700798 anim.addListener(new AnimatorListenerAdapter() {
799 @Override
800 public void onAnimationEnd(Animator animation) {
801 mBackgroundView.setVisibility(View.GONE);
Romain Guy8279acb2011-11-29 13:56:25 -0800802 mScreenshotView.setVisibility(View.GONE);
803 mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
Winson Chung9112ec32011-06-27 13:15:32 -0700804 }
805 });
Winson Chunga63bb842011-10-17 10:26:28 -0700806
807 if (!statusBarVisible || !navBarVisible) {
808 // There is no status bar/nav bar, so just fade the screenshot away in place
Winson Chung22ca0952011-10-20 19:44:32 -0700809 anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
Winson Chunga63bb842011-10-17 10:26:28 -0700810 anim.addUpdateListener(new AnimatorUpdateListener() {
811 @Override
812 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800813 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700814 float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
Romain Guy8279acb2011-11-29 13:56:25 -0800815 - t * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
816 mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
817 mScreenshotView.setAlpha(1f - t);
818 mScreenshotView.setScaleX(scaleT);
819 mScreenshotView.setScaleY(scaleT);
Winson Chunga63bb842011-10-17 10:26:28 -0700820 }
821 });
822 } else {
Winson Chung22ca0952011-10-20 19:44:32 -0700823 // In the case where there is a status bar, animate to the origin of the bar (top-left)
824 final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
825 / SCREENSHOT_DROP_OUT_DURATION;
826 final Interpolator scaleInterpolator = new Interpolator() {
827 @Override
828 public float getInterpolation(float x) {
829 if (x < scaleDurationPct) {
830 // Decelerate, and scale the input accordingly
831 return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
832 }
833 return 1f;
834 }
835 };
836
Winson Chunga63bb842011-10-17 10:26:28 -0700837 // Determine the bounds of how to scale
838 float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
839 float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
Winson Chung22ca0952011-10-20 19:44:32 -0700840 final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
841 final PointF finalPos = new PointF(
842 -halfScreenWidth + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
843 -halfScreenHeight + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
Winson Chunga63bb842011-10-17 10:26:28 -0700844
845 // Animate the screenshot to the status bar
Winson Chung22ca0952011-10-20 19:44:32 -0700846 anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
Winson Chunga63bb842011-10-17 10:26:28 -0700847 anim.addUpdateListener(new AnimatorUpdateListener() {
848 @Override
849 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800850 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700851 float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
Romain Guy8279acb2011-11-29 13:56:25 -0800852 - scaleInterpolator.getInterpolation(t)
Winson Chung22ca0952011-10-20 19:44:32 -0700853 * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
Romain Guy8279acb2011-11-29 13:56:25 -0800854 mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
855 mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
856 mScreenshotView.setScaleX(scaleT);
857 mScreenshotView.setScaleY(scaleT);
858 mScreenshotView.setTranslationX(t * finalPos.x);
859 mScreenshotView.setTranslationY(t * finalPos.y);
Winson Chunga63bb842011-10-17 10:26:28 -0700860 }
861 });
862 }
Winson Chung9112ec32011-06-27 13:15:32 -0700863 return anim;
864 }
Winson Chungc57ccf02011-10-13 15:04:59 -0700865
Winsone5591712016-02-18 13:45:54 -0800866 static void notifyScreenshotError(Context context, NotificationManager nManager, int msgResId) {
Winson Chungc57ccf02011-10-13 15:04:59 -0700867 Resources r = context.getResources();
Winsone5591712016-02-18 13:45:54 -0800868 String errorMsg = r.getString(msgResId);
Winson Chungc57ccf02011-10-13 15:04:59 -0700869
Winsone5591712016-02-18 13:45:54 -0800870 // Repurpose the existing notification to notify the user of the error
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500871 Notification.Builder b = new Notification.Builder(context, NotificationChannels.ALERTS)
Winson Chungc57ccf02011-10-13 15:04:59 -0700872 .setTicker(r.getString(R.string.screenshot_failed_title))
873 .setContentTitle(r.getString(R.string.screenshot_failed_title))
Winsone5591712016-02-18 13:45:54 -0800874 .setContentText(errorMsg)
Winson Chungb787a752011-10-19 18:31:53 -0700875 .setSmallIcon(R.drawable.stat_notify_image_error)
Winson Chungc57ccf02011-10-13 15:04:59 -0700876 .setWhen(System.currentTimeMillis())
Dan Sandler156e97f2014-01-30 11:14:07 -0500877 .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
Dan Sandler59f02582014-03-21 12:04:19 -0400878 .setCategory(Notification.CATEGORY_ERROR)
Selim Cinek255dd042014-08-19 22:29:02 +0200879 .setAutoCancel(true)
Alan Viverette4a357cd2015-03-18 18:37:18 -0700880 .setColor(context.getColor(
Selim Cinek255dd042014-08-19 22:29:02 +0200881 com.android.internal.R.color.system_notification_accent_color));
phweissec3d9ca2017-02-09 16:57:39 +0100882 final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
883 Context.DEVICE_POLICY_SERVICE);
884 final Intent intent = dpm.createAdminSupportIntent(
885 DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
886 if (intent != null) {
887 final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
888 context, 0, intent, 0, null, UserHandle.CURRENT);
889 b.setContentIntent(pendingIntent);
890 }
891
Julia Reynolds037d8082018-03-18 15:25:19 -0400892 SystemUI.overrideNotificationAppName(context, b, true);
Winsone5591712016-02-18 13:45:54 -0800893
894 Notification n = new Notification.BigTextStyle(b)
895 .bigText(errorMsg)
Winson Chung224848f2012-07-18 16:47:51 -0700896 .build();
Chris Wren5e6c0ff2017-01-05 12:57:06 -0500897 nManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
Winson Chungc57ccf02011-10-13 15:04:59 -0700898 }
Adam Powelld4d34152015-04-17 12:13:40 -0700899
900 /**
Winson Chung06ca2742018-06-29 12:26:49 -0700901 * Receiver to proxy the share or edit intent, used to clean up the notification and send
902 * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
Winson Chunged376a32017-09-07 14:05:42 -0700903 */
Winson Chung06ca2742018-06-29 12:26:49 -0700904 public static class ActionProxyReceiver extends BroadcastReceiver {
Winson Chunged376a32017-09-07 14:05:42 -0700905 @Override
Winson Chung06ca2742018-06-29 12:26:49 -0700906 public void onReceive(Context context, final Intent intent) {
907 Runnable startActivityRunnable = () -> {
908 ActivityManagerWrapper.getInstance().closeSystemWindows(
909 SYSTEM_DIALOG_REASON_SCREENSHOT);
Winson Chunged376a32017-09-07 14:05:42 -0700910
Winson Chung06ca2742018-06-29 12:26:49 -0700911 Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
912 if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
913 cancelScreenshotNotification(context);
914 }
915 ActivityOptions opts = ActivityOptions.makeBasic();
916 opts.setDisallowEnterPictureInPictureWhileLaunching(
917 intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
Winson Chung6c454f62018-07-09 15:55:56 -0700918 context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
Winson Chung06ca2742018-06-29 12:26:49 -0700919 };
920 StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
921 statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
922 true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
Winson Chunged376a32017-09-07 14:05:42 -0700923 }
924 }
925
926 /**
Winson Chung06ca2742018-06-29 12:26:49 -0700927 * Removes the notification for a screenshot after a share target is chosen.
Adam Powelld4d34152015-04-17 12:13:40 -0700928 */
929 public static class TargetChosenReceiver extends BroadcastReceiver {
930 @Override
931 public void onReceive(Context context, Intent intent) {
Winson Chung06ca2742018-06-29 12:26:49 -0700932 // Clear the notification only after the user has chosen a share action
933 cancelScreenshotNotification(context);
Adam Powelld4d34152015-04-17 12:13:40 -0700934 }
935 }
Winson Chung8858e6e2015-05-27 17:18:56 -0700936
937 /**
938 * Removes the last screenshot.
939 */
940 public static class DeleteScreenshotReceiver extends BroadcastReceiver {
941 @Override
942 public void onReceive(Context context, Intent intent) {
Winsone5591712016-02-18 13:45:54 -0800943 if (!intent.hasExtra(SCREENSHOT_URI_ID)) {
Winson Chung8858e6e2015-05-27 17:18:56 -0700944 return;
945 }
946
Winson Chung06ca2742018-06-29 12:26:49 -0700947 // Clear the notification when the image is deleted
948 cancelScreenshotNotification(context);
Winson Chung8858e6e2015-05-27 17:18:56 -0700949
950 // And delete the image from the media store
Winson Chung06ca2742018-06-29 12:26:49 -0700951 final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
Winson Chung8858e6e2015-05-27 17:18:56 -0700952 new DeleteImageInBackgroundTask(context).execute(uri);
953 }
954 }
Winson Chung06ca2742018-06-29 12:26:49 -0700955
956 private static void cancelScreenshotNotification(Context context) {
957 final NotificationManager nm =
958 (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
959 nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
960 }
Winson Chung9112ec32011-06-27 13:15:32 -0700961}