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 @file:OptIn(ExperimentalCoroutinesApi::class) 17 18 package com.android.systemui.common.ui.domain.interactor 19 20 import android.content.res.Configuration 21 import android.graphics.Rect 22 import android.view.Surface 23 import com.android.systemui.common.ui.data.repository.ConfigurationRepository 24 import com.android.systemui.dagger.SysUISingleton 25 import javax.inject.Inject 26 import kotlinx.coroutines.ExperimentalCoroutinesApi 27 import kotlinx.coroutines.flow.Flow 28 import kotlinx.coroutines.flow.distinctUntilChanged 29 import kotlinx.coroutines.flow.map 30 import kotlinx.coroutines.flow.mapLatest 31 import kotlinx.coroutines.flow.onStart 32 33 /** Business logic related to configuration changes. */ 34 @SysUISingleton 35 class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) { 36 /** 37 * Returns screen size adjusted to rotation, so returned screen size is stable across all 38 * rotations 39 */ 40 private val Configuration.naturalScreenBounds: Rect 41 get() { 42 val rotation = windowConfiguration.displayRotation 43 val maxBounds = windowConfiguration.maxBounds 44 return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { 45 Rect(0, 0, maxBounds.width(), maxBounds.height()) 46 } else { 47 Rect(0, 0, maxBounds.height(), maxBounds.width()) 48 } 49 } 50 51 /** Returns the unadjusted screen size. */ 52 val maxBounds: Flow<Rect> = 53 repository.configurationValues <lambda>null54 .map { Rect(it.windowConfiguration.maxBounds) } 55 .distinctUntilChanged() 56 57 /** 58 * Returns screen size adjusted to rotation, so returned screen sizes are stable across all 59 * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on 60 * foldable devices) 61 */ 62 val naturalMaxBounds: Flow<Rect> = <lambda>null63 repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged() 64 65 /** 66 * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or 67 * `View#LAYOUT_DIRECTION_RTL`. 68 */ 69 val layoutDirection: Flow<Int> = <lambda>null70 repository.configurationValues.map { it.layoutDirection }.distinctUntilChanged() 71 72 /** Given [resourceId], emit the dimension pixel size on config change */ dimensionPixelSizenull73 fun dimensionPixelSize(resourceId: Int): Flow<Int> { 74 return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) } 75 } 76 77 /** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */ dimensionPixelSizenull78 fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> { 79 return onAnyConfigurationChange.mapLatest { 80 resourceIds.associateWith { repository.getDimensionPixelSize(it) } 81 } 82 } 83 84 /** Emit an event on any config change */ 85 val onAnyConfigurationChange: Flow<Unit> = <lambda>null86 repository.onAnyConfigurationChange.onStart { emit(Unit) } 87 88 /** Emits the new configuration on any configuration change */ 89 val configurationValues: Flow<Configuration> = repository.configurationValues 90 91 /** Emits the current resolution scaling factor */ 92 val scaleForResolution: Flow<Float> = repository.scaleForResolution 93 } 94