summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jordan Demeulenaere <jdemeulenaere@google.com> 2022-07-11 17:50:34 +0200
committer Jordan Demeulenaere <jdemeulenaere@google.com> 2022-07-12 11:13:02 +0200
commita38f25ded64f61973d745c67fcdd107501c1d42f (patch)
tree9f763170a628bddbdd58bfdee955148d23b8fb23
parent8a3b78ac7f7be0dce955c0c985a2c7f3af1877b4 (diff)
Add support for device emulation in the Gallery app
This CL adds support for device emulation in the Gallery app so that we can easily emulate any device on another device, e.g. emulate a phone on a tablet or a emulate tablet on a phone. The emulation is done at the device level, so that might even be useful outside of the Gallery app, e.g. when working on SystemUI. See b/231131244#comment11 for a quick video. Bug: 231131244 Test: Manual Change-Id: I64094369c32b796ad41a274d23e0910b7c61a3b0
-rw-r--r--packages/SystemUI/compose/gallery/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/compose/gallery/app/AndroidManifest.xml4
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt114
3 files changed, 118 insertions, 3 deletions
diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml
index 562e1cc3339a..4fcce0bf6968 100644
--- a/packages/SystemUI/compose/gallery/AndroidManifest.xml
+++ b/packages/SystemUI/compose/gallery/AndroidManifest.xml
@@ -17,5 +17,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.systemui.compose.gallery">
-
+ <!-- To emulate a display size and density. -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
</manifest>
diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
index 81132632079a..e7d496c2cb35 100644
--- a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
+++ b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.systemui.compose.gallery">
+ package="com.android.systemui.compose.gallery.app">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@@ -25,7 +25,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.SystemUIGallery">
<activity
- android:name=".GalleryActivity"
+ android:name="com.android.systemui.compose.gallery.GalleryActivity"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
index 65ea88a8eede..06bdad8a6735 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
@@ -1,5 +1,10 @@
package com.android.systemui.compose.gallery
+import android.graphics.Point
+import android.os.UserHandle
+import android.view.Display
+import android.view.WindowManagerGlobal
+import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
@@ -9,14 +14,25 @@ import androidx.compose.material.icons.filled.BrightnessLow
import androidx.compose.material.icons.filled.FormatSize
import androidx.compose.material.icons.filled.FormatTextdirectionLToR
import androidx.compose.material.icons.filled.FormatTextdirectionRToL
+import androidx.compose.material.icons.filled.Smartphone
+import androidx.compose.material.icons.filled.Tablet
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import kotlin.math.max
+import kotlin.math.min
enum class FontScale(val scale: Float) {
Small(0.85f),
@@ -35,6 +51,39 @@ fun ConfigurationControls(
onChangeLayoutDirection: () -> Unit,
onChangeFontScale: () -> Unit,
) {
+ // The display we are emulating, if any.
+ var emulatedDisplayName by rememberSaveable { mutableStateOf<String?>(null) }
+ val emulatedDisplay =
+ emulatedDisplayName?.let { name -> EmulatedDisplays.firstOrNull { it.name == name } }
+
+ LaunchedEffect(emulatedDisplay) {
+ val wm = WindowManagerGlobal.getWindowManagerService()
+
+ val defaultDisplayId = Display.DEFAULT_DISPLAY
+ if (emulatedDisplay == null) {
+ wm.clearForcedDisplayDensityForUser(defaultDisplayId, UserHandle.myUserId())
+ wm.clearForcedDisplaySize(defaultDisplayId)
+ } else {
+ val density = emulatedDisplay.densityDpi
+
+ // Emulate the display and make sure that we use the maximum available space possible.
+ val initialSize = Point()
+ wm.getInitialDisplaySize(defaultDisplayId, initialSize)
+ val width = emulatedDisplay.width
+ val height = emulatedDisplay.height
+ val minOfSize = min(width, height)
+ val maxOfSize = max(width, height)
+ if (initialSize.x < initialSize.y) {
+ wm.setForcedDisplaySize(defaultDisplayId, minOfSize, maxOfSize)
+ } else {
+ wm.setForcedDisplaySize(defaultDisplayId, maxOfSize, minOfSize)
+ }
+ wm.setForcedDisplayDensityForUser(defaultDisplayId, density, UserHandle.myUserId())
+ }
+ }
+
+ // TODO(b/231131244): Fork FlowRow from Accompanist and use that instead to make sure that users
+ // don't miss any available configuration.
LazyRow {
// Dark/light theme.
item {
@@ -82,5 +131,70 @@ fun ConfigurationControls(
}
}
}
+
+ // Display emulation.
+ EmulatedDisplays.forEach { display ->
+ item {
+ DisplayButton(
+ display,
+ emulatedDisplay == display,
+ { emulatedDisplayName = it?.name },
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun DisplayButton(
+ display: EmulatedDisplay,
+ selected: Boolean,
+ onChangeEmulatedDisplay: (EmulatedDisplay?) -> Unit,
+) {
+ val onClick = {
+ if (selected) {
+ onChangeEmulatedDisplay(null)
+ } else {
+ onChangeEmulatedDisplay(display)
+ }
+ }
+
+ val content: @Composable RowScope.() -> Unit = {
+ Icon(display.icon, null)
+ Spacer(Modifier.width(8.dp))
+ Text(display.name)
+ }
+
+ if (selected) {
+ Button(onClick, contentPadding = ButtonDefaults.TextButtonContentPadding, content = content)
+ } else {
+ TextButton(onClick, content = content)
}
}
+
+/** The displays that can be emulated from this Gallery app. */
+private val EmulatedDisplays =
+ listOf(
+ EmulatedDisplay(
+ "Phone",
+ Icons.Default.Smartphone,
+ width = 1440,
+ height = 3120,
+ densityDpi = 560,
+ ),
+ EmulatedDisplay(
+ "Tablet",
+ Icons.Default.Tablet,
+ width = 2560,
+ height = 1600,
+ densityDpi = 320,
+ ),
+ )
+
+private data class EmulatedDisplay(
+ val name: String,
+ val icon: ImageVector,
+ val width: Int,
+ val height: Int,
+ val densityDpi: Int,
+)