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