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