1 /*
2  * 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 
17 package com.android.systemui.biometrics.domain.interactor
18 
19 import android.content.Context
20 import android.content.res.Configuration
21 import com.android.systemui.biometrics.data.repository.DisplayStateRepository
22 import com.android.systemui.biometrics.shared.model.DisplayRotation
23 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
24 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
25 import com.android.systemui.dagger.qualifiers.Application
26 import com.android.systemui.dagger.qualifiers.Main
27 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
28 import com.android.systemui.unfold.updates.FoldProvider
29 import java.util.concurrent.Executor
30 import javax.inject.Inject
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.channels.awaitClose
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.SharingStarted
35 import kotlinx.coroutines.flow.StateFlow
36 import kotlinx.coroutines.flow.stateIn
37 
38 /** Aggregates display state information. */
39 interface DisplayStateInteractor {
40 
41     /** Whether the device is currently in rear display mode. */
42     val isInRearDisplayMode: StateFlow<Boolean>
43 
44     /** Whether the device is currently folded. */
45     val isFolded: Flow<Boolean>
46 
47     /** Current rotation of the display */
48     val currentRotation: StateFlow<DisplayRotation>
49 
50     /** Called on configuration changes, used to keep the display state in sync */
51     fun onConfigurationChanged(newConfig: Configuration)
52 }
53 
54 /** Encapsulates logic for interacting with the display state. */
55 class DisplayStateInteractorImpl
56 @Inject
57 constructor(
58     @Application applicationScope: CoroutineScope,
59     @Application context: Context,
60     @Main mainExecutor: Executor,
61     displayStateRepository: DisplayStateRepository,
62 ) : DisplayStateInteractor {
63     private var screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
64 
65     fun setScreenSizeFoldProvider(foldProvider: ScreenSizeFoldProvider) {
66         screenSizeFoldProvider = foldProvider
67     }
68 
69     override val isFolded: Flow<Boolean> =
70         conflatedCallbackFlow {
71                 val sendFoldStateUpdate = { state: Boolean ->
72                     trySendWithFailureLogging(
73                         state,
74                         TAG,
75                         "Error sending fold state update to $state"
76                     )
77                 }
78 
79                 val callback =
80                     object : FoldProvider.FoldCallback {
81                         override fun onFoldUpdated(isFolded: Boolean) {
82                             sendFoldStateUpdate(isFolded)
83                         }
84                     }
85 
86                 sendFoldStateUpdate(false)
87                 screenSizeFoldProvider.registerCallback(callback, mainExecutor)
88                 awaitClose { screenSizeFoldProvider.unregisterCallback(callback) }
89             }
90             .stateIn(
91                 applicationScope,
92                 started = SharingStarted.Eagerly,
93                 initialValue = false,
94             )
95 
96     override val isInRearDisplayMode: StateFlow<Boolean> =
97         displayStateRepository.isInRearDisplayMode
98 
99     override val currentRotation: StateFlow<DisplayRotation> =
100         displayStateRepository.currentRotation
101 
102     override fun onConfigurationChanged(newConfig: Configuration) {
103         screenSizeFoldProvider.onConfigurationChange(newConfig)
104     }
105 
106     companion object {
107         private const val TAG = "DisplayStateInteractor"
108     }
109 }
110