• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package com.android.systemui.screenshot;
18 
19 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
20 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
21 
22 import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
23 import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
24 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
25 import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
26 import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
27 import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
28 import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
29 import static com.android.systemui.screenshot.LogConfig.logTag;
30 import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
31 import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT;
32 
33 import static java.util.Objects.requireNonNull;
34 
35 import android.animation.Animator;
36 import android.animation.AnimatorListenerAdapter;
37 import android.annotation.MainThread;
38 import android.annotation.NonNull;
39 import android.annotation.Nullable;
40 import android.app.ActivityManager;
41 import android.app.ActivityOptions;
42 import android.app.ExitTransitionCoordinator;
43 import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
44 import android.app.ICompatCameraControlCallback;
45 import android.app.Notification;
46 import android.app.assist.AssistContent;
47 import android.content.BroadcastReceiver;
48 import android.content.ComponentName;
49 import android.content.Context;
50 import android.content.Intent;
51 import android.content.IntentFilter;
52 import android.content.pm.ActivityInfo;
53 import android.content.res.Configuration;
54 import android.graphics.Bitmap;
55 import android.graphics.Insets;
56 import android.graphics.Rect;
57 import android.hardware.display.DisplayManager;
58 import android.media.AudioAttributes;
59 import android.media.AudioSystem;
60 import android.media.MediaPlayer;
61 import android.net.Uri;
62 import android.os.Bundle;
63 import android.os.Process;
64 import android.os.RemoteException;
65 import android.os.UserHandle;
66 import android.os.UserManager;
67 import android.provider.Settings;
68 import android.util.DisplayMetrics;
69 import android.util.Log;
70 import android.util.Pair;
71 import android.view.Display;
72 import android.view.IRemoteAnimationFinishedCallback;
73 import android.view.IRemoteAnimationRunner;
74 import android.view.KeyEvent;
75 import android.view.LayoutInflater;
76 import android.view.RemoteAnimationAdapter;
77 import android.view.RemoteAnimationTarget;
78 import android.view.ScrollCaptureResponse;
79 import android.view.View;
80 import android.view.ViewRootImpl;
81 import android.view.ViewTreeObserver;
82 import android.view.WindowInsets;
83 import android.view.WindowManager;
84 import android.view.WindowManagerGlobal;
85 import android.view.accessibility.AccessibilityManager;
86 import android.widget.Toast;
87 import android.window.OnBackInvokedCallback;
88 import android.window.OnBackInvokedDispatcher;
89 import android.window.WindowContext;
90 
91 import androidx.concurrent.futures.CallbackToFutureAdapter;
92 
93 import com.android.internal.app.ChooserActivity;
94 import com.android.internal.logging.UiEventLogger;
95 import com.android.internal.policy.PhoneWindow;
96 import com.android.settingslib.applications.InterestingConfigChanges;
97 import com.android.systemui.R;
98 import com.android.systemui.broadcast.BroadcastSender;
99 import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
100 import com.android.systemui.dagger.qualifiers.Main;
101 import com.android.systemui.flags.FeatureFlags;
102 import com.android.systemui.flags.Flags;
103 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
104 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
105 import com.android.systemui.settings.DisplayTracker;
106 import com.android.systemui.util.Assert;
107 
108 import com.google.common.util.concurrent.ListenableFuture;
109 
110 import java.io.File;
111 import java.util.List;
112 import java.util.concurrent.CancellationException;
113 import java.util.concurrent.ExecutionException;
114 import java.util.concurrent.Executor;
115 import java.util.concurrent.ExecutorService;
116 import java.util.concurrent.Executors;
117 import java.util.concurrent.Future;
118 import java.util.function.Consumer;
119 import java.util.function.Supplier;
120 
121 import javax.inject.Inject;
122 
123 /**
124  * Controls the state and flow for screenshots.
125  */
126 public class ScreenshotController {
127     private static final String TAG = logTag(ScreenshotController.class);
128 
129     private ScrollCaptureResponse mLastScrollCaptureResponse;
130     private ListenableFuture<ScrollCaptureResponse> mLastScrollCaptureRequest;
131 
132     /**
133      * This is effectively a no-op, but we need something non-null to pass in, in order to
134      * successfully override the pending activity entrance animation.
135      */
136     static final IRemoteAnimationRunner.Stub SCREENSHOT_REMOTE_RUNNER =
137             new IRemoteAnimationRunner.Stub() {
138                 @Override
139                 public void onAnimationStart(
140                         @WindowManager.TransitionOldType int transit,
141                         RemoteAnimationTarget[] apps,
142                         RemoteAnimationTarget[] wallpapers,
143                         RemoteAnimationTarget[] nonApps,
144                         final IRemoteAnimationFinishedCallback finishedCallback) {
145                     try {
146                         finishedCallback.onAnimationFinished();
147                     } catch (RemoteException e) {
148                         Log.e(TAG, "Error finishing screenshot remote animation", e);
149                     }
150                 }
151 
152                 @Override
153                 public void onAnimationCancelled(boolean isKeyguardOccluded) {
154                 }
155             };
156 
157     /**
158      * POD used in the AsyncTask which saves an image in the background.
159      */
160     static class SaveImageInBackgroundData {
161         public Bitmap image;
162         public Consumer<Uri> finisher;
163         public ScreenshotController.ActionsReadyListener mActionsReadyListener;
164         public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener;
165         public UserHandle owner;
166 
clearImage()167         void clearImage() {
168             image = null;
169         }
170     }
171 
172     /**
173      * Structure returned by the SaveImageInBackgroundTask
174      */
175     static class SavedImageData {
176         public Uri uri;
177         public Supplier<ActionTransition> shareTransition;
178         public Supplier<ActionTransition> editTransition;
179         public Notification.Action deleteAction;
180         public List<Notification.Action> smartActions;
181         public Notification.Action quickShareAction;
182         public UserHandle owner;
183         public String subject;  // Title for sharing
184 
185         /**
186          * POD for shared element transition.
187          */
188         static class ActionTransition {
189             public Bundle bundle;
190             public Notification.Action action;
191             public Runnable onCancelRunnable;
192         }
193 
194         /**
195          * Used to reset the return data on error
196          */
reset()197         public void reset() {
198             uri = null;
199             shareTransition = null;
200             editTransition = null;
201             deleteAction = null;
202             smartActions = null;
203             quickShareAction = null;
204             subject = null;
205         }
206     }
207 
208     /**
209      * Structure returned by the QueryQuickShareInBackgroundTask
210      */
211     static class QuickShareData {
212         public Notification.Action quickShareAction;
213 
214         /**
215          * Used to reset the return data on error
216          */
reset()217         public void reset() {
218             quickShareAction = null;
219         }
220     }
221 
222     interface ActionsReadyListener {
onActionsReady(ScreenshotController.SavedImageData imageData)223         void onActionsReady(ScreenshotController.SavedImageData imageData);
224     }
225 
226     interface QuickShareActionReadyListener {
onActionsReady(ScreenshotController.QuickShareData quickShareData)227         void onActionsReady(ScreenshotController.QuickShareData quickShareData);
228     }
229 
230     interface TransitionDestination {
231         /**
232          * Allows the long screenshot activity to call back with a destination location (the bounds
233          * on screen of the destination for the transitioning view) and a Runnable to be run once
234          * the transition animation is complete.
235          */
setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd)236         void setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd);
237     }
238 
239     // These strings are used for communicating the action invoked to
240     // ScreenshotNotificationSmartActionsProvider.
241     static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
242     static final String EXTRA_ID = "android:screenshot_id";
243     static final String ACTION_TYPE_DELETE = "Delete";
244     static final String ACTION_TYPE_SHARE = "Share";
245     static final String ACTION_TYPE_EDIT = "Edit";
246     static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
247     static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition";
248     static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
249 
250     static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
251     static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
252     static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
253 
254     // From WizardManagerHelper.java
255     private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
256 
257     private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
258 
259     private final WindowContext mContext;
260     private final FeatureFlags mFlags;
261     private final ScreenshotNotificationsController mNotificationsController;
262     private final ScreenshotSmartActions mScreenshotSmartActions;
263     private final UiEventLogger mUiEventLogger;
264     private final ImageExporter mImageExporter;
265     private final ImageCapture mImageCapture;
266     private final Executor mMainExecutor;
267     private final ExecutorService mBgExecutor;
268     private final BroadcastSender mBroadcastSender;
269 
270     private final WindowManager mWindowManager;
271     private final WindowManager.LayoutParams mWindowLayoutParams;
272     private final AccessibilityManager mAccessibilityManager;
273     private final ListenableFuture<MediaPlayer> mCameraSound;
274     private final ScrollCaptureClient mScrollCaptureClient;
275     private final PhoneWindow mWindow;
276     private final DisplayManager mDisplayManager;
277     private final DisplayTracker mDisplayTracker;
278     private final ScrollCaptureController mScrollCaptureController;
279     private final LongScreenshotData mLongScreenshotHolder;
280     private final boolean mIsLowRamDevice;
281     private final ScreenshotNotificationSmartActionsProvider
282             mScreenshotNotificationSmartActionsProvider;
283     private final TimeoutHandler mScreenshotHandler;
284     private final ActionIntentExecutor mActionExecutor;
285     private final UserManager mUserManager;
286     private final AssistContentRequester mAssistContentRequester;
287 
288     private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
289         if (DEBUG_INPUT) {
290             Log.d(TAG, "Predictive Back callback dispatched");
291         }
292         respondToBack();
293     };
294 
295     private ScreenshotView mScreenshotView;
296     private final MessageContainerController mMessageContainerController;
297     private Bitmap mScreenBitmap;
298     private SaveImageInBackgroundTask mSaveInBgTask;
299     private boolean mScreenshotTakenInPortrait;
300     private boolean mBlockAttach;
301 
302     private Animator mScreenshotAnimation;
303     private RequestCallback mCurrentRequestCallback;
304     private String mPackageName = "";
305     private BroadcastReceiver mCopyBroadcastReceiver;
306 
307     /** Tracks config changes that require re-creating UI */
308     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
309             ActivityInfo.CONFIG_ORIENTATION
310                     | ActivityInfo.CONFIG_LAYOUT_DIRECTION
311                     | ActivityInfo.CONFIG_LOCALE
312                     | ActivityInfo.CONFIG_UI_MODE
313                     | ActivityInfo.CONFIG_SCREEN_LAYOUT
314                     | ActivityInfo.CONFIG_ASSETS_PATHS);
315 
316     @Inject
ScreenshotController( Context context, FeatureFlags flags, ScreenshotSmartActions screenshotSmartActions, ScreenshotNotificationsController screenshotNotificationsController, ScrollCaptureClient scrollCaptureClient, UiEventLogger uiEventLogger, ImageExporter imageExporter, ImageCapture imageCapture, @Main Executor mainExecutor, ScrollCaptureController scrollCaptureController, LongScreenshotData longScreenshotHolder, ActivityManager activityManager, TimeoutHandler timeoutHandler, BroadcastSender broadcastSender, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, ActionIntentExecutor actionExecutor, UserManager userManager, AssistContentRequester assistContentRequester, MessageContainerController messageContainerController, DisplayTracker displayTracker )317     ScreenshotController(
318             Context context,
319             FeatureFlags flags,
320             ScreenshotSmartActions screenshotSmartActions,
321             ScreenshotNotificationsController screenshotNotificationsController,
322             ScrollCaptureClient scrollCaptureClient,
323             UiEventLogger uiEventLogger,
324             ImageExporter imageExporter,
325             ImageCapture imageCapture,
326             @Main Executor mainExecutor,
327             ScrollCaptureController scrollCaptureController,
328             LongScreenshotData longScreenshotHolder,
329             ActivityManager activityManager,
330             TimeoutHandler timeoutHandler,
331             BroadcastSender broadcastSender,
332             ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
333             ActionIntentExecutor actionExecutor,
334             UserManager userManager,
335             AssistContentRequester assistContentRequester,
336             MessageContainerController messageContainerController,
337             DisplayTracker displayTracker
338     ) {
339         mScreenshotSmartActions = screenshotSmartActions;
340         mNotificationsController = screenshotNotificationsController;
341         mScrollCaptureClient = scrollCaptureClient;
342         mUiEventLogger = uiEventLogger;
343         mImageExporter = imageExporter;
344         mImageCapture = imageCapture;
345         mMainExecutor = mainExecutor;
346         mScrollCaptureController = scrollCaptureController;
347         mLongScreenshotHolder = longScreenshotHolder;
348         mIsLowRamDevice = activityManager.isLowRamDevice();
349         mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
350         mBgExecutor = Executors.newSingleThreadExecutor();
351         mBroadcastSender = broadcastSender;
352 
353         mScreenshotHandler = timeoutHandler;
354         mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
355         mScreenshotHandler.setOnTimeoutRunnable(() -> {
356             if (DEBUG_UI) {
357                 Log.d(TAG, "Corner timeout hit");
358             }
359             dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT);
360         });
361 
362         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
363         mDisplayTracker = displayTracker;
364         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
365         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
366         mWindowManager = mContext.getSystemService(WindowManager.class);
367         mFlags = flags;
368         mActionExecutor = actionExecutor;
369         mUserManager = userManager;
370         mMessageContainerController = messageContainerController;
371         mAssistContentRequester = assistContentRequester;
372 
373         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
374 
375         // Setup the window that we are going to use
376         mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
377         mWindowLayoutParams.setTitle("ScreenshotAnimation");
378 
379         mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
380         mWindow.setWindowManager(mWindowManager, null, null);
381 
382         mConfigChanges.applyNewConfig(context.getResources());
383         reloadAssets();
384 
385         // Setup the Camera shutter sound
386         mCameraSound = loadCameraSound();
387 
388         mCopyBroadcastReceiver = new BroadcastReceiver() {
389             @Override
390             public void onReceive(Context context, Intent intent) {
391                 if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
392                     dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
393                 }
394             }
395         };
396         mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
397                         ClipboardOverlayController.COPY_OVERLAY_ACTION),
398                 ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
399     }
400 
handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, RequestCallback requestCallback)401     void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
402             RequestCallback requestCallback) {
403         Assert.isMainThread();
404         mCurrentRequestCallback = requestCallback;
405         if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
406             Rect bounds = getFullScreenRect();
407             screenshot.setBitmap(
408                     mImageCapture.captureDisplay(mDisplayTracker.getDefaultDisplayId(), bounds));
409             screenshot.setScreenBounds(bounds);
410         }
411 
412         if (screenshot.getBitmap() == null) {
413             Log.e(TAG, "handleScreenshot: Screenshot bitmap was null");
414             mNotificationsController.notifyScreenshotError(
415                     R.string.screenshot_failed_to_capture_text);
416             if (mCurrentRequestCallback != null) {
417                 mCurrentRequestCallback.reportError();
418             }
419             return;
420         }
421 
422         mScreenBitmap = screenshot.getBitmap();
423         String oldPackageName = mPackageName;
424         mPackageName = screenshot.getPackageNameString();
425 
426         if (!isUserSetupComplete(Process.myUserHandle())) {
427             Log.w(TAG, "User setup not complete, displaying toast only");
428             // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
429             // and sharing shouldn't be exposed to the user.
430             saveScreenshotAndToast(screenshot.getUserHandle(), finisher);
431             return;
432         }
433 
434         mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
435                 ClipboardOverlayController.SELF_PERMISSION);
436 
437         mScreenshotTakenInPortrait =
438                 mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
439 
440         // Optimizations
441         mScreenBitmap.setHasAlpha(false);
442         mScreenBitmap.prepareToDraw();
443 
444         prepareViewForNewScreenshot(screenshot, oldPackageName);
445 
446         saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
447                 this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
448 
449         // The window is focusable by default
450         setWindowFocusable(true);
451         mScreenshotView.requestFocus();
452 
453         enqueueScrollCaptureRequest(screenshot.getUserHandle());
454 
455         attachWindow();
456 
457         boolean showFlash = true;
458         if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
459             if (screenshot.getScreenBounds() != null
460                     && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
461                             screenshot.getScreenBounds())) {
462                 showFlash = false;
463             } else {
464                 showFlash = true;
465                 screenshot.setInsets(Insets.NONE);
466                 screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
467                         screenshot.getBitmap().getHeight()));
468             }
469         }
470 
471         prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> {
472             if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
473                 mMessageContainerController.onScreenshotTaken(screenshot);
474             }
475         });
476 
477         if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
478             mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
479                     mContext.getDrawable(R.drawable.overlay_badge_background),
480                     screenshot.getUserHandle()));
481         }
482         mScreenshotView.setScreenshot(screenshot);
483 
484         if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
485             mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
486                     new AssistContentRequester.Callback() {
487                         @Override
488                         public void onAssistContentAvailable(AssistContent assistContent) {
489                             screenshot.setContextUrl(assistContent.getWebUri());
490                         }
491                     });
492         }
493 
494         // ignore system bar insets for the purpose of window layout
495         mWindow.getDecorView().setOnApplyWindowInsetsListener(
496                 (v, insets) -> WindowInsets.CONSUMED);
497         mScreenshotHandler.cancelTimeout(); // restarted after animation
498     }
499 
prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName)500     void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
501         withWindowAttached(() -> {
502             if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
503                     && mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
504                 mScreenshotView.announceForAccessibility(mContext.getResources().getString(
505                         R.string.screenshot_saving_work_profile_title));
506             } else {
507                 mScreenshotView.announceForAccessibility(
508                         mContext.getResources().getString(R.string.screenshot_saving_title));
509             }
510         });
511 
512         mScreenshotView.reset();
513 
514         if (mScreenshotView.isAttachedToWindow()) {
515             // if we didn't already dismiss for another reason
516             if (!mScreenshotView.isDismissing()) {
517                 mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
518                         oldPackageName);
519             }
520             if (DEBUG_WINDOW) {
521                 Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
522                         + "(dismissing=" + mScreenshotView.isDismissing() + ")");
523             }
524         }
525 
526         mScreenshotView.setPackageName(mPackageName);
527 
528         mScreenshotView.updateOrientation(
529                 mWindowManager.getCurrentWindowMetrics().getWindowInsets());
530     }
531 
532     @MainThread
takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback)533     void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
534             RequestCallback requestCallback) {
535         Assert.isMainThread();
536         mCurrentRequestCallback = requestCallback;
537         takeScreenshotInternal(topComponent, finisher, getFullScreenRect());
538     }
539 
540     @MainThread
handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback)541     void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
542             Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
543             Consumer<Uri> finisher, RequestCallback requestCallback) {
544         Assert.isMainThread();
545         if (screenshot == null) {
546             Log.e(TAG, "Got null bitmap from screenshot message");
547             mNotificationsController.notifyScreenshotError(
548                     R.string.screenshot_failed_to_capture_text);
549             requestCallback.reportError();
550             return;
551         }
552 
553         boolean showFlash = false;
554         if (screenshotScreenBounds == null
555                 || !aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
556             showFlash = true;
557             visibleInsets = Insets.NONE;
558             screenshotScreenBounds = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
559         }
560         mCurrentRequestCallback = requestCallback;
561         saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
562                 showFlash, UserHandle.of(userId));
563     }
564 
565     /**
566      * Clears current screenshot
567      */
dismissScreenshot(ScreenshotEvent event)568     void dismissScreenshot(ScreenshotEvent event) {
569         if (DEBUG_DISMISS) {
570             Log.d(TAG, "dismissScreenshot");
571         }
572         // If we're already animating out, don't restart the animation
573         if (mScreenshotView.isDismissing()) {
574             if (DEBUG_DISMISS) {
575                 Log.v(TAG, "Already dismissing, ignoring duplicate command");
576             }
577             return;
578         }
579         mUiEventLogger.log(event, 0, mPackageName);
580         mScreenshotHandler.cancelTimeout();
581         mScreenshotView.animateDismissal();
582     }
583 
isPendingSharedTransition()584     boolean isPendingSharedTransition() {
585         return mScreenshotView.isPendingSharedTransition();
586     }
587 
588     // Any cleanup needed when the service is being destroyed.
onDestroy()589     void onDestroy() {
590         if (mSaveInBgTask != null) {
591             // just log success/failure for the pre-existing screenshot
592             mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
593         }
594         removeWindow();
595         releaseMediaPlayer();
596         releaseContext();
597         mBgExecutor.shutdownNow();
598     }
599 
600     /**
601      * Release the constructed window context.
602      */
releaseContext()603     private void releaseContext() {
604         mContext.unregisterReceiver(mCopyBroadcastReceiver);
605         mContext.release();
606     }
607 
releaseMediaPlayer()608     private void releaseMediaPlayer() {
609         // Note that this may block if the sound is still being loaded (very unlikely) but we can't
610         // reliably release in the background because the service is being destroyed.
611         try {
612             MediaPlayer player = mCameraSound.get();
613             if (player != null) {
614                 player.release();
615             }
616         } catch (InterruptedException | ExecutionException e) {
617         }
618     }
619 
respondToBack()620     private void respondToBack() {
621         dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
622     }
623 
624     /**
625      * Update resources on configuration change. Reinflate for theme/color changes.
626      */
reloadAssets()627     private void reloadAssets() {
628         if (DEBUG_UI) {
629             Log.d(TAG, "reloadAssets()");
630         }
631 
632         // Inflate the screenshot layout
633         mScreenshotView = (ScreenshotView)
634                 LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
635         if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
636             mMessageContainerController.setView(mScreenshotView);
637         }
638         mScreenshotView.addOnAttachStateChangeListener(
639                 new View.OnAttachStateChangeListener() {
640                     @Override
641                     public void onViewAttachedToWindow(@NonNull View v) {
642                         if (DEBUG_INPUT) {
643                             Log.d(TAG, "Registering Predictive Back callback");
644                         }
645                         mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
646                                 OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
647                     }
648 
649                     @Override
650                     public void onViewDetachedFromWindow(@NonNull View v) {
651                         if (DEBUG_INPUT) {
652                             Log.d(TAG, "Unregistering Predictive Back callback");
653                         }
654                         mScreenshotView.findOnBackInvokedDispatcher()
655                                 .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
656                     }
657                 });
658         mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
659             @Override
660             public void onUserInteraction() {
661                 if (DEBUG_INPUT) {
662                     Log.d(TAG, "onUserInteraction");
663                 }
664                 mScreenshotHandler.resetTimeout();
665             }
666 
667             @Override
668             public void onDismiss() {
669                 finishDismiss();
670             }
671 
672             @Override
673             public void onTouchOutside() {
674                 // TODO(159460485): Remove this when focus is handled properly in the system
675                 setWindowFocusable(false);
676             }
677         }, mActionExecutor, mFlags);
678         mScreenshotView.setDefaultDisplay(mDisplayTracker.getDefaultDisplayId());
679         mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
680 
681         mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
682             if (keyCode == KeyEvent.KEYCODE_BACK) {
683                 if (DEBUG_INPUT) {
684                     Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
685                 }
686                 respondToBack();
687                 return true;
688             }
689             return false;
690         });
691 
692         if (DEBUG_WINDOW) {
693             Log.d(TAG, "adding OnComputeInternalInsetsListener");
694         }
695         mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
696         if (DEBUG_WINDOW) {
697             Log.d(TAG, "setContentView: " + mScreenshotView);
698         }
699         setContentView(mScreenshotView);
700     }
701 
702     /**
703      * Takes a screenshot of the current display and shows an animation.
704      */
takeScreenshotInternal(ComponentName topComponent, Consumer<Uri> finisher, Rect crop)705     private void takeScreenshotInternal(ComponentName topComponent, Consumer<Uri> finisher,
706             Rect crop) {
707         mScreenshotTakenInPortrait =
708                 mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
709 
710         // copy the input Rect, since SurfaceControl.screenshot can mutate it
711         Rect screenRect = new Rect(crop);
712         Bitmap screenshot = mImageCapture.captureDisplay(mDisplayTracker.getDefaultDisplayId(),
713                 crop);
714 
715         if (screenshot == null) {
716             Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
717             mNotificationsController.notifyScreenshotError(
718                     R.string.screenshot_failed_to_capture_text);
719             if (mCurrentRequestCallback != null) {
720                 mCurrentRequestCallback.reportError();
721             }
722             return;
723         }
724 
725         saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true,
726                 Process.myUserHandle());
727 
728         mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
729                 ClipboardOverlayController.SELF_PERMISSION);
730     }
731 
saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner)732     private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
733             Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) {
734         withWindowAttached(() -> {
735             if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
736                     && mUserManager.isManagedProfile(owner.getIdentifier())) {
737                 mScreenshotView.announceForAccessibility(mContext.getResources().getString(
738                         R.string.screenshot_saving_work_profile_title));
739             } else {
740                 mScreenshotView.announceForAccessibility(
741                         mContext.getResources().getString(R.string.screenshot_saving_title));
742             }
743         });
744 
745         mScreenshotView.reset();
746 
747         if (mScreenshotView.isAttachedToWindow()) {
748             // if we didn't already dismiss for another reason
749             if (!mScreenshotView.isDismissing()) {
750                 mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, mPackageName);
751             }
752             if (DEBUG_WINDOW) {
753                 Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
754                         + "(dismissing=" + mScreenshotView.isDismissing() + ")");
755             }
756         }
757         mPackageName = topComponent == null ? "" : topComponent.getPackageName();
758         mScreenshotView.setPackageName(mPackageName);
759 
760         mScreenshotView.updateOrientation(
761                 mWindowManager.getCurrentWindowMetrics().getWindowInsets());
762 
763         mScreenBitmap = screenshot;
764 
765         if (!isUserSetupComplete(owner)) {
766             Log.w(TAG, "User setup not complete, displaying toast only");
767             // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
768             // and sharing shouldn't be exposed to the user.
769             saveScreenshotAndToast(owner, finisher);
770             return;
771         }
772 
773         // Optimizations
774         mScreenBitmap.setHasAlpha(false);
775         mScreenBitmap.prepareToDraw();
776 
777         saveScreenshotInWorkerThread(owner, finisher, this::showUiOnActionsReady,
778                 this::showUiOnQuickShareActionReady);
779 
780         // The window is focusable by default
781         setWindowFocusable(true);
782         mScreenshotView.requestFocus();
783 
784         enqueueScrollCaptureRequest(owner);
785 
786         attachWindow();
787         prepareAnimation(screenRect, showFlash, () -> {
788             if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
789                 mMessageContainerController.onScreenshotTaken(owner);
790             }
791         });
792 
793         if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
794             mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
795                     mContext.getDrawable(R.drawable.overlay_badge_background), owner));
796         }
797         mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
798         // ignore system bar insets for the purpose of window layout
799         mWindow.getDecorView().setOnApplyWindowInsetsListener(
800                 (v, insets) -> WindowInsets.CONSUMED);
801         mScreenshotHandler.cancelTimeout(); // restarted after animation
802     }
803 
prepareAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete)804     private void prepareAnimation(Rect screenRect, boolean showFlash,
805             Runnable onAnimationComplete) {
806         mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
807                 new ViewTreeObserver.OnPreDrawListener() {
808                     @Override
809                     public boolean onPreDraw() {
810                         if (DEBUG_WINDOW) {
811                             Log.d(TAG, "onPreDraw: startAnimation");
812                         }
813                         mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
814                         startAnimation(screenRect, showFlash, onAnimationComplete);
815                         return true;
816                     }
817                 });
818     }
819 
enqueueScrollCaptureRequest(UserHandle owner)820     private void enqueueScrollCaptureRequest(UserHandle owner) {
821         // Wait until this window is attached to request because it is
822         // the reference used to locate the target window (below).
823         withWindowAttached(() -> {
824             requestScrollCapture(owner);
825             mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
826                     new ViewRootImpl.ActivityConfigCallback() {
827                         @Override
828                         public void onConfigurationChanged(Configuration overrideConfig,
829                                 int newDisplayId) {
830                             if (mConfigChanges.applyNewConfig(mContext.getResources())) {
831                                 // Hide the scroll chip until we know it's available in this
832                                 // orientation
833                                 mScreenshotView.hideScrollChip();
834                                 // Delay scroll capture eval a bit to allow the underlying activity
835                                 // to set up in the new orientation.
836                                 mScreenshotHandler.postDelayed(() -> {
837                                     requestScrollCapture(owner);
838                                 }, 150);
839                                 mScreenshotView.updateInsets(
840                                         mWindowManager.getCurrentWindowMetrics().getWindowInsets());
841                                 // Screenshot animation calculations won't be valid anymore,
842                                 // so just end
843                                 if (mScreenshotAnimation != null
844                                         && mScreenshotAnimation.isRunning()) {
845                                     mScreenshotAnimation.end();
846                                 }
847                             }
848                         }
849 
850                         @Override
851                         public void requestCompatCameraControl(boolean showControl,
852                                 boolean transformationApplied,
853                                 ICompatCameraControlCallback callback) {
854                             Log.w(TAG, "Unexpected requestCompatCameraControl callback");
855                         }
856                     });
857         });
858     }
859 
requestScrollCapture(UserHandle owner)860     private void requestScrollCapture(UserHandle owner) {
861         if (!allowLongScreenshots()) {
862             Log.d(TAG, "Long screenshots not supported on this device");
863             return;
864         }
865         mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
866         if (mLastScrollCaptureRequest != null) {
867             mLastScrollCaptureRequest.cancel(true);
868         }
869         final ListenableFuture<ScrollCaptureResponse> future =
870                 mScrollCaptureClient.request(mDisplayTracker.getDefaultDisplayId());
871         mLastScrollCaptureRequest = future;
872         mLastScrollCaptureRequest.addListener(() ->
873                 onScrollCaptureResponseReady(future, owner), mMainExecutor);
874     }
875 
onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture, UserHandle owner)876     private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
877             UserHandle owner) {
878         try {
879             if (mLastScrollCaptureResponse != null) {
880                 mLastScrollCaptureResponse.close();
881                 mLastScrollCaptureResponse = null;
882             }
883             if (responseFuture.isCancelled()) {
884                 return;
885             }
886             mLastScrollCaptureResponse = responseFuture.get();
887             if (!mLastScrollCaptureResponse.isConnected()) {
888                 // No connection means that the target window wasn't found
889                 // or that it cannot support scroll capture.
890                 Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " ["
891                         + mLastScrollCaptureResponse.getWindowTitle() + "]");
892                 return;
893             }
894             Log.d(TAG, "ScrollCapture: connected to window ["
895                     + mLastScrollCaptureResponse.getWindowTitle() + "]");
896 
897             final ScrollCaptureResponse response = mLastScrollCaptureResponse;
898             mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
899                 DisplayMetrics displayMetrics = new DisplayMetrics();
900                 getDefaultDisplay().getRealMetrics(displayMetrics);
901                 Bitmap newScreenshot = mImageCapture.captureDisplay(
902                         mDisplayTracker.getDefaultDisplayId(),
903                         new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
904 
905                 mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
906                         mScreenshotTakenInPortrait);
907                 // delay starting scroll capture to make sure the scrim is up before the app moves
908                 mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
909             });
910         } catch (InterruptedException | ExecutionException e) {
911             Log.e(TAG, "requestScrollCapture failed", e);
912         }
913     }
914 
915     ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
916 
runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner)917     private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
918         // Clear the reference to prevent close() in dismissScreenshot
919         mLastScrollCaptureResponse = null;
920 
921         if (mLongScreenshotFuture != null) {
922             mLongScreenshotFuture.cancel(true);
923         }
924         mLongScreenshotFuture = mScrollCaptureController.run(response);
925         mLongScreenshotFuture.addListener(() -> {
926             ScrollCaptureController.LongScreenshot longScreenshot;
927             try {
928                 longScreenshot = mLongScreenshotFuture.get();
929             } catch (CancellationException e) {
930                 Log.e(TAG, "Long screenshot cancelled");
931                 return;
932             } catch (InterruptedException | ExecutionException e) {
933                 Log.e(TAG, "Exception", e);
934                 mScreenshotView.restoreNonScrollingUi();
935                 return;
936             }
937 
938             if (longScreenshot.getHeight() == 0) {
939                 mScreenshotView.restoreNonScrollingUi();
940                 return;
941             }
942 
943             mLongScreenshotHolder.setLongScreenshot(longScreenshot);
944             mLongScreenshotHolder.setTransitionDestinationCallback(
945                     (transitionDestination, onTransitionEnd) -> {
946                         mScreenshotView.startLongScreenshotTransition(
947                                 transitionDestination, onTransitionEnd,
948                                 longScreenshot);
949                         // TODO: Do this via ActionIntentExecutor instead.
950                         mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
951                     }
952             );
953 
954             final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
955             intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
956                     owner);
957             intent.setFlags(
958                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
959 
960             mContext.startActivity(intent,
961                     ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
962             RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
963                     SCREENSHOT_REMOTE_RUNNER, 0, 0);
964             try {
965                 WindowManagerGlobal.getWindowManagerService()
966                         .overridePendingAppTransitionRemote(runner,
967                                 mDisplayTracker.getDefaultDisplayId());
968             } catch (Exception e) {
969                 Log.e(TAG, "Error overriding screenshot app transition", e);
970             }
971         }, mMainExecutor);
972     }
973 
withWindowAttached(Runnable action)974     private void withWindowAttached(Runnable action) {
975         View decorView = mWindow.getDecorView();
976         if (decorView.isAttachedToWindow()) {
977             action.run();
978         } else {
979             decorView.getViewTreeObserver().addOnWindowAttachListener(
980                     new ViewTreeObserver.OnWindowAttachListener() {
981                         @Override
982                         public void onWindowAttached() {
983                             mBlockAttach = false;
984                             decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
985                             action.run();
986                         }
987 
988                         @Override
989                         public void onWindowDetached() {
990                         }
991                     });
992 
993         }
994     }
995 
setContentView(View contentView)996     private void setContentView(View contentView) {
997         mWindow.setContentView(contentView);
998     }
999 
1000     @MainThread
attachWindow()1001     private void attachWindow() {
1002         View decorView = mWindow.getDecorView();
1003         if (decorView.isAttachedToWindow() || mBlockAttach) {
1004             return;
1005         }
1006         if (DEBUG_WINDOW) {
1007             Log.d(TAG, "attachWindow");
1008         }
1009         mBlockAttach = true;
1010         mWindowManager.addView(decorView, mWindowLayoutParams);
1011         decorView.requestApplyInsets();
1012     }
1013 
removeWindow()1014     void removeWindow() {
1015         final View decorView = mWindow.peekDecorView();
1016         if (decorView != null && decorView.isAttachedToWindow()) {
1017             if (DEBUG_WINDOW) {
1018                 Log.d(TAG, "Removing screenshot window");
1019             }
1020             mWindowManager.removeViewImmediate(decorView);
1021         }
1022         // Ensure that we remove the input monitor
1023         if (mScreenshotView != null) {
1024             mScreenshotView.stopInputListening();
1025         }
1026     }
1027 
loadCameraSound()1028     private ListenableFuture<MediaPlayer> loadCameraSound() {
1029         // The media player creation is slow and needs on the background thread.
1030         return CallbackToFutureAdapter.getFuture((completer) -> {
1031             mBgExecutor.execute(() -> {
1032                 try {
1033                     MediaPlayer player = MediaPlayer.create(mContext,
1034                             Uri.fromFile(new File(mContext.getResources().getString(
1035                                     com.android.internal.R.string.config_cameraShutterSound))),
1036                             null,
1037                             new AudioAttributes.Builder()
1038                                     .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
1039                                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
1040                                     .build(), AudioSystem.newAudioSessionId());
1041                     completer.set(player);
1042                 } catch (IllegalStateException e) {
1043                     Log.w(TAG, "Screenshot sound initialization failed", e);
1044                     completer.set(null);
1045                 }
1046             });
1047             return "ScreenshotController#loadCameraSound";
1048         });
1049     }
1050 
1051     private void playCameraSound() {
1052         mCameraSound.addListener(() -> {
1053             try {
1054                 MediaPlayer player = mCameraSound.get();
1055                 if (player != null) {
1056                     player.start();
1057                 }
1058             } catch (InterruptedException | ExecutionException e) {
1059             }
1060         }, mBgExecutor);
1061     }
1062 
1063     /**
1064      * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
1065      * failure).
1066      */
1067     private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) {
1068         // Play the shutter sound to notify that we've taken a screenshot
1069         playCameraSound();
1070 
1071         saveScreenshotInWorkerThread(
1072                 owner,
1073                 /* onComplete */ finisher,
1074                 /* actionsReadyListener */ imageData -> {
1075                     if (DEBUG_CALLBACK) {
1076                         Log.d(TAG, "returning URI to finisher (Consumer<URI>): " + imageData.uri);
1077                     }
1078                     finisher.accept(imageData.uri);
1079                     if (imageData.uri == null) {
1080                         mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName);
1081                         mNotificationsController.notifyScreenshotError(
1082                                 R.string.screenshot_failed_to_save_text);
1083                     } else {
1084                         mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
1085                         mScreenshotHandler.post(() -> Toast.makeText(mContext,
1086                                 R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
1087                     }
1088                 },
1089                 null);
1090     }
1091 
1092     /**
1093      * Starts the animation after taking the screenshot
1094      */
1095     private void startAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) {
1096         if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
1097             mScreenshotAnimation.cancel();
1098         }
1099 
1100         mScreenshotAnimation =
1101                 mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
1102         if (onAnimationComplete != null) {
1103             mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
1104                 @Override
1105                 public void onAnimationEnd(Animator animation) {
1106                     super.onAnimationEnd(animation);
1107                     onAnimationComplete.run();
1108                 }
1109             });
1110         }
1111 
1112         // Play the shutter sound to notify that we've taken a screenshot
1113         playCameraSound();
1114 
1115         if (DEBUG_ANIM) {
1116             Log.d(TAG, "starting post-screenshot animation");
1117         }
1118         mScreenshotAnimation.start();
1119     }
1120 
1121     /** Reset screenshot view and then call onCompleteRunnable */
1122     private void finishDismiss() {
1123         if (DEBUG_DISMISS) {
1124             Log.d(TAG, "finishDismiss");
1125         }
1126         if (mLastScrollCaptureRequest != null) {
1127             mLastScrollCaptureRequest.cancel(true);
1128             mLastScrollCaptureRequest = null;
1129         }
1130         if (mLastScrollCaptureResponse != null) {
1131             mLastScrollCaptureResponse.close();
1132             mLastScrollCaptureResponse = null;
1133         }
1134         if (mLongScreenshotFuture != null) {
1135             mLongScreenshotFuture.cancel(true);
1136         }
1137         if (mCurrentRequestCallback != null) {
1138             mCurrentRequestCallback.onFinish();
1139             mCurrentRequestCallback = null;
1140         }
1141         mScreenshotView.reset();
1142         removeWindow();
1143         mScreenshotHandler.cancelTimeout();
1144     }
1145 
1146     /**
1147      * Creates a new worker thread and saves the screenshot to the media store.
1148      */
1149     private void saveScreenshotInWorkerThread(
1150             UserHandle owner,
1151             @NonNull Consumer<Uri> finisher,
1152             @Nullable ActionsReadyListener actionsReadyListener,
1153             @Nullable QuickShareActionReadyListener
1154                     quickShareActionsReadyListener) {
1155         ScreenshotController.SaveImageInBackgroundData
1156                 data = new ScreenshotController.SaveImageInBackgroundData();
1157         data.image = mScreenBitmap;
1158         data.finisher = finisher;
1159         data.mActionsReadyListener = actionsReadyListener;
1160         data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
1161         data.owner = owner;
1162 
1163         if (mSaveInBgTask != null) {
1164             // just log success/failure for the pre-existing screenshot
1165             mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
1166         }
1167 
1168         mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
1169                 mScreenshotSmartActions, data, getActionTransitionSupplier(),
1170                 mScreenshotNotificationSmartActionsProvider);
1171         mSaveInBgTask.execute();
1172     }
1173 
1174 
1175     /**
1176      * Sets up the action shade and its entrance animation, once we get the screenshot URI.
1177      */
1178     private void showUiOnActionsReady(ScreenshotController.SavedImageData imageData) {
1179         logSuccessOnActionsReady(imageData);
1180         if (DEBUG_UI) {
1181             Log.d(TAG, "Showing UI actions");
1182         }
1183 
1184         mScreenshotHandler.resetTimeout();
1185 
1186         if (imageData.uri != null) {
1187             if (!imageData.owner.equals(Process.myUserHandle())) {
1188                 Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
1189                         + imageData.uri);
1190             }
1191             mScreenshotHandler.post(() -> {
1192                 if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
1193                     mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
1194                         @Override
1195                         public void onAnimationEnd(Animator animation) {
1196                             super.onAnimationEnd(animation);
1197                             doPostAnimation(imageData);
1198                         }
1199                     });
1200                 } else {
1201                     doPostAnimation(imageData);
1202                 }
1203             });
1204         }
1205     }
1206 
1207     private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
1208         mScreenshotView.setChipIntents(imageData);
1209     }
1210 
1211     /**
1212      * Sets up the action shade and its entrance animation, once we get the Quick Share action data.
1213      */
1214     private void showUiOnQuickShareActionReady(ScreenshotController.QuickShareData quickShareData) {
1215         if (DEBUG_UI) {
1216             Log.d(TAG, "Showing UI for Quick Share action");
1217         }
1218         if (quickShareData.quickShareAction != null) {
1219             mScreenshotHandler.post(() -> {
1220                 if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
1221                     mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
1222                         @Override
1223                         public void onAnimationEnd(Animator animation) {
1224                             super.onAnimationEnd(animation);
1225                             mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
1226                         }
1227                     });
1228                 } else {
1229                     mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
1230                 }
1231             });
1232         }
1233     }
1234 
1235     /**
1236      * Supplies the necessary bits for the shared element transition to share sheet.
1237      * Note that once supplied, the action intent to share must be sent immediately after.
1238      */
1239     private Supplier<ActionTransition> getActionTransitionSupplier() {
1240         return () -> {
1241             Pair<ActivityOptions, ExitTransitionCoordinator> transition =
1242                     ActivityOptions.startSharedElementAnimation(
1243                             mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(),
1244                             null, Pair.create(mScreenshotView.getScreenshotPreview(),
1245                                     ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
1246             transition.second.startExit();
1247 
1248             ActionTransition supply = new ActionTransition();
1249             supply.bundle = transition.first.toBundle();
1250             supply.onCancelRunnable = () -> ActivityOptions.stopSharedElementAnimation(mWindow);
1251             return supply;
1252         };
1253     }
1254 
1255     /**
1256      * Logs success/failure of the screenshot saving task, and shows an error if it failed.
1257      */
1258     private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
1259         if (imageData.uri == null) {
1260             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName);
1261             mNotificationsController.notifyScreenshotError(
1262                     R.string.screenshot_failed_to_save_text);
1263         } else {
1264             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
1265             if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
1266                     && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
1267                 mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
1268                         mPackageName);
1269             }
1270         }
1271     }
1272 
1273     private boolean isUserSetupComplete(UserHandle owner) {
1274         if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
1275             return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
1276                     .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
1277         } else {
1278             return Settings.Secure.getInt(mContext.getContentResolver(),
1279                     SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
1280         }
1281     }
1282 
1283     /**
1284      * Updates the window focusability.  If the window is already showing, then it updates the
1285      * window immediately, otherwise the layout params will be applied when the window is next
1286      * shown.
1287      */
1288     private void setWindowFocusable(boolean focusable) {
1289         if (DEBUG_WINDOW) {
1290             Log.d(TAG, "setWindowFocusable: " + focusable);
1291         }
1292         int flags = mWindowLayoutParams.flags;
1293         if (focusable) {
1294             mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1295         } else {
1296             mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1297         }
1298         if (mWindowLayoutParams.flags == flags) {
1299             if (DEBUG_WINDOW) {
1300                 Log.d(TAG, "setWindowFocusable: skipping, already " + focusable);
1301             }
1302             return;
1303         }
1304         final View decorView = mWindow.peekDecorView();
1305         if (decorView != null && decorView.isAttachedToWindow()) {
1306             mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
1307         }
1308     }
1309 
1310     private Display getDefaultDisplay() {
1311         return mDisplayManager.getDisplay(mDisplayTracker.getDefaultDisplayId());
1312     }
1313 
1314     private boolean allowLongScreenshots() {
1315         return !mIsLowRamDevice;
1316     }
1317 
1318     private Rect getFullScreenRect() {
1319         DisplayMetrics displayMetrics = new DisplayMetrics();
1320         getDefaultDisplay().getRealMetrics(displayMetrics);
1321         return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
1322     }
1323 
1324     /** Does the aspect ratio of the bitmap with insets removed match the bounds. */
1325     private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets,
1326             Rect screenBounds) {
1327         int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right;
1328         int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom;
1329 
1330         if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
1331                 || bitmap.getHeight() == 0) {
1332             if (DEBUG_UI) {
1333                 Log.e(TAG, "Provided bitmap and insets create degenerate region: "
1334                         + bitmap.getWidth() + "x" + bitmap.getHeight() + " " + bitmapInsets);
1335             }
1336             return false;
1337         }
1338 
1339         float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight;
1340         float boundsAspect = ((float) screenBounds.width()) / screenBounds.height();
1341 
1342         boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f;
1343         if (DEBUG_UI) {
1344             Log.d(TAG, "aspectRatiosMatch: don't match bitmap: " + insettedBitmapAspect
1345                     + ", bounds: " + boundsAspect);
1346         }
1347         return matchWithinTolerance;
1348     }
1349 
1350     private class ScreenshotExitTransitionCallbacksSupplier implements
1351             Supplier<ExitTransitionCallbacks> {
1352         final boolean mDismissOnHideSharedElements;
1353 
1354         ScreenshotExitTransitionCallbacksSupplier(boolean dismissOnHideSharedElements) {
1355             mDismissOnHideSharedElements = dismissOnHideSharedElements;
1356         }
1357 
1358         @Override
1359         public ExitTransitionCallbacks get() {
1360             return new ExitTransitionCallbacks() {
1361                 @Override
1362                 public boolean isReturnTransitionAllowed() {
1363                     return false;
1364                 }
1365 
1366                 @Override
1367                 public void hideSharedElements() {
1368                     if (mDismissOnHideSharedElements) {
1369                         finishDismiss();
1370                     }
1371                 }
1372 
1373                 @Override
1374                 public void onFinish() {
1375                 }
1376             };
1377         }
1378     }
1379 }
1380