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