1 /*
<lambda>null2  * Copyright (C) 2024 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 package com.android.wallpaper.testing
17 
18 import android.content.Context
19 import android.content.res.Configuration.ORIENTATION_LANDSCAPE
20 import android.content.res.Configuration.ORIENTATION_PORTRAIT
21 import android.content.res.Configuration.Orientation
22 import android.graphics.Point
23 import android.hardware.display.DisplayManager
24 import android.view.Display
25 import com.android.wallpaper.util.DisplaysProvider
26 import dagger.hilt.android.qualifiers.ApplicationContext
27 import javax.inject.Inject
28 import javax.inject.Singleton
29 import org.robolectric.shadows.ShadowDisplayManager
30 
31 /**
32  * Uses ShadowDisplayManager to create fake displays for testing, due to the difficulty in directly
33  * creating Display instances.
34  *
35  * The limitations of the fake displays include:
36  * - The created display's type will not be internal, but will function the same when testing
37  *   DisplayUtils.
38  * - The created displays will have the same uniqueId, and is not customizable through
39  *   ShadowDisplayManager.
40  */
41 @Singleton
42 class FakeDisplaysProvider
43 @Inject
44 constructor(@ApplicationContext private val appContext: Context) : DisplaysProvider {
45     private val displayManager =
46         appContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
47 
48     fun setDisplays(displayConfigs: List<FakeDisplayConfig>) {
49         ShadowDisplayManager.reset()
50         displayConfigs.forEachIndexed { index, config ->
51             if (index == 0) {
52                 ShadowDisplayManager.changeDisplay(
53                     Display.DEFAULT_DISPLAY,
54                     configToQualifierString(config)
55                 )
56                 config.naturallyPortrait?.let {
57                     ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, it)
58                 }
59                 appContext.resources.configuration.densityDpi = config.dpi
60             } else {
61                 val displayId = ShadowDisplayManager.addDisplay(configToQualifierString(config))
62                 config.naturallyPortrait?.let {
63                     ShadowDisplayManager.setNaturallyPortrait(displayId, it)
64                 }
65             }
66         }
67     }
68 
69     fun configToQualifierString(config: FakeDisplayConfig): String {
70         val suffix =
71             if (config.orientation == ORIENTATION_LANDSCAPE) "-land"
72             else if (config.orientation == ORIENTATION_PORTRAIT) "-port" else ""
73         return "w${config.displaySize.x}dp-h${config.displaySize.y}dp$suffix"
74     }
75 
76     override fun getInternalDisplays(): List<Display> {
77         val allDisplays: Array<out Display> =
78             displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
79         return allDisplays.toList()
80     }
81 
82     data class FakeDisplayConfig(
83         /**
84          * The display width and height in pixels. When returned from the Display, it is adjusted
85          * based on the orientation.
86          */
87         val displaySize: Point,
88         /** Whether the device is naturally portrait, used to determine the degree of rotation. */
89         val naturallyPortrait: Boolean? = null,
90         /**
91          * The current orientation of the device. The Display adjusts its size accordingly, with x
92          * as the larger dimension if landscape, and y as the larger dimension if portrait.
93          */
94         @Orientation val orientation: Int? = null,
95         /**
96          * The DPI of a device, used to calculate screen size. This value needs to be set in
97          * appContext.resources.configuration.densityDpi to take effect.
98          */
99         val dpi: Int = 1,
100     )
101 
102     companion object {
103         // Common display sizes used for testing
104         val HANDHELD = FakeDisplayConfig(Point(1440, 3120), true, ORIENTATION_PORTRAIT, 560)
105         val FOLDABLE_FOLDED = FakeDisplayConfig(Point(1080, 2092), true, ORIENTATION_PORTRAIT, 408)
106         val FOLDABLE_UNFOLDED_LAND =
107             FakeDisplayConfig(Point(2208, 1840), false, ORIENTATION_LANDSCAPE, 380)
108         val FOLDABLE_UNFOLDED_PORT =
109             FakeDisplayConfig(Point(2208, 1840), false, ORIENTATION_PORTRAIT, 380)
110         val TABLET_LAND = FakeDisplayConfig(Point(2560, 1600), false, ORIENTATION_LANDSCAPE, 276)
111         val TABLET_PORT = FakeDisplayConfig(Point(2560, 1600), false, ORIENTATION_PORTRAIT, 276)
112     }
113 }
114