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.util.DisplayMetrics 21 import android.util.Size 22 import android.view.DisplayInfo 23 import com.android.systemui.biometrics.shared.model.DisplayRotation 24 import com.android.systemui.biometrics.shared.model.toDisplayRotation 25 import com.android.systemui.dagger.SysUISingleton 26 import com.android.systemui.dagger.qualifiers.Application 27 import com.android.systemui.dagger.qualifiers.Background 28 import com.android.systemui.display.data.repository.DeviceStateRepository 29 import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.REAR_DISPLAY 30 import com.android.systemui.display.data.repository.DisplayRepository 31 import javax.inject.Inject 32 import kotlin.math.min 33 import kotlinx.coroutines.CoroutineScope 34 import kotlinx.coroutines.flow.SharingStarted 35 import kotlinx.coroutines.flow.StateFlow 36 import kotlinx.coroutines.flow.map 37 import kotlinx.coroutines.flow.stateIn 38 39 /** Repository for the current state of the display */ 40 interface DisplayStateRepository { 41 /** 42 * If true, the direction rotation is applied to get to an application's requested orientation 43 * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a 44 * portrait device an app requesting landscape will cause a clockwise rotation, and on a 45 * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting 46 * true here reverses that logic. See go/natural-orientation for context. 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 /** Provides the current display size */ 57 val currentDisplaySize: StateFlow<Size> 58 59 /** Provides whether the current display is large screen */ 60 val isLargeScreen: StateFlow<Boolean> 61 } 62 63 @SysUISingleton 64 class DisplayStateRepositoryImpl 65 @Inject 66 constructor( 67 @Background backgroundScope: CoroutineScope, 68 @Application val context: Context, 69 deviceStateRepository: DeviceStateRepository, 70 displayRepository: DisplayRepository, 71 ) : DisplayStateRepository { 72 override val isReverseDefaultRotation = 73 context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation) 74 75 override val isInRearDisplayMode: StateFlow<Boolean> = 76 deviceStateRepository.state <lambda>null77 .map { it == REAR_DISPLAY } 78 .stateIn( 79 backgroundScope, 80 started = SharingStarted.Eagerly, 81 initialValue = false, 82 ) 83 getDisplayInfonull84 private fun getDisplayInfo(): DisplayInfo { 85 val cachedDisplayInfo = DisplayInfo() 86 context.display?.getDisplayInfo(cachedDisplayInfo) 87 return cachedDisplayInfo 88 } 89 90 private val currentDisplayInfo: StateFlow<DisplayInfo> = 91 displayRepository.displayChangeEvent <lambda>null92 .map { getDisplayInfo() } 93 .stateIn( 94 backgroundScope, 95 started = SharingStarted.Eagerly, 96 initialValue = getDisplayInfo(), 97 ) 98 rotationToDisplayRotationnull99 private fun rotationToDisplayRotation(rotation: Int): DisplayRotation { 100 var adjustedRotation = rotation 101 if (isReverseDefaultRotation) { 102 adjustedRotation = (rotation + 1) % 4 103 } 104 return adjustedRotation.toDisplayRotation() 105 } 106 107 override val currentRotation: StateFlow<DisplayRotation> = 108 currentDisplayInfo <lambda>null109 .map { rotationToDisplayRotation(it.rotation) } 110 .stateIn( 111 backgroundScope, 112 started = SharingStarted.WhileSubscribed(), 113 initialValue = rotationToDisplayRotation(currentDisplayInfo.value.rotation) 114 ) 115 116 override val currentDisplaySize: StateFlow<Size> = 117 currentDisplayInfo <lambda>null118 .map { Size(it.naturalWidth, it.naturalHeight) } 119 .stateIn( 120 backgroundScope, 121 started = SharingStarted.WhileSubscribed(), 122 initialValue = 123 Size( 124 currentDisplayInfo.value.naturalWidth, 125 currentDisplayInfo.value.naturalHeight 126 ), 127 ) 128 129 override val isLargeScreen: StateFlow<Boolean> = 130 currentDisplayInfo <lambda>null131 .map { 132 // copied from systemui/shared/...Utilities.java 133 val smallestWidth = 134 dpiFromPx( 135 min(it.logicalWidth, it.logicalHeight).toFloat(), 136 context.resources.configuration.densityDpi 137 ) 138 smallestWidth >= LARGE_SCREEN_MIN_DPS 139 } 140 .stateIn( 141 backgroundScope, 142 started = SharingStarted.Eagerly, 143 initialValue = false, 144 ) 145 dpiFromPxnull146 private fun dpiFromPx(size: Float, densityDpi: Int): Float { 147 val densityRatio = densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT 148 return size / densityRatio 149 } 150 151 companion object { 152 const val TAG = "DisplayStateRepositoryImpl" 153 const val LARGE_SCREEN_MIN_DPS = 600f 154 } 155 } 156