Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +15 −117 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 = ""; Loading @@ -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, Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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 -> { Loading Loading @@ -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); } }); Loading @@ -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, Loading @@ -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 Loading @@ -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()); Loading Loading @@ -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(); } Loading Loading @@ -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); Loading Loading @@ -826,6 +724,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler { * * @param display display to capture */ LegacyScreenshotController create(Display display); ScreenshotController create(Display display); } } packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt 0 → 100644 +194 −0 Original line number Diff line number Diff line /* * 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" } } Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +15 −117 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 = ""; Loading @@ -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, Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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 -> { Loading Loading @@ -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); } }); Loading @@ -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, Loading @@ -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 Loading @@ -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()); Loading Loading @@ -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(); } Loading Loading @@ -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); Loading Loading @@ -826,6 +724,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler { * * @param display display to capture */ LegacyScreenshotController create(Display display); ScreenshotController create(Display display); } }
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt 0 → 100644 +194 −0 Original line number Diff line number Diff line /* * 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" } }