blob: 775a1649702ae12540c9792816f44f2eb235b2c9 [file] [log] [blame]
Selim Cinek5dbef2d2020-05-07 17:44:38 -07001/*
2 * Copyright (C) 2020 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
17package com.android.systemui.media
18
Selim Cinekf0f74952020-04-21 11:45:16 -070019import android.animation.Animator
20import android.animation.AnimatorListenerAdapter
21import android.animation.ValueAnimator
Selim Cinek5dbef2d2020-05-07 17:44:38 -070022import android.annotation.IntDef
23import android.content.Context
Selim Cinek2de5ebb2020-05-20 15:39:03 -070024import android.graphics.Rect
25import android.util.MathUtils
Selim Cinekf0f74952020-04-21 11:45:16 -070026import android.view.View
Selim Cinek5dbef2d2020-05-07 17:44:38 -070027import android.view.ViewGroup
Selim Cinekf0f74952020-04-21 11:45:16 -070028import android.view.ViewGroupOverlay
Selim Cinekf0f74952020-04-21 11:45:16 -070029import com.android.systemui.Interpolators
Selim Cinek7f657602020-05-21 12:37:14 -070030import com.android.systemui.keyguard.WakefulnessLifecycle
Selim Cinek5dbef2d2020-05-07 17:44:38 -070031import com.android.systemui.plugins.statusbar.StatusBarStateController
Lucas Dupin989a1112020-05-19 18:56:28 -070032import com.android.systemui.statusbar.NotificationLockscreenUserManager
Selim Cinek5dbef2d2020-05-07 17:44:38 -070033import com.android.systemui.statusbar.StatusBarState
Selim Cinekf0f74952020-04-21 11:45:16 -070034import com.android.systemui.statusbar.SysuiStatusBarStateController
Selim Cinekf0f74952020-04-21 11:45:16 -070035import com.android.systemui.statusbar.notification.stack.StackStateAnimator
Selim Cinek5dbef2d2020-05-07 17:44:38 -070036import com.android.systemui.statusbar.phone.KeyguardBypassController
Selim Cinekf0f74952020-04-21 11:45:16 -070037import com.android.systemui.statusbar.policy.KeyguardStateController
Selim Cinek54809622020-04-30 19:04:44 -070038import com.android.systemui.util.animation.UniqueObjectHostView
Selim Cinek5dbef2d2020-05-07 17:44:38 -070039import javax.inject.Inject
40import javax.inject.Singleton
41
42/**
43 * This manager is responsible for placement of the unique media view between the different hosts
44 * and animate the positions of the views to achieve seamless transitions.
45 */
46@Singleton
47class MediaHierarchyManager @Inject constructor(
48 private val context: Context,
Selim Cinekf0f74952020-04-21 11:45:16 -070049 private val statusBarStateController: SysuiStatusBarStateController,
50 private val keyguardStateController: KeyguardStateController,
Selim Cinek5dbef2d2020-05-07 17:44:38 -070051 private val bypassController: KeyguardBypassController,
Selim Cinek54809622020-04-30 19:04:44 -070052 private val mediaViewManager: MediaViewManager,
Selim Cinek7f657602020-05-21 12:37:14 -070053 private val notifLockscreenUserManager: NotificationLockscreenUserManager,
54 wakefulnessLifecycle: WakefulnessLifecycle
Selim Cinek5dbef2d2020-05-07 17:44:38 -070055) {
Selim Cinekf418bb02020-05-04 17:16:58 -070056 /**
57 * The root overlay of the hierarchy. This is where the media notification is attached to
58 * whenever the view is transitioning from one host to another. It also make sure that the
59 * view is always in its final state when it is attached to a view host.
60 */
Selim Cinekf0f74952020-04-21 11:45:16 -070061 private var rootOverlay: ViewGroupOverlay? = null
Selim Cinek2de5ebb2020-05-20 15:39:03 -070062
63 private var rootView: View? = null
64 private var currentBounds = Rect()
65 private var animationStartBounds: Rect = Rect()
66 private var targetBounds: Rect = Rect()
67 private val mediaFrame
Robert Snoeberger52dfb622020-05-21 16:31:29 -040068 get() = mediaViewManager.mediaFrame
Selim Cinek2d7be5f2020-05-01 13:16:01 -070069 private var statusbarState: Int = statusBarStateController.state
Selim Cinekf0f74952020-04-21 11:45:16 -070070 private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
71 interpolator = Interpolators.FAST_OUT_SLOW_IN
72 addUpdateListener {
73 updateTargetState()
Selim Cinek2de5ebb2020-05-20 15:39:03 -070074 interpolateBounds(animationStartBounds, targetBounds, animatedFraction,
75 result = currentBounds)
76 applyState(currentBounds)
Selim Cinekf0f74952020-04-21 11:45:16 -070077 }
78 addListener(object : AnimatorListenerAdapter() {
79 private var cancelled: Boolean = false
80
81 override fun onAnimationCancel(animation: Animator?) {
82 cancelled = true
Selim Cinek2de5ebb2020-05-20 15:39:03 -070083 animationPending = false
84 rootView?.removeCallbacks(startAnimation)
Selim Cinekf0f74952020-04-21 11:45:16 -070085 }
Selim Cinek2de5ebb2020-05-20 15:39:03 -070086
Selim Cinekf0f74952020-04-21 11:45:16 -070087 override fun onAnimationEnd(animation: Animator?) {
88 if (!cancelled) {
89 applyTargetStateIfNotAnimating()
90 }
91 }
92
93 override fun onAnimationStart(animation: Animator?) {
94 cancelled = false
Selim Cinek2de5ebb2020-05-20 15:39:03 -070095 animationPending = false
Selim Cinekf0f74952020-04-21 11:45:16 -070096 }
97 })
98 }
Selim Cinekf418bb02020-05-04 17:16:58 -070099
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700100 private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
Selim Cinekf418bb02020-05-04 17:16:58 -0700101 /**
102 * The last location where this view was at before going to the desired location. This is
103 * useful for guided transitions.
104 */
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700105 @MediaLocation
106 private var previousLocation = -1
Selim Cinekf418bb02020-05-04 17:16:58 -0700107 /**
108 * The desired location where the view will be at the end of the transition.
109 */
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700110 @MediaLocation
111 private var desiredLocation = -1
Selim Cinekf418bb02020-05-04 17:16:58 -0700112
113 /**
114 * The current attachment location where the view is currently attached.
115 * Usually this matches the desired location except for animations whenever a view moves
116 * to the new desired location, during which it is in [IN_OVERLAY].
117 */
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700118 @MediaLocation
119 private var currentAttachmentLocation = -1
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700120
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700121 /**
122 * Are we currently waiting on an animation to start?
123 */
124 private var animationPending: Boolean = false
125 private val startAnimation: Runnable = Runnable { animator.start() }
126
127 /**
128 * The expansion of quick settings
129 */
Selim Cinekf0f74952020-04-21 11:45:16 -0700130 var qsExpansion: Float = 0.0f
131 set(value) {
132 if (field != value) {
133 field = value
134 updateDesiredLocation()
135 if (getQSTransformationProgress() >= 0) {
136 updateTargetState()
137 applyTargetStateIfNotAnimating()
138 }
139 }
140 }
141
Selim Cinek7f657602020-05-21 12:37:14 -0700142 /**
143 * Are location changes currently blocked?
144 */
145 private val blockLocationChanges: Boolean
146 get() {
147 return goingToSleep || dozeAnimationRunning
148 }
149
150 /**
151 * Are we currently going to sleep
152 */
153 private var goingToSleep: Boolean = false
154 set(value) {
155 if (field != value) {
156 field = value
157 if (!value) {
158 updateDesiredLocation()
159 }
160 }
161 }
162
163 /**
164 * Is the doze animation currently Running
165 */
166 private var dozeAnimationRunning: Boolean = false
167 private set(value) {
168 if (field != value) {
169 field = value
170 if (!value) {
171 updateDesiredLocation()
172 }
173 }
174 }
175
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700176 init {
Selim Cinekf0f74952020-04-21 11:45:16 -0700177 statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
Selim Cinek2d7be5f2020-05-01 13:16:01 -0700178 override fun onStatePreChange(oldState: Int, newState: Int) {
179 // We're updating the location before the state change happens, since we want the
180 // location of the previous state to still be up to date when the animation starts
181 statusbarState = newState
Selim Cinekf0f74952020-04-21 11:45:16 -0700182 updateDesiredLocation()
183 }
Selim Cinek2d7be5f2020-05-01 13:16:01 -0700184
185 override fun onStateChanged(newState: Int) {
186 updateTargetState()
187 }
Selim Cinek7f657602020-05-21 12:37:14 -0700188
189 override fun onDozeAmountChanged(linear: Float, eased: Float) {
190 dozeAnimationRunning = linear != 0.0f && linear != 1.0f
191 }
192
193 override fun onDozingChanged(isDozing: Boolean) {
194 if (!isDozing) {
195 dozeAnimationRunning = false
196 }
197 }
198 })
199
200 wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
201 override fun onFinishedGoingToSleep() {
202 goingToSleep = false
203 }
204
205 override fun onStartedGoingToSleep() {
206 goingToSleep = true
207 }
208
209 override fun onFinishedWakingUp() {
210 goingToSleep = false
211 }
212
213 override fun onStartedWakingUp() {
214 goingToSleep = false
215 }
Selim Cinekf0f74952020-04-21 11:45:16 -0700216 })
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700217 }
218
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700219 /**
Selim Cinekb52642b2020-04-17 14:30:29 -0700220 * Register a media host and create a view can be attached to a view hierarchy
221 * and where the players will be placed in when the host is the currently desired state.
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700222 *
223 * @return the hostView associated with this location
224 */
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700225 fun register(mediaObject: MediaHost): UniqueObjectHostView {
226 val viewHost = createUniqueObjectHost()
Lucas Dupin989a1112020-05-19 18:56:28 -0700227 mediaObject.hostView = viewHost
Selim Cinekb52642b2020-04-17 14:30:29 -0700228 mediaHosts[mediaObject.location] = mediaObject
Selim Cinekf0f74952020-04-21 11:45:16 -0700229 if (mediaObject.location == desiredLocation) {
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700230 // In case we are overriding a view that is already visible, make sure we attach it
231 // to this new host view in the below call
Selim Cinekf0f74952020-04-21 11:45:16 -0700232 desiredLocation = -1
233 }
234 if (mediaObject.location == currentAttachmentLocation) {
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700235 currentAttachmentLocation = -1
236 }
Selim Cinekf0f74952020-04-21 11:45:16 -0700237 updateDesiredLocation()
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700238 return viewHost
239 }
240
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700241 private fun createUniqueObjectHost(): UniqueObjectHostView {
Selim Cinek54809622020-04-30 19:04:44 -0700242 val viewHost = UniqueObjectHostView(context)
Selim Cinekf0f74952020-04-21 11:45:16 -0700243 viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
244 override fun onViewAttachedToWindow(p0: View?) {
245 if (rootOverlay == null) {
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700246 rootView = viewHost.viewRootImpl.view
247 rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700248 }
Selim Cinekf0f74952020-04-21 11:45:16 -0700249 viewHost.removeOnAttachStateChangeListener(this)
250 }
251
252 override fun onViewDetachedFromWindow(p0: View?) {
253 }
254 })
255 return viewHost
256 }
257
Selim Cinekf418bb02020-05-04 17:16:58 -0700258 /**
259 * Updates the location that the view should be in. If it changes, an animation may be triggered
260 * going from the old desired location to the new one.
261 */
Selim Cinekf0f74952020-04-21 11:45:16 -0700262 private fun updateDesiredLocation() {
263 val desiredLocation = calculateLocation()
264 if (desiredLocation != this.desiredLocation) {
265 if (this.desiredLocation >= 0) {
266 previousLocation = this.desiredLocation
267 }
268 val isNewView = this.desiredLocation == -1
269 this.desiredLocation = desiredLocation
270 // Let's perform a transition
Selim Cinek54809622020-04-30 19:04:44 -0700271 val animate = shouldAnimateTransition(desiredLocation, previousLocation)
272 val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700273 val host = getHost(desiredLocation)
274 mediaViewManager.onDesiredLocationChanged(desiredLocation, host, animate, animDuration,
275 delay)
Selim Cinek54809622020-04-30 19:04:44 -0700276 performTransitionToNewLocation(isNewView, animate)
Selim Cinekf0f74952020-04-21 11:45:16 -0700277 }
278 }
279
Selim Cinek54809622020-04-30 19:04:44 -0700280 private fun performTransitionToNewLocation(isNewView: Boolean, animate: Boolean) {
281 if (previousLocation < 0 || isNewView) {
Selim Cinekf0f74952020-04-21 11:45:16 -0700282 cancelAnimationAndApplyDesiredState()
283 return
284 }
285 val currentHost = getHost(desiredLocation)
286 val previousHost = getHost(previousLocation)
287 if (currentHost == null || previousHost == null) {
288 cancelAnimationAndApplyDesiredState()
Selim Cinek2d7be5f2020-05-01 13:16:01 -0700289 return
Selim Cinekf0f74952020-04-21 11:45:16 -0700290 }
291 updateTargetState()
292 if (isCurrentlyInGuidedTransformation()) {
293 applyTargetStateIfNotAnimating()
Selim Cinek54809622020-04-30 19:04:44 -0700294 } else if (animate) {
Selim Cinekf0f74952020-04-21 11:45:16 -0700295 animator.cancel()
Lucas Dupin989a1112020-05-19 18:56:28 -0700296 if (currentAttachmentLocation == IN_OVERLAY ||
297 !previousHost.hostView.isAttachedToWindow) {
Selim Cinek2d7be5f2020-05-01 13:16:01 -0700298 // Let's animate to the new position, starting from the current position
299 // We also go in here in case the view was detached, since the bounds wouldn't
300 // be correct anymore
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700301 animationStartBounds.set(currentBounds)
Selim Cinek2d7be5f2020-05-01 13:16:01 -0700302 } else {
303 // otherwise, let's take the freshest state, since the current one could
304 // be outdated
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700305 animationStartBounds.set(previousHost.currentBounds)
Selim Cinek2d7be5f2020-05-01 13:16:01 -0700306 }
Selim Cinek54809622020-04-30 19:04:44 -0700307 adjustAnimatorForTransition(desiredLocation, previousLocation)
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700308 rootView?.let {
309 // Let's delay the animation start until we finished laying out
310 animationPending = true
311 it.postOnAnimation(startAnimation)
312 }
Selim Cinekf0f74952020-04-21 11:45:16 -0700313 } else {
314 cancelAnimationAndApplyDesiredState()
315 }
316 }
317
Selim Cinek54809622020-04-30 19:04:44 -0700318 private fun shouldAnimateTransition(
319 @MediaLocation currentLocation: Int,
320 @MediaLocation previousLocation: Int
321 ): Boolean {
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700322 if (isCurrentlyInGuidedTransformation()) {
323 return false
324 }
Lucas Dupin989a1112020-05-19 18:56:28 -0700325 if (currentLocation == LOCATION_QQS &&
326 previousLocation == LOCATION_LOCKSCREEN &&
327 (statusBarStateController.leaveOpenOnKeyguardHide() ||
328 statusbarState == StatusBarState.SHADE_LOCKED)) {
Selim Cinekf0f74952020-04-21 11:45:16 -0700329 // Usually listening to the isShown is enough to determine this, but there is some
330 // non-trivial reattaching logic happening that will make the view not-shown earlier
331 return true
332 }
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700333 return mediaFrame.isShown || animator.isRunning || animationPending
Selim Cinekf0f74952020-04-21 11:45:16 -0700334 }
335
Selim Cinek54809622020-04-30 19:04:44 -0700336 private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) {
337 val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
338 animator.apply {
Lucas Dupin989a1112020-05-19 18:56:28 -0700339 duration = animDuration
Selim Cinek54809622020-04-30 19:04:44 -0700340 startDelay = delay
341 }
Selim Cinek54809622020-04-30 19:04:44 -0700342 }
343
344 private fun getAnimationParams(previousLocation: Int, desiredLocation: Int): Pair<Long, Long> {
Selim Cinekf0f74952020-04-21 11:45:16 -0700345 var animDuration = 200L
346 var delay = 0L
347 if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
348 // Going to the full shade, let's adjust the animation duration
Lucas Dupin989a1112020-05-19 18:56:28 -0700349 if (statusbarState == StatusBarState.SHADE &&
350 keyguardStateController.isKeyguardFadingAway) {
Selim Cinekf0f74952020-04-21 11:45:16 -0700351 delay = keyguardStateController.keyguardFadingAwayDelay
352 }
353 animDuration = StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE.toLong()
354 } else if (previousLocation == LOCATION_QQS && desiredLocation == LOCATION_LOCKSCREEN) {
355 animDuration = StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR.toLong()
356 }
Selim Cinek54809622020-04-30 19:04:44 -0700357 return animDuration to delay
Selim Cinekf0f74952020-04-21 11:45:16 -0700358 }
359
360 private fun applyTargetStateIfNotAnimating() {
361 if (!animator.isRunning) {
362 // Let's immediately apply the target state (which is interpolated) if there is
363 // no animation running. Otherwise the animation update will already update
364 // the location
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700365 applyState(targetBounds)
Selim Cinekf0f74952020-04-21 11:45:16 -0700366 }
367 }
368
Selim Cinekf418bb02020-05-04 17:16:58 -0700369 /**
370 * Updates the state that the view wants to be in at the end of the animation.
371 */
Selim Cinekf0f74952020-04-21 11:45:16 -0700372 private fun updateTargetState() {
373 if (isCurrentlyInGuidedTransformation()) {
374 val progress = getTransformationProgress()
375 val currentHost = getHost(desiredLocation)!!
376 val previousHost = getHost(previousLocation)!!
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700377 val newBounds = currentHost.currentBounds
378 val previousBounds = previousHost.currentBounds
379 targetBounds = interpolateBounds(previousBounds, newBounds, progress)
Selim Cinekf0f74952020-04-21 11:45:16 -0700380 } else {
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700381 val bounds = getHost(desiredLocation)?.currentBounds ?: return
382 targetBounds.set(bounds)
Selim Cinekf0f74952020-04-21 11:45:16 -0700383 }
384 }
385
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700386 private fun interpolateBounds(
387 startBounds: Rect,
388 endBounds: Rect,
389 progress: Float,
390 result: Rect? = null
391 ): Rect {
392 val left = MathUtils.lerp(startBounds.left.toFloat(),
393 endBounds.left.toFloat(), progress).toInt()
394 val top = MathUtils.lerp(startBounds.top.toFloat(),
395 endBounds.top.toFloat(), progress).toInt()
396 val right = MathUtils.lerp(startBounds.right.toFloat(),
397 endBounds.right.toFloat(), progress).toInt()
398 val bottom = MathUtils.lerp(startBounds.bottom.toFloat(),
399 endBounds.bottom.toFloat(), progress).toInt()
400 val resultBounds = result ?: Rect()
401 resultBounds.set(left, top, right, bottom)
402 return resultBounds
403 }
404
Selim Cinekf0f74952020-04-21 11:45:16 -0700405 /**
406 * @return true if this transformation is guided by an external progress like a finger
407 */
Lucas Dupin989a1112020-05-19 18:56:28 -0700408 private fun isCurrentlyInGuidedTransformation(): Boolean {
Selim Cinekf0f74952020-04-21 11:45:16 -0700409 return getTransformationProgress() >= 0
410 }
411
412 /**
Lucas Dupin989a1112020-05-19 18:56:28 -0700413 * @return the current transformation progress if we're in a guided transformation and -1
Selim Cinekf0f74952020-04-21 11:45:16 -0700414 * otherwise
415 */
416 private fun getTransformationProgress(): Float {
417 val progress = getQSTransformationProgress()
418 if (progress >= 0) {
419 return progress
420 }
421 return -1.0f
422 }
423
424 private fun getQSTransformationProgress(): Float {
425 val currentHost = getHost(desiredLocation)
426 val previousHost = getHost(previousLocation)
427 if (currentHost?.location == LOCATION_QS) {
428 if (previousHost?.location == LOCATION_QQS) {
429 return qsExpansion
430 }
431 }
432 return -1.0f
433 }
434
435 private fun getHost(@MediaLocation location: Int): MediaHost? {
436 if (location < 0) {
437 return null
438 }
439 return mediaHosts[location]
440 }
441
442 private fun cancelAnimationAndApplyDesiredState() {
443 animator.cancel()
444 getHost(desiredLocation)?.let {
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700445 applyState(it.currentBounds, immediately = true)
Selim Cinekf0f74952020-04-21 11:45:16 -0700446 }
447 }
448
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700449 /**
450 * Apply the current state to the view, updating it's bounds and desired state
451 */
452 private fun applyState(bounds: Rect, immediately: Boolean = false) {
453 currentBounds.set(bounds)
454 val currentlyInGuidedTransformation = isCurrentlyInGuidedTransformation()
455 val startLocation = if (currentlyInGuidedTransformation) previousLocation else -1
456 val progress = if (currentlyInGuidedTransformation) getTransformationProgress() else 1.0f
457 val endLocation = desiredLocation
458 mediaViewManager.setCurrentState(startLocation, endLocation, progress, immediately)
Selim Cinekf0f74952020-04-21 11:45:16 -0700459 updateHostAttachment()
Selim Cinekf0f74952020-04-21 11:45:16 -0700460 if (currentAttachmentLocation == IN_OVERLAY) {
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700461 mediaFrame.setLeftTopRightBottom(
462 currentBounds.left,
463 currentBounds.top,
464 currentBounds.right,
465 currentBounds.bottom)
Selim Cinekf0f74952020-04-21 11:45:16 -0700466 }
Selim Cinekf0f74952020-04-21 11:45:16 -0700467 }
468
469 private fun updateHostAttachment() {
470 val inOverlay = isTransitionRunning() && rootOverlay != null
471 val newLocation = if (inOverlay) IN_OVERLAY else desiredLocation
472 if (currentAttachmentLocation != newLocation) {
473 currentAttachmentLocation = newLocation
474
475 // Remove the carousel from the old host
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700476 (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame)
Selim Cinekf0f74952020-04-21 11:45:16 -0700477
478 // Add it to the new one
479 val targetHost = getHost(desiredLocation)!!.hostView
480 if (inOverlay) {
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700481 rootOverlay!!.add(mediaFrame)
Selim Cinekf0f74952020-04-21 11:45:16 -0700482 } else {
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700483 targetHost.addView(mediaFrame)
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700484 }
485 }
486 }
487
Selim Cinekf0f74952020-04-21 11:45:16 -0700488 private fun isTransitionRunning(): Boolean {
Lucas Dupin989a1112020-05-19 18:56:28 -0700489 return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f ||
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700490 animator.isRunning || animationPending
Selim Cinekb52642b2020-04-17 14:30:29 -0700491 }
492
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700493 @MediaLocation
Lucas Dupin989a1112020-05-19 18:56:28 -0700494 private fun calculateLocation(): Int {
Selim Cinek7f657602020-05-21 12:37:14 -0700495 if (blockLocationChanges) {
496 // Keep the current location until we're allowed to again
497 return desiredLocation
498 }
Lucas Dupin989a1112020-05-19 18:56:28 -0700499 val onLockscreen = (!bypassController.bypassEnabled &&
500 (statusbarState == StatusBarState.KEYGUARD ||
501 statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
502 val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700503 return when {
Selim Cinekf0f74952020-04-21 11:45:16 -0700504 qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
505 qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
Lucas Dupin989a1112020-05-19 18:56:28 -0700506 onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700507 else -> LOCATION_QQS
508 }
509 }
510
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700511 companion object {
512 /**
513 * Attached in expanded quick settings
514 */
515 const val LOCATION_QS = 0
516
517 /**
518 * Attached in the collapsed QS
519 */
520 const val LOCATION_QQS = 1
521
522 /**
523 * Attached on the lock screen
524 */
525 const val LOCATION_LOCKSCREEN = 2
Selim Cinekf0f74952020-04-21 11:45:16 -0700526
527 /**
528 * Attached at the root of the hierarchy in an overlay
529 */
530 const val IN_OVERLAY = -1000
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700531 }
Robert Snoeberger52dfb622020-05-21 16:31:29 -0400532}
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700533
534@IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
535 MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
536@Retention(AnnotationRetention.SOURCE)
537annotation class MediaLocation