blob: fedd855a858e12424576d3897d5ab55252673833 [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;
Satakshid2010f22019-10-29 10:57:43 -070020import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
Miranda Kephartea2e67a2019-10-29 11:52:56 -040021import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
Miranda Kephart0148b282019-10-31 15:36:21 -040022import static android.view.View.VISIBLE;
Adrian Roosd1229772018-03-21 14:46:10 +010023import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
Alison Cichowlas3e340502018-08-07 17:15:01 -040024
Miranda Kephartea2e67a2019-10-29 11:52:56 -040025import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
Winson Chunged376a32017-09-07 14:05:42 -070026import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
27
Winson Chung9112ec32011-06-27 13:15:32 -070028import android.animation.Animator;
29import android.animation.AnimatorListenerAdapter;
30import android.animation.AnimatorSet;
Winson Chung9112ec32011-06-27 13:15:32 -070031import android.animation.ValueAnimator;
32import android.animation.ValueAnimator.AnimatorUpdateListener;
Miranda Kephartea2e67a2019-10-29 11:52:56 -040033import android.annotation.Nullable;
Satakshid2010f22019-10-29 10:57:43 -070034import android.app.ActivityManager;
Winson Chunged376a32017-09-07 14:05:42 -070035import android.app.ActivityOptions;
Winson Chungc57ccf02011-10-13 15:04:59 -070036import android.app.Notification;
37import android.app.NotificationManager;
38import android.app.PendingIntent;
Adrian Roosd1229772018-03-21 14:46:10 +010039import android.app.admin.DevicePolicyManager;
Adam Powelld4d34152015-04-17 12:13:40 -070040import android.content.BroadcastReceiver;
Alison Cichowlasf2806532018-03-06 19:08:18 -050041import android.content.ComponentName;
Winson Chung9112ec32011-06-27 13:15:32 -070042import android.content.Context;
Winson Chungc57ccf02011-10-13 15:04:59 -070043import android.content.Intent;
Beth Thibodeaud98a9452019-03-08 14:32:51 -050044import android.content.res.Configuration;
Winson Chungc57ccf02011-10-13 15:04:59 -070045import android.content.res.Resources;
Winson Chung9112ec32011-06-27 13:15:32 -070046import android.graphics.Bitmap;
Beth Thibodeaud98a9452019-03-08 14:32:51 -050047import android.graphics.Color;
Winson Chung9112ec32011-06-27 13:15:32 -070048import android.graphics.PixelFormat;
Winson Chunga63bb842011-10-17 10:26:28 -070049import android.graphics.PointF;
Muyuan Li6ca619f2016-03-08 13:30:42 -080050import android.graphics.Rect;
Eino-Ville Talvalae6909582012-03-01 11:01:38 -080051import android.media.MediaActionSound;
Winson Chung9112ec32011-06-27 13:15:32 -070052import android.net.Uri;
53import android.os.AsyncTask;
Miranda Kephartea2e67a2019-10-29 11:52:56 -040054import android.os.Handler;
55import android.os.Looper;
56import android.os.Message;
Geoffrey Pitsched5de312017-06-27 11:42:12 -040057import android.os.PowerManager;
Satakshid2010f22019-10-29 10:57:43 -070058import android.os.SystemClock;
phweissec3d9ca2017-02-09 16:57:39 +010059import android.os.UserHandle;
Miranda Kephartea2e67a2019-10-29 11:52:56 -040060import android.provider.DeviceConfig;
Winson Chung9112ec32011-06-27 13:15:32 -070061import android.util.DisplayMetrics;
Miranda Kephartea2e67a2019-10-29 11:52:56 -040062import android.util.Log;
Dan Sandler150651b2017-06-14 10:43:07 -040063import android.util.Slog;
Winson Chung9112ec32011-06-27 13:15:32 -070064import android.view.Display;
Winson Chung9112ec32011-06-27 13:15:32 -070065import android.view.LayoutInflater;
66import android.view.MotionEvent;
Mathias Agopian3866f0d2013-02-11 22:08:48 -080067import android.view.SurfaceControl;
Winson Chung9112ec32011-06-27 13:15:32 -070068import android.view.View;
69import android.view.ViewGroup;
70import android.view.WindowManager;
Winson Chung22ca0952011-10-20 19:44:32 -070071import android.view.animation.Interpolator;
Winson Chung9112ec32011-06-27 13:15:32 -070072import android.widget.ImageView;
Miranda Kephart0148b282019-10-31 15:36:21 -040073import android.widget.LinearLayout;
74import android.widget.TextView;
Geoffrey Pitsched5de312017-06-27 11:42:12 -040075import android.widget.Toast;
Alison Cichowlas3e340502018-08-07 17:15:01 -040076
Satakshid2010f22019-10-29 10:57:43 -070077import com.android.internal.annotations.VisibleForTesting;
Chris Wren5e6c0ff2017-01-05 12:57:06 -050078import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
Winson Chung9112ec32011-06-27 13:15:32 -070079import com.android.systemui.R;
Adrian Roose25c18d2016-06-17 15:59:49 -070080import com.android.systemui.SystemUI;
Satakshiaaf69532019-11-07 17:54:24 -080081import com.android.systemui.SystemUIFactory;
Dave Mankoffaefb3462019-11-01 14:21:45 -040082import com.android.systemui.dagger.qualifiers.MainResources;
Winson Chung06ca2742018-06-29 12:26:49 -070083import com.android.systemui.shared.system.ActivityManagerWrapper;
84import com.android.systemui.statusbar.phone.StatusBar;
Dan Sandler8e032e12017-01-25 13:41:38 -050085import com.android.systemui.util.NotificationChannels;
Alison Cichowlas3e340502018-08-07 17:15:01 -040086
Satakshid2010f22019-10-29 10:57:43 -070087import java.util.Collections;
Satakshid2010f22019-10-29 10:57:43 -070088import java.util.List;
Garfield Tana22423972019-11-04 17:40:26 -080089import java.util.Optional;
Satakshid2010f22019-10-29 10:57:43 -070090import java.util.concurrent.CompletableFuture;
Matt Pietal777c82d2019-06-11 15:47:10 -040091import java.util.concurrent.ExecutionException;
92import java.util.concurrent.TimeUnit;
93import java.util.concurrent.TimeoutException;
Aran Inkeb565fc2019-11-15 15:52:31 -050094import java.util.function.Consumer;
Matt Pietal777c82d2019-06-11 15:47:10 -040095
Dave Mankoffaefb3462019-11-01 14:21:45 -040096import javax.inject.Inject;
97import javax.inject.Singleton;
Matt Pietal777c82d2019-06-11 15:47:10 -040098
Garfield Tana22423972019-11-04 17:40:26 -080099import dagger.Lazy;
Winson Chung9112ec32011-06-27 13:15:32 -0700100
101/**
Sunny Goyalaf7cddd2019-11-05 17:49:17 -0800102 * Class for handling device screen shots
Winson Chung9112ec32011-06-27 13:15:32 -0700103 */
Sunny Goyalaf7cddd2019-11-05 17:49:17 -0800104@Singleton
105public class GlobalScreenshot {
Winson Chung0e6232c2013-01-22 14:11:46 -0800106
Sunny Goyalaf7cddd2019-11-05 17:49:17 -0800107 /**
108 * POD used in the AsyncTask which saves an image in the background.
109 */
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500110 static class SaveImageInBackgroundData {
Sunny Goyalaf7cddd2019-11-05 17:49:17 -0800111 public Context context;
112 public Bitmap image;
113 public Uri imageUri;
Aran Inkeb565fc2019-11-15 15:52:31 -0500114 public Consumer<Uri> finisher;
Miranda Kephart0148b282019-10-31 15:36:21 -0400115 public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
Sunny Goyalaf7cddd2019-11-05 17:49:17 -0800116 public int iconSize;
117 public int previewWidth;
118 public int previewheight;
119 public int errorMsgResId;
Winson Chung9112ec32011-06-27 13:15:32 -0700120
Sunny Goyalaf7cddd2019-11-05 17:49:17 -0800121 void clearImage() {
122 image = null;
123 imageUri = null;
124 iconSize = 0;
125 }
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500126
Sunny Goyalaf7cddd2019-11-05 17:49:17 -0800127 void clearContext() {
128 context = null;
129 }
Winson Chungc57ccf02011-10-13 15:04:59 -0700130 }
131
Miranda Kephart0148b282019-10-31 15:36:21 -0400132 abstract static class ActionsReadyListener {
133 abstract void onActionsReady(PendingIntent shareAction, PendingIntent editAction);
134 }
135
Satakshiaaf69532019-11-07 17:54:24 -0800136 // These strings are used for communicating the action invoked to
137 // ScreenshotNotificationSmartActionsProvider.
138 static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
139 static final String EXTRA_ID = "android:screenshot_id";
140 static final String ACTION_TYPE_DELETE = "Delete";
141 static final String ACTION_TYPE_SHARE = "Share";
142 static final String ACTION_TYPE_EDIT = "Edit";
143 static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
Winson Chung06ca2742018-06-29 12:26:49 -0700144 static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
Satakshiaaf69532019-11-07 17:54:24 -0800145
146 static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
Winson Chung06ca2742018-06-29 12:26:49 -0700147 static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
148 static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
Adam Powelld4d34152015-04-17 12:13:40 -0700149
Matt Pietal777c82d2019-06-11 15:47:10 -0400150 private static final String TAG = "GlobalScreenshot";
151
Winson Chung22ca0952011-10-20 19:44:32 -0700152 private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
153 private static final int SCREENSHOT_DROP_IN_DURATION = 430;
154 private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
155 private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
156 private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
157 private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
158 private static final float BACKGROUND_ALPHA = 0.5f;
159 private static final float SCREENSHOT_SCALE = 1f;
160 private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
161 private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400162 private static final float SCREENSHOT_CORNER_MIN_SCALE = SCREENSHOT_SCALE * 0.2f;
Winson Chung22ca0952011-10-20 19:44:32 -0700163 private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
164 private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400165 private static final float SCREENSHOT_CORNER_MIN_SCALE_OFFSET = .1f;
166 private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000;
167 private static final int MESSAGE_CORNER_TIMEOUT = 2;
Chris Wrenf63185e2014-07-31 13:52:39 +0100168 private final int mPreviewWidth;
169 private final int mPreviewHeight;
Winson Chung9112ec32011-06-27 13:15:32 -0700170
171 private Context mContext;
Winson Chung9112ec32011-06-27 13:15:32 -0700172 private WindowManager mWindowManager;
173 private WindowManager.LayoutParams mWindowLayoutParams;
Winson Chungc57ccf02011-10-13 15:04:59 -0700174 private NotificationManager mNotificationManager;
Winson Chung9112ec32011-06-27 13:15:32 -0700175 private Display mDisplay;
176 private DisplayMetrics mDisplayMetrics;
Winson Chung9112ec32011-06-27 13:15:32 -0700177
178 private Bitmap mScreenBitmap;
179 private View mScreenshotLayout;
Muyuan Li6ca619f2016-03-08 13:30:42 -0800180 private ScreenshotSelectorView mScreenshotSelectorView;
Winson Chung9112ec32011-06-27 13:15:32 -0700181 private ImageView mBackgroundView;
Winson Chung9112ec32011-06-27 13:15:32 -0700182 private ImageView mScreenshotView;
Winson Chung22ca0952011-10-20 19:44:32 -0700183 private ImageView mScreenshotFlash;
Miranda Kephart0148b282019-10-31 15:36:21 -0400184 private LinearLayout mActionsView;
185 private TextView mShareAction;
186 private TextView mEditAction;
187 private TextView mScrollAction;
Winson Chung9112ec32011-06-27 13:15:32 -0700188
189 private AnimatorSet mScreenshotAnimation;
190
Winson Chunga63bb842011-10-17 10:26:28 -0700191 private int mNotificationIconSize;
Winson Chunga63bb842011-10-17 10:26:28 -0700192 private float mBgPadding;
193 private float mBgPaddingScale;
194
Winsonc7aa65b2016-03-10 12:52:27 -0800195 private AsyncTask<Void, Void, Void> mSaveInBgTask;
Winson Chung0e6232c2013-01-22 14:11:46 -0800196
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800197 private MediaActionSound mCameraSound;
Winson Chung8d513ea2011-12-01 14:39:12 -0800198
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400199 private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
200 @Override
201 public void handleMessage(Message msg) {
202 switch (msg.what) {
203 case MESSAGE_CORNER_TIMEOUT:
204 GlobalScreenshot.this.clearScreenshot();
205 break;
206 default:
207 break;
208 }
209 }
210 };
211
Winson Chung9112ec32011-06-27 13:15:32 -0700212 /**
213 * @param context everything needs a context :(
214 */
Dave Mankoffaefb3462019-11-01 14:21:45 -0400215 @Inject
216 public GlobalScreenshot(Context context, @MainResources Resources resources,
217 LayoutInflater layoutInflater) {
Winson Chung9112ec32011-06-27 13:15:32 -0700218 mContext = context;
Winson Chung9112ec32011-06-27 13:15:32 -0700219
220 // Inflate the screenshot layout
Romain Guy8279acb2011-11-29 13:56:25 -0800221 mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
Dave Mankoffaefb3462019-11-01 14:21:45 -0400222 mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_background);
223 mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot);
Miranda Kephart0148b282019-10-31 15:36:21 -0400224 mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
225
226 mShareAction = (TextView) layoutInflater.inflate(
227 R.layout.global_screenshot_action_chip, mActionsView, false);
228 mEditAction = (TextView) layoutInflater.inflate(
229 R.layout.global_screenshot_action_chip, mActionsView, false);
230 mScrollAction = (TextView) layoutInflater.inflate(
231 R.layout.global_screenshot_action_chip, mActionsView, false);
232
233 mShareAction.setText(com.android.internal.R.string.share);
234 mEditAction.setText(com.android.internal.R.string.screenshot_edit);
235 mScrollAction.setText("Scroll"); // TODO (mkephart): Add to resources and translate
236
237 mActionsView.addView(mShareAction);
238 mActionsView.addView(mEditAction);
239 mActionsView.addView(mScrollAction);
240
Dave Mankoffaefb3462019-11-01 14:21:45 -0400241 mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
242 mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
Winson Chung9112ec32011-06-27 13:15:32 -0700243 mScreenshotLayout.setFocusable(true);
Muyuan Li6ca619f2016-03-08 13:30:42 -0800244 mScreenshotSelectorView.setFocusable(true);
245 mScreenshotSelectorView.setFocusableInTouchMode(true);
Miranda Kephart0148b282019-10-31 15:36:21 -0400246 mScreenshotLayout.setOnTouchListener((v, event) -> {
247 // Intercept and ignore all touch events
248 return true;
Winson Chung9112ec32011-06-27 13:15:32 -0700249 });
250
251 // Setup the window that we are going to use
Winson Chung9112ec32011-06-27 13:15:32 -0700252 mWindowLayoutParams = new WindowManager.LayoutParams(
253 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
Muyuan Li6ca619f2016-03-08 13:30:42 -0800254 WindowManager.LayoutParams.TYPE_SCREENSHOT,
Winson Chung9112ec32011-06-27 13:15:32 -0700255 WindowManager.LayoutParams.FLAG_FULLSCREEN
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500256 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
257 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
Winson Chung9112ec32011-06-27 13:15:32 -0700258 PixelFormat.TRANSLUCENT);
Winson Chung9112ec32011-06-27 13:15:32 -0700259 mWindowLayoutParams.setTitle("ScreenshotAnimation");
Adrian Roosd1229772018-03-21 14:46:10 +0100260 mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
Winson Chung9112ec32011-06-27 13:15:32 -0700261 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Winson Chungc57ccf02011-10-13 15:04:59 -0700262 mNotificationManager =
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500263 (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
Winson Chung9112ec32011-06-27 13:15:32 -0700264 mDisplay = mWindowManager.getDefaultDisplay();
Winson Chunga63bb842011-10-17 10:26:28 -0700265 mDisplayMetrics = new DisplayMetrics();
266 mDisplay.getRealMetrics(mDisplayMetrics);
267
268 // Get the various target sizes
Winson Chunga63bb842011-10-17 10:26:28 -0700269 mNotificationIconSize =
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500270 resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
Winson Chunga63bb842011-10-17 10:26:28 -0700271
272 // Scale has to account for both sides of the bg
Dave Mankoffaefb3462019-11-01 14:21:45 -0400273 mBgPadding = (float) resources.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500274 mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
Winson Chung8d513ea2011-12-01 14:39:12 -0800275
Chris Wrenf63185e2014-07-31 13:52:39 +0100276 // determine the optimal preview size
277 int panelWidth = 0;
278 try {
Dave Mankoffaefb3462019-11-01 14:21:45 -0400279 panelWidth = resources.getDimensionPixelSize(R.dimen.notification_panel_width);
Chris Wrenf63185e2014-07-31 13:52:39 +0100280 } catch (Resources.NotFoundException e) {
Dan Sandlerdeb0d112014-08-07 09:21:06 -0400281 }
282 if (panelWidth <= 0) {
283 // includes notification_panel_width==match_parent (-1)
Chris Wrenf63185e2014-07-31 13:52:39 +0100284 panelWidth = mDisplayMetrics.widthPixels;
285 }
286 mPreviewWidth = panelWidth;
Dave Mankoffaefb3462019-11-01 14:21:45 -0400287 mPreviewHeight = resources.getDimensionPixelSize(R.dimen.notification_max_height);
Chris Wrenf63185e2014-07-31 13:52:39 +0100288
Winson Chung8d513ea2011-12-01 14:39:12 -0800289 // Setup the Camera shutter sound
Eino-Ville Talvalae6909582012-03-01 11:01:38 -0800290 mCameraSound = new MediaActionSound();
291 mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
Winson Chung9112ec32011-06-27 13:15:32 -0700292 }
293
294 /**
295 * Creates a new worker thread and saves the screenshot to the media store.
296 */
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400297 private void saveScreenshotInWorkerThread(
Miranda Kephart0148b282019-10-31 15:36:21 -0400298 Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
Winson Chung9112ec32011-06-27 13:15:32 -0700299 SaveImageInBackgroundData data = new SaveImageInBackgroundData();
300 data.context = mContext;
301 data.image = mScreenBitmap;
Winson Chunga63bb842011-10-17 10:26:28 -0700302 data.iconSize = mNotificationIconSize;
Dianne Hackbornfc8fa632011-08-17 16:20:47 -0700303 data.finisher = finisher;
Miranda Kephart0148b282019-10-31 15:36:21 -0400304 data.mActionsReadyListener = actionsReadyListener;
Chris Wrenf63185e2014-07-31 13:52:39 +0100305 data.previewWidth = mPreviewWidth;
306 data.previewheight = mPreviewHeight;
Winson Chung0e6232c2013-01-22 14:11:46 -0800307 if (mSaveInBgTask != null) {
308 mSaveInBgTask.cancel(false);
309 }
Winsone5591712016-02-18 13:45:54 -0800310 mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
Winsonc7aa65b2016-03-10 12:52:27 -0800311 .execute();
Winson Chung9112ec32011-06-27 13:15:32 -0700312 }
313
Aran Inkeb565fc2019-11-15 15:52:31 -0500314 private void saveScreenshotInWorkerThread(Consumer<Uri> finisher) {
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400315 saveScreenshotInWorkerThread(finisher, null);
316 }
317
Winson Chung9112ec32011-06-27 13:15:32 -0700318 /**
Winson Chung9112ec32011-06-27 13:15:32 -0700319 * Takes a screenshot of the current display and shows an animation.
320 */
Aran Inkeb565fc2019-11-15 15:52:31 -0500321 private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
322 boolean navBarVisible, Rect crop) {
chaviwa69e0a72017-11-29 17:55:12 -0800323 int rot = mDisplay.getRotation();
324 int width = crop.width();
325 int height = crop.height();
Winson Chunga46d7782012-01-04 16:43:10 -0800326
327 // Take the screenshot
chaviwa69e0a72017-11-29 17:55:12 -0800328 mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
Winson Chunga46d7782012-01-04 16:43:10 -0800329 if (mScreenBitmap == null) {
Winsone5591712016-02-18 13:45:54 -0800330 notifyScreenshotError(mContext, mNotificationManager,
331 R.string.screenshot_failed_to_capture_text);
Aran Inkeb565fc2019-11-15 15:52:31 -0500332 finisher.accept(null);
Winson Chunga46d7782012-01-04 16:43:10 -0800333 return;
334 }
335
Winson Chunga63bb842011-10-17 10:26:28 -0700336 // Optimizations
337 mScreenBitmap.setHasAlpha(false);
338 mScreenBitmap.prepareToDraw();
339
Winson Chung9112ec32011-06-27 13:15:32 -0700340 // Start the post-screenshot animation
Winson Chunga63bb842011-10-17 10:26:28 -0700341 startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
342 statusBarVisible, navBarVisible);
Winson Chung9112ec32011-06-27 13:15:32 -0700343 }
344
Aran Inkeb565fc2019-11-15 15:52:31 -0500345 void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
Muyuan Li6ca619f2016-03-08 13:30:42 -0800346 mDisplay.getRealMetrics(mDisplayMetrics);
chaviwa69e0a72017-11-29 17:55:12 -0800347 takeScreenshot(finisher, statusBarVisible, navBarVisible,
348 new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
Muyuan Li6ca619f2016-03-08 13:30:42 -0800349 }
350
351 /**
352 * Displays a screenshot selector
353 */
Aran Inkeb565fc2019-11-15 15:52:31 -0500354 void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
Muyuan Li6ca619f2016-03-08 13:30:42 -0800355 final boolean navBarVisible) {
356 mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
357 mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
358 @Override
359 public boolean onTouch(View v, MotionEvent event) {
360 ScreenshotSelectorView view = (ScreenshotSelectorView) v;
361 switch (event.getAction()) {
362 case MotionEvent.ACTION_DOWN:
363 view.startSelection((int) event.getX(), (int) event.getY());
364 return true;
365 case MotionEvent.ACTION_MOVE:
366 view.updateSelection((int) event.getX(), (int) event.getY());
367 return true;
368 case MotionEvent.ACTION_UP:
369 view.setVisibility(View.GONE);
370 mWindowManager.removeView(mScreenshotLayout);
371 final Rect rect = view.getSelectionRect();
372 if (rect != null) {
373 if (rect.width() != 0 && rect.height() != 0) {
374 // Need mScreenshotLayout to handle it after the view disappears
375 mScreenshotLayout.post(new Runnable() {
376 public void run() {
377 takeScreenshot(finisher, statusBarVisible, navBarVisible,
chaviwa69e0a72017-11-29 17:55:12 -0800378 rect);
Muyuan Li6ca619f2016-03-08 13:30:42 -0800379 }
380 });
381 }
382 }
383
384 view.stopSelection();
385 return true;
386 }
387
388 return false;
389 }
390 });
391 mScreenshotLayout.post(new Runnable() {
392 @Override
393 public void run() {
394 mScreenshotSelectorView.setVisibility(View.VISIBLE);
395 mScreenshotSelectorView.requestFocus();
396 }
397 });
398 }
399
400 /**
401 * Cancels screenshot request
402 */
403 void stopScreenshot() {
404 // If the selector layer still presents on screen, we remove it and resets its state.
405 if (mScreenshotSelectorView.getSelectionRect() != null) {
406 mWindowManager.removeView(mScreenshotLayout);
407 mScreenshotSelectorView.stopSelection();
408 }
409 }
Winson Chung9112ec32011-06-27 13:15:32 -0700410
411 /**
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400412 * Clears current screenshot
413 */
414 private void clearScreenshot() {
415 if (mScreenshotLayout.isAttachedToWindow()) {
416 mWindowManager.removeView(mScreenshotLayout);
417 }
418
419 // Clear any references to the bitmap
420 mScreenBitmap = null;
421 mScreenshotView.setImageBitmap(null);
Miranda Kephart0148b282019-10-31 15:36:21 -0400422 mActionsView.setVisibility(View.GONE);
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400423 mBackgroundView.setVisibility(View.GONE);
424 mScreenshotView.setVisibility(View.GONE);
425 mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
426 }
427
428 /**
Winson Chung9112ec32011-06-27 13:15:32 -0700429 * Starts the animation after taking the screenshot
430 */
Aran Inkeb565fc2019-11-15 15:52:31 -0500431 private void startAnimation(final Consumer<Uri> finisher, int w, int h,
432 boolean statusBarVisible, boolean navBarVisible) {
Geoffrey Pitsched5de312017-06-27 11:42:12 -0400433 // If power save is on, show a toast so there is some visual indication that a screenshot
434 // has been taken.
435 PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
436 if (powerManager.isPowerSaveMode()) {
437 Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
438 }
439
Winson Chung9112ec32011-06-27 13:15:32 -0700440 // Add the view for the animation
441 mScreenshotView.setImageBitmap(mScreenBitmap);
442 mScreenshotLayout.requestFocus();
443
444 // Setup the animation with the screenshot just taken
445 if (mScreenshotAnimation != null) {
Dan Sandlerd9cd20b2015-11-13 15:01:44 -0500446 if (mScreenshotAnimation.isStarted()) {
447 mScreenshotAnimation.end();
448 }
Winson Chung0e6232c2013-01-22 14:11:46 -0800449 mScreenshotAnimation.removeAllListeners();
Winson Chung9112ec32011-06-27 13:15:32 -0700450 }
451
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400452 boolean useCornerFlow =
453 DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
Winson Chung9112ec32011-06-27 13:15:32 -0700454 mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
Winson Chung22ca0952011-10-20 19:44:32 -0700455 ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400456 ValueAnimator screenshotFadeOutAnim = useCornerFlow
457 ? createScreenshotToCornerAnimation(w, h)
458 : createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
Winson Chung9112ec32011-06-27 13:15:32 -0700459 mScreenshotAnimation = new AnimatorSet();
Winson Chung22ca0952011-10-20 19:44:32 -0700460 mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
Winson Chung9112ec32011-06-27 13:15:32 -0700461 mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
462 @Override
463 public void onAnimationEnd(Animator animation) {
464 // Save the screenshot once we have a bit of time now
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400465 if (!useCornerFlow) {
466 saveScreenshotInWorkerThread(finisher);
467 clearScreenshot();
468 } else {
Miranda Kephart0148b282019-10-31 15:36:21 -0400469 saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
470 @Override
471 void onActionsReady(PendingIntent shareAction, PendingIntent editAction) {
472 mScreenshotHandler.post(() ->
473 createScreenshotActionsShadeAnimation(shareAction, editAction)
474 .start());
475 }
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400476 });
477 mScreenshotHandler.sendMessageDelayed(
478 mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
479 SCREENSHOT_CORNER_TIMEOUT_MILLIS);
480 }
Winson Chung9112ec32011-06-27 13:15:32 -0700481 }
482 });
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400483 mScreenshotHandler.post(() -> {
484 // Play the shutter sound to notify that we've taken a screenshot
485 mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
Winson Chung8d513ea2011-12-01 14:39:12 -0800486
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400487 mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
488 mScreenshotView.buildLayer();
489 mScreenshotAnimation.start();
Winson Chunga63bb842011-10-17 10:26:28 -0700490 });
Winson Chung9112ec32011-06-27 13:15:32 -0700491 }
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500492
Winson Chung22ca0952011-10-20 19:44:32 -0700493 private ValueAnimator createScreenshotDropInAnimation() {
494 final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
495 / SCREENSHOT_DROP_IN_DURATION);
496 final float flashDurationPct = 2f * flashPeakDurationPct;
497 final Interpolator flashAlphaInterpolator = new Interpolator() {
498 @Override
499 public float getInterpolation(float x) {
500 // Flash the flash view in and out quickly
501 if (x <= flashDurationPct) {
502 return (float) Math.sin(Math.PI * (x / flashDurationPct));
503 }
504 return 0;
505 }
506 };
507 final Interpolator scaleInterpolator = new Interpolator() {
508 @Override
509 public float getInterpolation(float x) {
510 // We start scaling when the flash is at it's peak
511 if (x < flashPeakDurationPct) {
512 return 0;
513 }
514 return (x - flashDurationPct) / (1f - flashDurationPct);
515 }
516 };
Beth Thibodeaud98a9452019-03-08 14:32:51 -0500517
518 Resources r = mContext.getResources();
519 if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
520 == Configuration.UI_MODE_NIGHT_YES) {
521 mScreenshotView.getBackground().setTint(Color.BLACK);
522 } else {
523 mScreenshotView.getBackground().setTintList(null);
524 }
525
Winson Chung9112ec32011-06-27 13:15:32 -0700526 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
Winson Chung22ca0952011-10-20 19:44:32 -0700527 anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
Winson Chung9112ec32011-06-27 13:15:32 -0700528 anim.addListener(new AnimatorListenerAdapter() {
529 @Override
530 public void onAnimationStart(Animator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800531 mBackgroundView.setAlpha(0f);
Winson Chung9112ec32011-06-27 13:15:32 -0700532 mBackgroundView.setVisibility(View.VISIBLE);
Romain Guy8279acb2011-11-29 13:56:25 -0800533 mScreenshotView.setAlpha(0f);
534 mScreenshotView.setTranslationX(0f);
535 mScreenshotView.setTranslationY(0f);
536 mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
537 mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
538 mScreenshotView.setVisibility(View.VISIBLE);
539 mScreenshotFlash.setAlpha(0f);
Winson Chung22ca0952011-10-20 19:44:32 -0700540 mScreenshotFlash.setVisibility(View.VISIBLE);
541 }
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500542
Winson Chung22ca0952011-10-20 19:44:32 -0700543 @Override
544 public void onAnimationEnd(android.animation.Animator animation) {
545 mScreenshotFlash.setVisibility(View.GONE);
Winson Chung9112ec32011-06-27 13:15:32 -0700546 }
547 });
548 anim.addUpdateListener(new AnimatorUpdateListener() {
549 @Override
550 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800551 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700552 float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500553 - scaleInterpolator.getInterpolation(t)
Winson Chung22ca0952011-10-20 19:44:32 -0700554 * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
Romain Guy8279acb2011-11-29 13:56:25 -0800555 mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
556 mScreenshotView.setAlpha(t);
557 mScreenshotView.setScaleX(scaleT);
558 mScreenshotView.setScaleY(scaleT);
559 mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
Winson Chung9112ec32011-06-27 13:15:32 -0700560 }
561 });
562 return anim;
563 }
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500564
Winson Chung22ca0952011-10-20 19:44:32 -0700565 private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
Winson Chunga63bb842011-10-17 10:26:28 -0700566 boolean navBarVisible) {
567 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
Winson Chung22ca0952011-10-20 19:44:32 -0700568 anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
Winson Chung9112ec32011-06-27 13:15:32 -0700569 anim.addListener(new AnimatorListenerAdapter() {
570 @Override
571 public void onAnimationEnd(Animator animation) {
572 mBackgroundView.setVisibility(View.GONE);
Romain Guy8279acb2011-11-29 13:56:25 -0800573 mScreenshotView.setVisibility(View.GONE);
574 mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
Winson Chung9112ec32011-06-27 13:15:32 -0700575 }
576 });
Winson Chunga63bb842011-10-17 10:26:28 -0700577
578 if (!statusBarVisible || !navBarVisible) {
579 // There is no status bar/nav bar, so just fade the screenshot away in place
Winson Chung22ca0952011-10-20 19:44:32 -0700580 anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
Winson Chunga63bb842011-10-17 10:26:28 -0700581 anim.addUpdateListener(new AnimatorUpdateListener() {
582 @Override
583 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800584 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700585 float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500586 - t * (SCREENSHOT_DROP_IN_MIN_SCALE
587 - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
Romain Guy8279acb2011-11-29 13:56:25 -0800588 mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
589 mScreenshotView.setAlpha(1f - t);
590 mScreenshotView.setScaleX(scaleT);
591 mScreenshotView.setScaleY(scaleT);
Winson Chunga63bb842011-10-17 10:26:28 -0700592 }
593 });
594 } else {
Winson Chung22ca0952011-10-20 19:44:32 -0700595 // In the case where there is a status bar, animate to the origin of the bar (top-left)
596 final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
597 / SCREENSHOT_DROP_OUT_DURATION;
598 final Interpolator scaleInterpolator = new Interpolator() {
599 @Override
600 public float getInterpolation(float x) {
601 if (x < scaleDurationPct) {
602 // Decelerate, and scale the input accordingly
603 return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
604 }
605 return 1f;
606 }
607 };
608
Winson Chunga63bb842011-10-17 10:26:28 -0700609 // Determine the bounds of how to scale
610 float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
611 float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
Winson Chung22ca0952011-10-20 19:44:32 -0700612 final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
613 final PointF finalPos = new PointF(
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500614 -halfScreenWidth
615 + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
616 -halfScreenHeight
617 + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
Winson Chunga63bb842011-10-17 10:26:28 -0700618
619 // Animate the screenshot to the status bar
Winson Chung22ca0952011-10-20 19:44:32 -0700620 anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
Winson Chunga63bb842011-10-17 10:26:28 -0700621 anim.addUpdateListener(new AnimatorUpdateListener() {
622 @Override
623 public void onAnimationUpdate(ValueAnimator animation) {
Romain Guy8279acb2011-11-29 13:56:25 -0800624 float t = (Float) animation.getAnimatedValue();
Winson Chung22ca0952011-10-20 19:44:32 -0700625 float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500626 - scaleInterpolator.getInterpolation(t)
Winson Chung22ca0952011-10-20 19:44:32 -0700627 * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
Romain Guy8279acb2011-11-29 13:56:25 -0800628 mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
629 mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
630 mScreenshotView.setScaleX(scaleT);
631 mScreenshotView.setScaleY(scaleT);
632 mScreenshotView.setTranslationX(t * finalPos.x);
633 mScreenshotView.setTranslationY(t * finalPos.y);
Winson Chunga63bb842011-10-17 10:26:28 -0700634 }
635 });
636 }
Winson Chung9112ec32011-06-27 13:15:32 -0700637 return anim;
638 }
Winson Chungc57ccf02011-10-13 15:04:59 -0700639
Miranda Kephartea2e67a2019-10-29 11:52:56 -0400640 private ValueAnimator createScreenshotToCornerAnimation(int w, int h) {
641 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
642 anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
643
644 final float scaleDurationPct =
645 (float) SCREENSHOT_DROP_OUT_SCALE_DURATION / 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
657 // Determine the bounds of how to scale
658 float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
659 float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
660 final float offsetPct = SCREENSHOT_CORNER_MIN_SCALE_OFFSET;
661 final PointF finalPos = new PointF(
662 -halfScreenWidth + (SCREENSHOT_CORNER_MIN_SCALE + offsetPct) * halfScreenWidth,
663 halfScreenHeight - (SCREENSHOT_CORNER_MIN_SCALE + offsetPct) * halfScreenHeight);
664
665 // Animate the screenshot to the bottom left corner
666 anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
667 anim.addUpdateListener(animation -> {
668 float t = (Float) animation.getAnimatedValue();
669 float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
670 - scaleInterpolator.getInterpolation(t)
671 * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_CORNER_MIN_SCALE);
672 mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
673 mScreenshotView.setScaleX(scaleT);
674 mScreenshotView.setScaleY(scaleT);
675 mScreenshotView.setTranslationX(t * finalPos.x);
676 mScreenshotView.setTranslationY(t * finalPos.y);
677 });
678 return anim;
679 }
680
Miranda Kephart0148b282019-10-31 15:36:21 -0400681 private ValueAnimator createScreenshotActionsShadeAnimation(
682 PendingIntent shareAction, PendingIntent editAction) {
683 ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
684 mActionsView.setY(mDisplayMetrics.heightPixels);
685 mActionsView.setVisibility(VISIBLE);
686 mActionsView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
687 float actionsViewHeight = mActionsView.getMeasuredHeight();
688 float screenshotStartHeight = mScreenshotView.getTranslationY();
689
690 animator.addUpdateListener(animation -> {
691 float t = animation.getAnimatedFraction();
692 mScreenshotView.setTranslationY(screenshotStartHeight - actionsViewHeight * t);
693 mActionsView.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t);
694 });
695 animator.addListener(new AnimatorListenerAdapter() {
696 @Override
697 public void onAnimationEnd(Animator animation) {
698 super.onAnimationEnd(animation);
699 mScreenshotView.requestFocus();
700 mShareAction.setOnClickListener(v -> {
701 try {
702 shareAction.send();
703 clearScreenshot();
704 } catch (PendingIntent.CanceledException e) {
705 Log.e(TAG, "Share intent cancelled", e);
706 }
707 });
708 mEditAction.setOnClickListener(v -> {
709 try {
710 editAction.send();
711 clearScreenshot();
712 } catch (PendingIntent.CanceledException e) {
713 Log.e(TAG, "Edit intent cancelled", e);
714 }
715 });
716 Toast scrollNotImplemented = Toast.makeText(
717 mContext, "Not implemented", Toast.LENGTH_SHORT);
718 mScrollAction.setOnClickListener(v -> scrollNotImplemented.show());
719 }
720 });
721 return animator;
722 }
723
Winsone5591712016-02-18 13:45:54 -0800724 static void notifyScreenshotError(Context context, NotificationManager nManager, int msgResId) {
Winson Chungc57ccf02011-10-13 15:04:59 -0700725 Resources r = context.getResources();
Winsone5591712016-02-18 13:45:54 -0800726 String errorMsg = r.getString(msgResId);
Winson Chungc57ccf02011-10-13 15:04:59 -0700727
Winsone5591712016-02-18 13:45:54 -0800728 // Repurpose the existing notification to notify the user of the error
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500729 Notification.Builder b = new Notification.Builder(context, NotificationChannels.ALERTS)
Miranda Kephartca6c5eb2019-11-14 12:44:11 -0500730 .setTicker(r.getString(R.string.screenshot_failed_title))
731 .setContentTitle(r.getString(R.string.screenshot_failed_title))
732 .setContentText(errorMsg)
733 .setSmallIcon(R.drawable.stat_notify_image_error)
734 .setWhen(System.currentTimeMillis())
735 .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
736 .setCategory(Notification.CATEGORY_ERROR)
737 .setAutoCancel(true)
738 .setColor(context.getColor(
Selim Cinek255dd042014-08-19 22:29:02 +0200739 com.android.internal.R.color.system_notification_accent_color));
phweissec3d9ca2017-02-09 16:57:39 +0100740 final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
741 Context.DEVICE_POLICY_SERVICE);
742 final Intent intent = dpm.createAdminSupportIntent(
743 DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
744 if (intent != null) {
745 final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
746 context, 0, intent, 0, null, UserHandle.CURRENT);
747 b.setContentIntent(pendingIntent);
748 }
749
Julia Reynolds037d8082018-03-18 15:25:19 -0400750 SystemUI.overrideNotificationAppName(context, b, true);
Winsone5591712016-02-18 13:45:54 -0800751
752 Notification n = new Notification.BigTextStyle(b)
753 .bigText(errorMsg)
Winson Chung224848f2012-07-18 16:47:51 -0700754 .build();
Chris Wren5e6c0ff2017-01-05 12:57:06 -0500755 nManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
Winson Chungc57ccf02011-10-13 15:04:59 -0700756 }
Adam Powelld4d34152015-04-17 12:13:40 -0700757
Satakshid2010f22019-10-29 10:57:43 -0700758 @VisibleForTesting
Satakshiaaf69532019-11-07 17:54:24 -0800759 static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
Satakshid2010f22019-10-29 10:57:43 -0700760 Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
Satakshiaaf69532019-11-07 17:54:24 -0800761 boolean smartActionsEnabled, boolean isManagedProfile) {
Satakshid2010f22019-10-29 10:57:43 -0700762 if (!smartActionsEnabled) {
763 Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
764 return CompletableFuture.completedFuture(Collections.emptyList());
765 }
766 if (image.getConfig() != Bitmap.Config.HARDWARE) {
767 Slog.w(TAG, String.format(
768 "Bitmap expected: Hardware, Bitmap found: %s. Returning empty list.",
769 image.getConfig()));
770 return CompletableFuture.completedFuture(Collections.emptyList());
771 }
772
773 Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
774 CompletableFuture<List<Notification.Action>> smartActionsFuture;
Satakshiaaf69532019-11-07 17:54:24 -0800775 long startTimeMs = SystemClock.uptimeMillis();
Satakshid2010f22019-10-29 10:57:43 -0700776 try {
777 ActivityManager.RunningTaskInfo runningTask =
778 ActivityManagerWrapper.getInstance().getRunningTask();
779 ComponentName componentName =
780 (runningTask != null && runningTask.topActivity != null)
781 ? runningTask.topActivity
782 : new ComponentName("", "");
Satakshiaaf69532019-11-07 17:54:24 -0800783 smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
Satakshid2010f22019-10-29 10:57:43 -0700784 componentName,
785 isManagedProfile);
786 } catch (Throwable e) {
Satakshiaaf69532019-11-07 17:54:24 -0800787 long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
Satakshid2010f22019-10-29 10:57:43 -0700788 smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
789 Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
Satakshiaaf69532019-11-07 17:54:24 -0800790 notifyScreenshotOp(screenshotId, smartActionsProvider,
791 ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
792 ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
793 waitTimeMs);
Satakshid2010f22019-10-29 10:57:43 -0700794 }
795 return smartActionsFuture;
796 }
797
798 @VisibleForTesting
Satakshiaaf69532019-11-07 17:54:24 -0800799 static List<Notification.Action> getSmartActions(String screenshotId,
800 CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
801 ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
802 long startTimeMs = SystemClock.uptimeMillis();
Satakshid2010f22019-10-29 10:57:43 -0700803 try {
Satakshid2010f22019-10-29 10:57:43 -0700804 List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
805 TimeUnit.MILLISECONDS);
Satakshiaaf69532019-11-07 17:54:24 -0800806 long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
Satakshid2010f22019-10-29 10:57:43 -0700807 Slog.d(TAG, String.format("Wait time for smart actions: %d ms",
Satakshiaaf69532019-11-07 17:54:24 -0800808 waitTimeMs));
809 notifyScreenshotOp(screenshotId, smartActionsProvider,
810 ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
811 ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
812 waitTimeMs);
Satakshid2010f22019-10-29 10:57:43 -0700813 return actions;
814 } catch (Throwable e) {
Satakshiaaf69532019-11-07 17:54:24 -0800815 long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
816 Slog.d(TAG, "Failed to obtain screenshot notification smart actions.", e);
817 ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
818 (e instanceof TimeoutException)
819 ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
820 : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
821 notifyScreenshotOp(screenshotId, smartActionsProvider,
822 ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
823 status, waitTimeMs);
Satakshid2010f22019-10-29 10:57:43 -0700824 return Collections.emptyList();
825 }
826 }
827
Satakshiaaf69532019-11-07 17:54:24 -0800828 static void notifyScreenshotOp(String screenshotId,
829 ScreenshotNotificationSmartActionsProvider smartActionsProvider,
830 ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
831 ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
832 try {
833 smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
834 } catch (Throwable e) {
835 Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
836 }
837 }
838
839 static void notifyScreenshotAction(Context context, String screenshotId, String action,
840 boolean isSmartAction) {
841 try {
842 ScreenshotNotificationSmartActionsProvider provider =
843 SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
844 context, THREAD_POOL_EXECUTOR, new Handler());
845 provider.notifyAction(screenshotId, action, isSmartAction);
846 } catch (Throwable e) {
847 Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
848 }
849 }
850
Adam Powelld4d34152015-04-17 12:13:40 -0700851 /**
Winson Chung06ca2742018-06-29 12:26:49 -0700852 * Receiver to proxy the share or edit intent, used to clean up the notification and send
853 * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
Winson Chunged376a32017-09-07 14:05:42 -0700854 */
Winson Chung06ca2742018-06-29 12:26:49 -0700855 public static class ActionProxyReceiver extends BroadcastReceiver {
Matt Pietal777c82d2019-06-11 15:47:10 -0400856 static final int CLOSE_WINDOWS_TIMEOUT_MILLIS = 3000;
Dave Mankoffaefb3462019-11-01 14:21:45 -0400857 private final StatusBar mStatusBar;
858
859 @Inject
Garfield Tana22423972019-11-04 17:40:26 -0800860 public ActionProxyReceiver(Optional<Lazy<StatusBar>> statusBarLazy) {
861 Lazy<StatusBar> statusBar = statusBarLazy.orElse(null);
862 mStatusBar = statusBar != null ? statusBar.get() : null;
Dave Mankoffaefb3462019-11-01 14:21:45 -0400863 }
Matt Pietal777c82d2019-06-11 15:47:10 -0400864
Winson Chunged376a32017-09-07 14:05:42 -0700865 @Override
Winson Chung06ca2742018-06-29 12:26:49 -0700866 public void onReceive(Context context, final Intent intent) {
867 Runnable startActivityRunnable = () -> {
Matt Pietal777c82d2019-06-11 15:47:10 -0400868 try {
869 ActivityManagerWrapper.getInstance().closeSystemWindows(
870 SYSTEM_DIALOG_REASON_SCREENSHOT).get(
871 CLOSE_WINDOWS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
872 } catch (TimeoutException | InterruptedException | ExecutionException e) {
873 Slog.e(TAG, "Unable to share screenshot", e);
874 return;
875 }
Winson Chunged376a32017-09-07 14:05:42 -0700876
Winson Chung06ca2742018-06-29 12:26:49 -0700877 Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
878 if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
879 cancelScreenshotNotification(context);
880 }
881 ActivityOptions opts = ActivityOptions.makeBasic();
882 opts.setDisallowEnterPictureInPictureWhileLaunching(
883 intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
Winson Chung6c454f62018-07-09 15:55:56 -0700884 context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
Winson Chung06ca2742018-06-29 12:26:49 -0700885 };
Dave Mankoffaefb3462019-11-01 14:21:45 -0400886
Garfield Tana22423972019-11-04 17:40:26 -0800887 if (mStatusBar != null) {
888 mStatusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
889 true /* dismissShade */, true /* afterKeyguardGone */,
890 true /* deferred */);
891 } else {
892 startActivityRunnable.run();
893 }
Satakshiaaf69532019-11-07 17:54:24 -0800894
895 if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
896 String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) ? ACTION_TYPE_EDIT
897 : ACTION_TYPE_SHARE;
898 notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
899 actionType, false);
900 }
Winson Chunged376a32017-09-07 14:05:42 -0700901 }
902 }
903
904 /**
Winson Chung06ca2742018-06-29 12:26:49 -0700905 * Removes the notification for a screenshot after a share target is chosen.
Adam Powelld4d34152015-04-17 12:13:40 -0700906 */
907 public static class TargetChosenReceiver extends BroadcastReceiver {
908 @Override
909 public void onReceive(Context context, Intent intent) {
Winson Chung06ca2742018-06-29 12:26:49 -0700910 // Clear the notification only after the user has chosen a share action
911 cancelScreenshotNotification(context);
Adam Powelld4d34152015-04-17 12:13:40 -0700912 }
913 }
Winson Chung8858e6e2015-05-27 17:18:56 -0700914
915 /**
916 * Removes the last screenshot.
917 */
918 public static class DeleteScreenshotReceiver extends BroadcastReceiver {
919 @Override
920 public void onReceive(Context context, Intent intent) {
Winsone5591712016-02-18 13:45:54 -0800921 if (!intent.hasExtra(SCREENSHOT_URI_ID)) {
Winson Chung8858e6e2015-05-27 17:18:56 -0700922 return;
923 }
924
Winson Chung06ca2742018-06-29 12:26:49 -0700925 // Clear the notification when the image is deleted
926 cancelScreenshotNotification(context);
Winson Chung8858e6e2015-05-27 17:18:56 -0700927
928 // And delete the image from the media store
Winson Chung06ca2742018-06-29 12:26:49 -0700929 final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
Winson Chung8858e6e2015-05-27 17:18:56 -0700930 new DeleteImageInBackgroundTask(context).execute(uri);
Satakshiaaf69532019-11-07 17:54:24 -0800931 if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
932 notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
933 ACTION_TYPE_DELETE,
934 false);
935 }
936 }
937 }
938
939 /**
940 * Executes the smart action tapped by the user in the notification.
941 */
942 public static class SmartActionsReceiver extends BroadcastReceiver {
943 @Override
944 public void onReceive(Context context, Intent intent) {
945 PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
946 ActivityOptions opts = ActivityOptions.makeBasic();
947 context.startActivityAsUser(actionIntent.getIntent(), opts.toBundle(),
948 UserHandle.CURRENT);
949
950 Slog.d(TAG, "Screenshot notification smart action is invoked.");
951 notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
952 intent.getStringExtra(EXTRA_ACTION_TYPE),
953 true);
Winson Chung8858e6e2015-05-27 17:18:56 -0700954 }
955 }
Winson Chung06ca2742018-06-29 12:26:49 -0700956
957 private static void cancelScreenshotNotification(Context context) {
958 final NotificationManager nm =
959 (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
960 nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
961 }
Winson Chung9112ec32011-06-27 13:15:32 -0700962}