1 /* <lambda>null2 * Copyright (C) 2024 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 android.R 20 import android.annotation.MainThread 21 import android.content.Context 22 import android.graphics.PixelFormat 23 import android.os.IBinder 24 import android.util.Log 25 import android.view.Display 26 import android.view.View 27 import android.view.ViewGroup 28 import android.view.ViewRootImpl 29 import android.view.ViewTreeObserver.OnWindowAttachListener 30 import android.view.Window 31 import android.view.WindowInsets 32 import android.view.WindowManager 33 import android.window.WindowContext 34 import com.android.internal.policy.PhoneWindow 35 import dagger.assisted.Assisted 36 import dagger.assisted.AssistedFactory 37 import dagger.assisted.AssistedInject 38 39 /** Creates and manages the window in which the screenshot UI is displayed. */ 40 class ScreenshotWindow 41 @AssistedInject 42 constructor( 43 private val windowManager: WindowManager, 44 private val context: Context, 45 @Assisted private val display: Display, 46 ) { 47 48 val window: PhoneWindow = 49 PhoneWindow( 50 context 51 .createDisplayContext(display) 52 .createWindowContext(WindowManager.LayoutParams.TYPE_SCREENSHOT, null) 53 ) 54 private val params = 55 WindowManager.LayoutParams( 56 ViewGroup.LayoutParams.MATCH_PARENT, 57 ViewGroup.LayoutParams.MATCH_PARENT, 58 0, /* xpos */ 59 0, /* ypos */ 60 WindowManager.LayoutParams.TYPE_SCREENSHOT, 61 WindowManager.LayoutParams.FLAG_FULLSCREEN or 62 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or 63 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or 64 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or 65 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or 66 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 67 PixelFormat.TRANSLUCENT 68 ) 69 .apply { 70 layoutInDisplayCutoutMode = 71 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS 72 setFitInsetsTypes(0) 73 // This is needed to let touches pass through outside the touchable areas 74 privateFlags = 75 privateFlags or WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY 76 title = "ScreenshotUI" 77 } 78 private var attachRequested: Boolean = false 79 private var detachRequested: Boolean = false 80 81 init { 82 window.requestFeature(Window.FEATURE_NO_TITLE) 83 window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) 84 window.setBackgroundDrawableResource(R.color.transparent) 85 window.setWindowManager(windowManager, null, null) 86 } 87 88 @MainThread 89 fun attachWindow() { 90 val decorView: View = window.getDecorView() 91 if (decorView.isAttachedToWindow || attachRequested) { 92 return 93 } 94 if (LogConfig.DEBUG_WINDOW) { 95 Log.d(TAG, "attachWindow") 96 } 97 attachRequested = true 98 windowManager.addView(decorView, params) 99 100 decorView.requestApplyInsets() 101 decorView.requireViewById<ViewGroup>(R.id.content).apply { 102 clipChildren = false 103 clipToPadding = false 104 // ignore system bar insets for the purpose of window layout 105 setOnApplyWindowInsetsListener { _, _ -> WindowInsets.CONSUMED } 106 } 107 } 108 109 fun whenWindowAttached(action: Runnable) { 110 val decorView: View = window.getDecorView() 111 if (decorView.isAttachedToWindow) { 112 action.run() 113 } else { 114 decorView 115 .getViewTreeObserver() 116 .addOnWindowAttachListener( 117 object : OnWindowAttachListener { 118 override fun onWindowAttached() { 119 attachRequested = false 120 decorView.getViewTreeObserver().removeOnWindowAttachListener(this) 121 action.run() 122 } 123 124 override fun onWindowDetached() {} 125 } 126 ) 127 } 128 } 129 130 fun removeWindow() { 131 val decorView: View? = window.peekDecorView() 132 if (decorView != null && decorView.isAttachedToWindow) { 133 if (LogConfig.DEBUG_WINDOW) { 134 Log.d(TAG, "Removing screenshot window") 135 } 136 windowManager.removeViewImmediate(decorView) 137 detachRequested = false 138 } 139 if (attachRequested && !detachRequested) { 140 detachRequested = true 141 whenWindowAttached { removeWindow() } 142 } 143 } 144 145 /** 146 * Updates the window focusability. If the window is already showing, then it updates the window 147 * immediately, otherwise the layout params will be applied when the window is next shown. 148 */ 149 fun setFocusable(focusable: Boolean) { 150 if (LogConfig.DEBUG_WINDOW) { 151 Log.d(TAG, "setWindowFocusable: $focusable") 152 } 153 val flags: Int = params.flags 154 if (focusable) { 155 params.flags = params.flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv() 156 } else { 157 params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 158 } 159 if (params.flags == flags) { 160 if (LogConfig.DEBUG_WINDOW) { 161 Log.d(TAG, "setWindowFocusable: skipping, already $focusable") 162 } 163 return 164 } 165 window.peekDecorView()?.also { 166 if (it.isAttachedToWindow) { 167 windowManager.updateViewLayout(it, params) 168 } 169 } 170 } 171 172 fun getContext(): WindowContext = window.context as WindowContext 173 174 fun getWindowToken(): IBinder = window.decorView.windowToken 175 176 fun getWindowInsets(): WindowInsets = windowManager.currentWindowMetrics.windowInsets 177 178 fun setContentView(view: View) { 179 window.setContentView(view) 180 } 181 182 fun setActivityConfigCallback(callback: ViewRootImpl.ActivityConfigCallback) { 183 window.peekDecorView().viewRootImpl.setActivityConfigCallback(callback) 184 } 185 186 @AssistedFactory 187 interface Factory { 188 fun create(display: Display): ScreenshotWindow 189 } 190 191 companion object { 192 private const val TAG = "ScreenshotWindow" 193 } 194 } 195