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.data.repository
18 
19 import android.content.Context
20 import android.hardware.devicestate.DeviceStateManager
21 import android.hardware.display.DisplayManager
22 import android.hardware.display.DisplayManager.DisplayListener
23 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
24 import android.os.Handler
25 import android.view.DisplayInfo
26 import com.android.internal.util.ArrayUtils
27 import com.android.systemui.biometrics.shared.model.DisplayRotation
28 import com.android.systemui.biometrics.shared.model.toDisplayRotation
29 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
30 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
31 import com.android.systemui.dagger.SysUISingleton
32 import com.android.systemui.dagger.qualifiers.Application
33 import com.android.systemui.dagger.qualifiers.Main
34 import java.util.concurrent.Executor
35 import javax.inject.Inject
36 import kotlinx.coroutines.CoroutineScope
37 import kotlinx.coroutines.channels.awaitClose
38 import kotlinx.coroutines.flow.SharingStarted
39 import kotlinx.coroutines.flow.StateFlow
40 import kotlinx.coroutines.flow.stateIn
41 
42 /** Repository for the current state of the display */
43 interface DisplayStateRepository {
44     /**
45      * Whether or not the direction rotation is applied to get to an application's requested
46      * orientation is reversed.
47      */
48     val isReverseDefaultRotation: Boolean
49 
50     /** Provides the current rear display state. */
51     val isInRearDisplayMode: StateFlow<Boolean>
52 
53     /** Provides the current display rotation */
54     val currentRotation: StateFlow<DisplayRotation>
55 }
56 
57 @SysUISingleton
58 class DisplayStateRepositoryImpl
59 @Inject
60 constructor(
61     @Application applicationScope: CoroutineScope,
62     @Application val context: Context,
63     deviceStateManager: DeviceStateManager,
64     displayManager: DisplayManager,
65     @Main handler: Handler,
66     @Main mainExecutor: Executor
67 ) : DisplayStateRepository {
68     override val isReverseDefaultRotation =
69         context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
70 
71     override val isInRearDisplayMode: StateFlow<Boolean> =
72         conflatedCallbackFlow {
73                 val sendRearDisplayStateUpdate = { state: Boolean ->
74                     trySendWithFailureLogging(
75                         state,
76                         TAG,
77                         "Error sending rear display state update to $state"
78                     )
79                 }
80 
81                 val callback =
82                     DeviceStateManager.DeviceStateCallback { state ->
83                         val isInRearDisplayMode =
84                             ArrayUtils.contains(
85                                 context.resources.getIntArray(
86                                     com.android.internal.R.array.config_rearDisplayDeviceStates
87                                 ),
88                                 state
89                             )
90                         sendRearDisplayStateUpdate(isInRearDisplayMode)
91                     }
92 
93                 sendRearDisplayStateUpdate(false)
94                 deviceStateManager.registerCallback(mainExecutor, callback)
95                 awaitClose { deviceStateManager.unregisterCallback(callback) }
96             }
97             .stateIn(
98                 applicationScope,
99                 started = SharingStarted.Eagerly,
100                 initialValue = false,
101             )
102 
103     private fun getDisplayRotation(): DisplayRotation {
104         val cachedDisplayInfo = DisplayInfo()
105         context.display?.getDisplayInfo(cachedDisplayInfo)
106         var rotation = cachedDisplayInfo.rotation
107         if (isReverseDefaultRotation) {
108             rotation = (rotation + 1) % 4
109         }
110         return rotation.toDisplayRotation()
111     }
112 
113     override val currentRotation: StateFlow<DisplayRotation> =
114         conflatedCallbackFlow {
115                 val callback =
116                     object : DisplayListener {
117                         override fun onDisplayRemoved(displayId: Int) {}
118 
119                         override fun onDisplayAdded(displayId: Int) {}
120 
121                         override fun onDisplayChanged(displayId: Int) {
122                             val rotation = getDisplayRotation()
123                             trySendWithFailureLogging(
124                                 rotation,
125                                 TAG,
126                                 "Error sending display rotation to $rotation"
127                             )
128                         }
129                     }
130                 displayManager.registerDisplayListener(
131                     callback,
132                     handler,
133                     EVENT_FLAG_DISPLAY_CHANGED
134                 )
135                 awaitClose { displayManager.unregisterDisplayListener(callback) }
136             }
137             .stateIn(
138                 applicationScope,
139                 started = SharingStarted.Eagerly,
140                 initialValue = getDisplayRotation(),
141             )
142 
143     companion object {
144         const val TAG = "DisplayStateRepositoryImpl"
145     }
146 }
147