summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Miranda Kephart <mkephart@google.com> 2024-07-22 13:12:04 -0400
committer Miranda Kephart <mkephart@google.com> 2024-08-05 13:48:40 +0000
commit6df1d0ab3914848811cf747d1309de8cd1327a2d (patch)
tree861822cd29d480ab2b6aed539587540c5c4cf62d
parent22a5dfe12e516eaba35c86e6529c27742f81c829 (diff)
Extract screenshot window code
Bug: 354711957 Flag: com.android.systemui.screenshot_ui_controller_refactor Test: manual Change-Id: Ida73a0fb36ba4b405ed6555a42ed2bd0b63155a4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt194
2 files changed, 209 insertions, 117 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 540d4c43c58d..7b802a2a40aa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -17,7 +17,6 @@
package com.android.systemui.screenshot;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.systemui.Flags.screenshotSaveImageExporter;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
@@ -31,7 +30,6 @@ import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERAC
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -52,17 +50,12 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.ScrollCaptureResponse;
-import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.WindowContext;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
@@ -115,11 +108,9 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
private final BroadcastDispatcher mBroadcastDispatcher;
private final ScreenshotActionsController mActionsController;
- private final WindowManager mWindowManager;
- private final WindowManager.LayoutParams mWindowLayoutParams;
@Nullable
private final ScreenshotSoundController mScreenshotSoundController;
- private final PhoneWindow mWindow;
+ private final ScreenshotWindow mWindow;
private final Display mDisplay;
private final ScrollCaptureExecutor mScrollCaptureExecutor;
private final ScreenshotNotificationSmartActionsProvider
@@ -135,8 +126,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
- private boolean mAttachRequested;
- private boolean mDetachRequested;
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
private String mPackageName = "";
@@ -155,7 +144,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
@AssistedInject
ScreenshotController(
Context context,
- WindowManager windowManager,
+ ScreenshotWindow.Factory screenshotWindowFactory,
FeatureFlags flags,
ScreenshotShelfViewProxy.Factory viewProxyFactory,
ScreenshotSmartActions screenshotSmartActions,
@@ -195,9 +184,8 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
mDisplay = display;
- mWindowManager = windowManager;
- final Context displayContext = context.createDisplayContext(display);
- mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+ mWindow = screenshotWindowFactory.create(mDisplay);
+ mContext = mWindow.getContext();
mFlags = flags;
mUserManager = userManager;
mMessageContainerController = messageContainerController;
@@ -213,17 +201,10 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
});
- // Setup the window that we are going to use
- mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
- mWindowLayoutParams.setTitle("ScreenshotAnimation");
-
- mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
- mWindow.setWindowManager(mWindowManager, null, null);
-
mConfigChanges.applyNewConfig(context.getResources());
reloadAssets();
- mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy,
+ mActionExecutor = actionExecutorFactory.create(mWindow.getWindow(), mViewProxy,
() -> {
finishDismiss();
return Unit.INSTANCE;
@@ -318,12 +299,12 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
}
// The window is focusable by default
- setWindowFocusable(true);
+ mWindow.setFocusable(true);
mViewProxy.requestFocus();
enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle());
- attachWindow();
+ mWindow.attachWindow();
boolean showFlash;
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
@@ -347,13 +328,10 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
mViewProxy.setScreenshot(screenshot);
- // ignore system bar insets for the purpose of window layout
- mWindow.getDecorView().setOnApplyWindowInsetsListener(
- (v, insets) -> WindowInsets.CONSUMED);
}
void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
- withWindowAttached(() -> {
+ mWindow.whenWindowAttached(() -> {
mAnnouncementResolver.getScreenshotAnnouncement(
screenshot.getUserHandle().getIdentifier(),
announcement -> {
@@ -444,7 +422,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
@Override
public void onTouchOutside() {
// TODO(159460485): Remove this when focus is handled properly in the system
- setWindowFocusable(false);
+ mWindow.setFocusable(false);
}
});
@@ -457,9 +435,9 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) {
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
- withWindowAttached(() -> {
+ mWindow.whenWindowAttached(() -> {
requestScrollCapture(requestId, owner);
- mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+ mWindow.setActivityConfigCallback(
new ViewRootImpl.ActivityConfigCallback() {
@Override
public void onConfigurationChanged(Configuration overrideConfig,
@@ -472,8 +450,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
// to set up in the new orientation.
mScreenshotHandler.postDelayed(
() -> requestScrollCapture(requestId, owner), 150);
- mViewProxy.updateInsets(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+ mViewProxy.updateInsets(mWindow.getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
// so just end
if (mScreenshotAnimation != null
@@ -489,7 +466,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
private void requestScrollCapture(UUID requestId, UserHandle owner) {
mScrollCaptureExecutor.requestScrollCapture(
mDisplay.getDisplayId(),
- mWindow.getDecorView().getWindowToken(),
+ mWindow.getWindowToken(),
(response) -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
0, response.getPackageName());
@@ -528,61 +505,9 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
mViewProxy::startLongScreenshotTransition);
}
- private void withWindowAttached(Runnable action) {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow()) {
- action.run();
- } else {
- decorView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- mAttachRequested = false;
- decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
- action.run();
- }
-
- @Override
- public void onWindowDetached() {
- }
- });
-
- }
- }
-
- @MainThread
- private void attachWindow() {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow() || mAttachRequested) {
- return;
- }
- if (DEBUG_WINDOW) {
- Log.d(TAG, "attachWindow");
- }
- mAttachRequested = true;
- mWindowManager.addView(decorView, mWindowLayoutParams);
- decorView.requestApplyInsets();
-
- ViewGroup layout = decorView.requireViewById(android.R.id.content);
- layout.setClipChildren(false);
- layout.setClipToPadding(false);
- }
-
@Override
public void removeWindow() {
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "Removing screenshot window");
- }
- mWindowManager.removeViewImmediate(decorView);
- mDetachRequested = false;
- }
- if (mAttachRequested && !mDetachRequested) {
- mDetachRequested = true;
- withWindowAttached(this::removeWindow);
- }
-
+ mWindow.removeWindow();
mViewProxy.stopInputListening();
}
@@ -759,33 +684,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
}
- /**
- * Updates the window focusability. If the window is already showing, then it updates the
- * window immediately, otherwise the layout params will be applied when the window is next
- * shown.
- */
- private void setWindowFocusable(boolean focusable) {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setWindowFocusable: " + focusable);
- }
- int flags = mWindowLayoutParams.flags;
- if (focusable) {
- mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- } else {
- mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- }
- if (mWindowLayoutParams.flags == flags) {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setWindowFocusable: skipping, already " + focusable);
- }
- return;
- }
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
- }
- }
-
private Rect getFullScreenRect() {
DisplayMetrics displayMetrics = new DisplayMetrics();
mDisplay.getRealMetrics(displayMetrics);
@@ -826,6 +724,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler {
*
* @param display display to capture
*/
- LegacyScreenshotController create(Display display);
+ ScreenshotController create(Display display);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt
new file mode 100644
index 000000000000..644e12cba6fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.R
+import android.annotation.MainThread
+import android.content.Context
+import android.graphics.PixelFormat
+import android.os.IBinder
+import android.util.Log
+import android.view.Display
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewRootImpl
+import android.view.ViewTreeObserver.OnWindowAttachListener
+import android.view.Window
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.window.WindowContext
+import com.android.internal.policy.PhoneWindow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Creates and manages the window in which the screenshot UI is displayed. */
+class ScreenshotWindow
+@AssistedInject
+constructor(
+ private val windowManager: WindowManager,
+ private val context: Context,
+ @Assisted private val display: Display,
+) {
+
+ val window: PhoneWindow =
+ PhoneWindow(
+ context
+ .createDisplayContext(display)
+ .createWindowContext(WindowManager.LayoutParams.TYPE_SCREENSHOT, null)
+ )
+ private val params =
+ WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 0, /* xpos */
+ 0, /* ypos */
+ WindowManager.LayoutParams.TYPE_SCREENSHOT,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN or
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ PixelFormat.TRANSLUCENT
+ )
+ .apply {
+ layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ setFitInsetsTypes(0)
+ // This is needed to let touches pass through outside the touchable areas
+ privateFlags =
+ privateFlags or WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+ title = "ScreenshotUI"
+ }
+ private var attachRequested: Boolean = false
+ private var detachRequested: Boolean = false
+
+ init {
+ window.requestFeature(Window.FEATURE_NO_TITLE)
+ window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
+ window.setBackgroundDrawableResource(R.color.transparent)
+ window.setWindowManager(windowManager, null, null)
+ }
+
+ @MainThread
+ fun attachWindow() {
+ val decorView: View = window.getDecorView()
+ if (decorView.isAttachedToWindow || attachRequested) {
+ return
+ }
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "attachWindow")
+ }
+ attachRequested = true
+ windowManager.addView(decorView, params)
+
+ decorView.requestApplyInsets()
+ decorView.requireViewById<ViewGroup>(R.id.content).apply {
+ clipChildren = false
+ clipToPadding = false
+ // ignore system bar insets for the purpose of window layout
+ setOnApplyWindowInsetsListener { _, _ -> WindowInsets.CONSUMED }
+ }
+ }
+
+ fun whenWindowAttached(action: Runnable) {
+ val decorView: View = window.getDecorView()
+ if (decorView.isAttachedToWindow) {
+ action.run()
+ } else {
+ decorView
+ .getViewTreeObserver()
+ .addOnWindowAttachListener(
+ object : OnWindowAttachListener {
+ override fun onWindowAttached() {
+ attachRequested = false
+ decorView.getViewTreeObserver().removeOnWindowAttachListener(this)
+ action.run()
+ }
+
+ override fun onWindowDetached() {}
+ }
+ )
+ }
+ }
+
+ fun removeWindow() {
+ val decorView: View? = window.peekDecorView()
+ if (decorView != null && decorView.isAttachedToWindow) {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "Removing screenshot window")
+ }
+ windowManager.removeViewImmediate(decorView)
+ detachRequested = false
+ }
+ if (attachRequested && !detachRequested) {
+ detachRequested = true
+ whenWindowAttached { removeWindow() }
+ }
+ }
+
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the window
+ * immediately, otherwise the layout params will be applied when the window is next shown.
+ */
+ fun setFocusable(focusable: Boolean) {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "setWindowFocusable: $focusable")
+ }
+ val flags: Int = params.flags
+ if (focusable) {
+ params.flags = params.flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv()
+ } else {
+ params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ }
+ if (params.flags == flags) {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "setWindowFocusable: skipping, already $focusable")
+ }
+ return
+ }
+ window.peekDecorView()?.also {
+ if (it.isAttachedToWindow) {
+ windowManager.updateViewLayout(it, params)
+ }
+ }
+ }
+
+ fun getContext(): WindowContext = window.context as WindowContext
+
+ fun getWindowToken(): IBinder = window.decorView.windowToken
+
+ fun getWindowInsets(): WindowInsets = windowManager.currentWindowMetrics.windowInsets
+
+ fun setContentView(view: View) {
+ window.setContentView(view)
+ }
+
+ fun setActivityConfigCallback(callback: ViewRootImpl.ActivityConfigCallback) {
+ window.peekDecorView().viewRootImpl.setActivityConfigCallback(callback)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(display: Display): ScreenshotWindow
+ }
+
+ companion object {
+ private const val TAG = "ScreenshotWindow"
+ }
+}