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
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user