• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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