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