1 /* <lambda>null2 * Copyright (C) 2021 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 package com.android.systemui.statusbar.phone 17 18 import android.app.StatusBarManager.WINDOW_STATUS_BAR 19 import android.graphics.Point 20 import android.util.Log 21 import android.view.InputDevice 22 import android.view.MotionEvent 23 import android.view.View 24 import android.view.ViewGroup 25 import android.view.ViewTreeObserver 26 import com.android.systemui.Gefingerpoken 27 import com.android.systemui.flags.FeatureFlags 28 import com.android.systemui.flags.Flags 29 import com.android.systemui.res.R 30 import com.android.systemui.scene.shared.flag.SceneContainerFlag 31 import com.android.systemui.scene.ui.view.WindowRootView 32 import com.android.systemui.shade.ShadeController 33 import com.android.systemui.shade.ShadeLogger 34 import com.android.systemui.shade.ShadeViewController 35 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor 36 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator 37 import com.android.systemui.statusbar.policy.ConfigurationController 38 import com.android.systemui.statusbar.window.StatusBarWindowStateController 39 import com.android.systemui.unfold.SysUIUnfoldComponent 40 import com.android.systemui.unfold.UNFOLD_STATUS_BAR 41 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider 42 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel 43 import com.android.systemui.util.ViewController 44 import com.android.systemui.util.kotlin.getOrNull 45 import com.android.systemui.util.view.ViewUtil 46 import java.util.Optional 47 import javax.inject.Inject 48 import javax.inject.Named 49 import javax.inject.Provider 50 51 private const val TAG = "PhoneStatusBarViewController" 52 53 /** Controller for [PhoneStatusBarView]. */ 54 class PhoneStatusBarViewController 55 private constructor( 56 view: PhoneStatusBarView, 57 @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?, 58 private val centralSurfaces: CentralSurfaces, 59 private val statusBarWindowStateController: StatusBarWindowStateController, 60 private val shadeController: ShadeController, 61 private val shadeViewController: ShadeViewController, 62 private val panelExpansionInteractor: PanelExpansionInteractor, 63 private val windowRootView: Provider<WindowRootView>, 64 private val shadeLogger: ShadeLogger, 65 private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, 66 private val userChipViewModel: StatusBarUserChipViewModel, 67 private val viewUtil: ViewUtil, 68 private val configurationController: ConfigurationController, 69 private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, 70 ) : ViewController<PhoneStatusBarView>(view) { 71 72 private lateinit var statusContainer: View 73 74 private val configurationListener = 75 object : ConfigurationController.ConfigurationListener { 76 override fun onDensityOrFontScaleChanged() { 77 mView.onDensityOrFontScaleChanged() 78 } 79 } 80 81 override fun onViewAttached() { 82 statusContainer = mView.requireViewById(R.id.system_icons) 83 statusContainer.setOnHoverListener( 84 statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer) 85 ) 86 statusContainer.setOnTouchListener(object : View.OnTouchListener { 87 override fun onTouch(v: View, event: MotionEvent): Boolean { 88 // We want to handle only mouse events here to avoid stealing finger touches from 89 // status bar which expands shade when swiped down on. We're using onTouchListener 90 // instead of onClickListener as the later will lead to isClickable being set to 91 // true and hence ALL touches always being intercepted. See [View.OnTouchEvent] 92 if (event.source == InputDevice.SOURCE_MOUSE) { 93 if (event.action == MotionEvent.ACTION_UP) { 94 v.performClick() 95 shadeController.animateExpandShade() 96 } 97 return true 98 } 99 return false 100 } 101 }) 102 103 progressProvider?.setReadyToHandleTransition(true) 104 configurationController.addCallback(configurationListener) 105 106 if (moveFromCenterAnimationController == null) return 107 108 val statusBarLeftSide: View = 109 mView.requireViewById(R.id.status_bar_start_side_except_heads_up) 110 val systemIconArea: ViewGroup = mView.requireViewById(R.id.status_bar_end_side_content) 111 112 val viewsToAnimate = arrayOf(statusBarLeftSide, systemIconArea) 113 114 mView.viewTreeObserver.addOnPreDrawListener( 115 object : ViewTreeObserver.OnPreDrawListener { 116 override fun onPreDraw(): Boolean { 117 moveFromCenterAnimationController.onViewsReady(viewsToAnimate) 118 mView.viewTreeObserver.removeOnPreDrawListener(this) 119 return true 120 } 121 } 122 ) 123 124 mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ -> 125 val widthChanged = right - left != oldRight - oldLeft 126 if (widthChanged) { 127 moveFromCenterAnimationController.onStatusBarWidthChanged() 128 } 129 } 130 } 131 132 override fun onViewDetached() { 133 statusContainer.setOnHoverListener(null) 134 progressProvider?.setReadyToHandleTransition(false) 135 moveFromCenterAnimationController?.onViewDetached() 136 configurationController.removeCallback(configurationListener) 137 } 138 139 init { 140 mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler()) 141 mView.init(userChipViewModel) 142 } 143 144 override fun onInit() {} 145 146 fun setImportantForAccessibility(mode: Int) { 147 mView.importantForAccessibility = mode 148 } 149 150 /** 151 * Sends a touch event to the status bar view. 152 * 153 * This is required in certain cases because the status bar view is in a separate window from 154 * the rest of SystemUI, and other windows may decide that their touch should instead be treated 155 * as a status bar window touch. 156 */ 157 fun sendTouchToView(ev: MotionEvent): Boolean { 158 return mView.dispatchTouchEvent(ev) 159 } 160 161 /** 162 * Returns true if the given (x, y) point (in screen coordinates) is within the status bar 163 * view's range and false otherwise. 164 */ 165 fun touchIsWithinView(x: Float, y: Float): Boolean { 166 return viewUtil.touchIsWithinView(mView, x, y) 167 } 168 169 /** Called when a touch event occurred on {@link PhoneStatusBarView}. */ 170 fun onTouch(event: MotionEvent) { 171 if (statusBarWindowStateController.windowIsShowing()) { 172 val upOrCancel = 173 event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL 174 centralSurfaces.setInteracting( 175 WINDOW_STATUS_BAR, 176 !upOrCancel || shadeController.isExpandedVisible 177 ) 178 } 179 } 180 181 inner class PhoneStatusBarViewTouchHandler : Gefingerpoken { 182 override fun onInterceptTouchEvent(event: MotionEvent): Boolean { 183 onTouch(event) 184 return false 185 } 186 187 override fun onTouchEvent(event: MotionEvent): Boolean { 188 onTouch(event) 189 190 // If panels aren't enabled, ignore the gesture and don't pass it down to the 191 // panel view. 192 if (!centralSurfaces.commandQueuePanelsEnabled) { 193 if (event.action == MotionEvent.ACTION_DOWN) { 194 Log.v( 195 TAG, 196 String.format( 197 "onTouchForwardedFromStatusBar: panel disabled, " + 198 "ignoring touch at (${event.x.toInt()},${event.y.toInt()})" 199 ) 200 ) 201 } 202 return false 203 } 204 205 // If scene framework is enabled, route the touch to it and 206 // ignore the rest of the gesture. 207 if (SceneContainerFlag.isEnabled) { 208 windowRootView.get().dispatchTouchEvent(event) 209 return true 210 } 211 212 if (event.action == MotionEvent.ACTION_DOWN) { 213 // If the view that would receive the touch is disabled, just have status 214 // bar eat the gesture. 215 if (!shadeViewController.isViewEnabled) { 216 shadeLogger.logMotionEvent( 217 event, 218 "onTouchForwardedFromStatusBar: panel view disabled" 219 ) 220 return true 221 } 222 if (panelExpansionInteractor.isFullyCollapsed && event.y < 1f) { 223 // b/235889526 Eat events on the top edge of the phone when collapsed 224 shadeLogger.logMotionEvent(event, "top edge touch ignored") 225 return true 226 } 227 } 228 return shadeViewController.handleExternalTouch(event) 229 } 230 } 231 232 class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider { 233 override fun getViewCenter(view: View, outPoint: Point) = 234 when (view.id) { 235 R.id.status_bar_start_side_except_heads_up -> { 236 // items aligned to the start, return start center point 237 getViewEdgeCenter(view, outPoint, isStart = true) 238 } 239 R.id.status_bar_end_side_content -> { 240 // items aligned to the end, return end center point 241 getViewEdgeCenter(view, outPoint, isStart = false) 242 } 243 else -> super.getViewCenter(view, outPoint) 244 } 245 246 /** Returns start or end (based on [isStart]) center point of the view */ 247 private fun getViewEdgeCenter(view: View, outPoint: Point, isStart: Boolean) { 248 val isRtl = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL 249 val isLeftEdge = isRtl xor isStart 250 251 val viewLocation = IntArray(2) 252 view.getLocationOnScreen(viewLocation) 253 254 val viewX = viewLocation[0] 255 val viewY = viewLocation[1] 256 257 outPoint.x = viewX + if (isLeftEdge) view.height / 2 else view.width - view.height / 2 258 outPoint.y = viewY + view.height / 2 259 } 260 } 261 262 class Factory 263 @Inject 264 constructor( 265 private val unfoldComponent: Optional<SysUIUnfoldComponent>, 266 @Named(UNFOLD_STATUS_BAR) 267 private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>, 268 private val featureFlags: FeatureFlags, 269 private val userChipViewModel: StatusBarUserChipViewModel, 270 private val centralSurfaces: CentralSurfaces, 271 private val statusBarWindowStateController: StatusBarWindowStateController, 272 private val shadeController: ShadeController, 273 private val shadeViewController: ShadeViewController, 274 private val panelExpansionInteractor: PanelExpansionInteractor, 275 private val windowRootView: Provider<WindowRootView>, 276 private val shadeLogger: ShadeLogger, 277 private val viewUtil: ViewUtil, 278 private val configurationController: ConfigurationController, 279 private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, 280 ) { 281 fun create(view: PhoneStatusBarView): PhoneStatusBarViewController { 282 val statusBarMoveFromCenterAnimationController = 283 if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) { 284 unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController() 285 } else { 286 null 287 } 288 289 return PhoneStatusBarViewController( 290 view, 291 progressProvider.getOrNull(), 292 centralSurfaces, 293 statusBarWindowStateController, 294 shadeController, 295 shadeViewController, 296 panelExpansionInteractor, 297 windowRootView, 298 shadeLogger, 299 statusBarMoveFromCenterAnimationController, 300 userChipViewModel, 301 viewUtil, 302 configurationController, 303 statusOverlayHoverListenerFactory, 304 ) 305 } 306 } 307 } 308