• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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.notification.stack.domain.interactor
17 
18 import android.graphics.Rect
19 import android.util.Log
20 import com.android.app.tracing.FlowTracing.traceEach
21 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.power.domain.interactor.PowerInteractor
24 import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
25 import com.android.systemui.shade.ShadeDisplayAware
26 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
27 import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
28 import com.android.systemui.util.kotlin.WithPrev
29 import com.android.systemui.util.kotlin.area
30 import com.android.systemui.util.kotlin.pairwise
31 import com.android.systemui.util.kotlin.race
32 import javax.inject.Inject
33 import kotlinx.coroutines.TimeoutCancellationException
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.distinctUntilChanged
36 import kotlinx.coroutines.flow.emptyFlow
37 import kotlinx.coroutines.flow.filter
38 import kotlinx.coroutines.flow.first
39 import kotlinx.coroutines.flow.flatMapLatest
40 import kotlinx.coroutines.flow.flow
41 import kotlinx.coroutines.withTimeout
42 
43 @SysUISingleton
44 class HideNotificationsInteractor
45 @Inject
46 constructor(
47     private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
48     @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
49     private val animationsStatus: AnimationStatusRepository,
50     private val powerInteractor: PowerInteractor,
51 ) {
52 
53     val shouldHideNotifications: Flow<Boolean>
54         get() =
55             if (!unfoldTransitionInteractor.isAvailable) {
56                 // Do nothing on non-foldable devices
57                 emptyFlow()
58             } else {
59                 screenSizeChangesFlow
60                     .flatMapLatest {
61                         flow {
62                             // Hide notifications on each display resize
63                             emit(true)
64                             try {
65                                 waitForDisplaySwitchFinish(it)
66                             } catch (_: TimeoutCancellationException) {
67                                 Log.e(TAG, "Timed out waiting for display switch")
68                             } finally {
69                                 emit(false)
70                             }
71                         }
72                     }
73                     .distinctUntilChanged()
74                     .traceEach(HIDE_STATUS_TRACK_NAME, logcat = true) { shouldHide ->
75                         if (shouldHide) "hidden" else "visible"
76                     }
77             }
78 
79     private suspend fun waitForDisplaySwitchFinish(screenSizeChange: WithPrev<Rect, Rect>) {
80         withTimeout(timeMillis = DISPLAY_SWITCH_TIMEOUT_MILLIS) {
81             val waitForDisplaySwitchOrAnimation: suspend () -> Unit = {
82                 if (shouldWaitForAnimationEnd(screenSizeChange)) {
83                     unfoldTransitionInteractor.waitForTransitionFinish()
84                 } else {
85                     waitForScreenTurnedOn()
86                 }
87             }
88 
89             race({ waitForDisplaySwitchOrAnimation() }, { waitForGoingToSleep() })
90         }
91     }
92 
93     private suspend fun shouldWaitForAnimationEnd(screenSizeChange: WithPrev<Rect, Rect>): Boolean =
94         animationsStatus.areAnimationsEnabled().first() && screenSizeChange.isUnfold
95 
96     private suspend fun waitForScreenTurnedOn() =
97         powerInteractor.screenPowerState.filter { it == SCREEN_ON }.first()
98 
99     private suspend fun waitForGoingToSleep() =
100         powerInteractor.detailedWakefulness.filter { it.isAsleep() }.first()
101 
102     private val screenSizeChangesFlow: Flow<WithPrev<Rect, Rect>>
103         get() = configurationInteractor.naturalMaxBounds.pairwise()
104 
105     private val WithPrev<Rect, Rect>.isUnfold: Boolean
106         get() = newValue.area > previousValue.area
107 
108     private companion object {
109         private const val TAG = "DisplaySwitchNotificationsHideInteractor"
110         private const val HIDE_STATUS_TRACK_NAME = "NotificationsHiddenForDisplayChange"
111         private const val DISPLAY_SWITCH_TIMEOUT_MILLIS = 5_000L
112     }
113 }
114