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