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